Протокол хранилища состояний брокера MQTT
Внимание
Предварительная версия операций Интернета вещей Azure, включенная Azure Arc в настоящее время в предварительной версии. Не следует использовать это программное обеспечение предварительной версии в рабочих средах.
Вам потребуется развернуть новую установку Операций Интернета вещей Azure, когда общедоступная версия станет доступной. Вы не сможете обновить предварительную установку.
Юридические условия, применимые к функциям Azure, которые находятся в бета-версии, предварительной версии или в противном случае еще не выпущены в общедоступную версию, см . в дополнительных условиях использования для предварительных версий Microsoft Azure.
Хранилище состояний MQ — это распределенная система хранения в кластере операций Интернета вещей Azure. Хранилище состояний предлагает те же гарантии высокой доступности, что и сообщения MQTT в брокере MQTT. В соответствии с рекомендациями по протоколу MQTT5/RPC клиенты должны использовать MQTT5 для взаимодействия с хранилищем состояний MQ. В этой статье приводятся рекомендации по протоколу для разработчиков, которым необходимо реализовать собственные клиенты хранилища состояний брокера MQTT.
Общие сведения о протоколе хранилища состояний
Хранилище состояний MQ поддерживает следующие команды:
SET
<keyName><keyValue><setOptions>GET
<keyName>DEL
<keyName>VDEL
<keyName><keyValue> ## Удаляет заданное <имя> ключа, если и только если его значение является <keyValue>
Протокол использует следующую модель ответа на запрос:
- Запрос. Клиенты публикуют запрос в четко определенный системный раздел хранилища состояний. Для публикации запроса клиенты используют необходимые свойства и полезные данные, описанные в следующих разделах.
- Ответ. Хранилище состояний асинхронно обрабатывает запрос и отвечает на раздел ответа, который клиент первоначально предоставил.
На следующей схеме показано базовое представление запроса и ответа:
Системный раздел хранилища состояний, QoS и обязательные свойства MQTT5
Чтобы взаимодействовать с хранилищем состояний, клиенты должны соответствовать следующим требованиям:
- Используйте MQTT5. Дополнительные сведения см. в спецификации MQTT 5.
- Используйте QoS 1 (уровень обслуживания 1). QoS 1 описано в спецификации MQTT 5.
- У вас есть часы, которые находится в течение одной минуты от часов брокера MQTT.
Чтобы взаимодействовать с хранилищем состояний, клиенты должны PUBLISH
запрашивать системный раздел statestore/v1/FA9AE35F-2F64-47CD-9BFF-08E2B32A0FE8/command/invoke
. Так как хранилище состояний является частью Операций Интернета вещей Azure, оно неявно SUBSCRIBE
относится к этой теме при запуске.
Чтобы создать запрос, требуются следующие свойства MQTT5. Если эти свойства отсутствуют или запрос не имеет типа QoS 1, запрос завершается ошибкой.
- Раздел ответа. Хранилище состояний отвечает на первоначальный запрос, используя это значение. Рекомендуется форматировать раздел ответа как
clients/{clientId}/services/statestore/_any_/command/invoke/response
. Установка раздела ответа какstatestore/v1/FA9AE35F-2F64-47CD-9BFF-08E2B32A0FE8/command/invoke
или как того, с который начинается,clients/statestore/v1/FA9AE35F-2F64-47CD-9BFF-08E2B32A0FE8
не разрешено в запросе хранилища состояний. Хранилище состояний отключает клиенты MQTT, использующие недопустимый раздел ответа. - Данные корреляции. Когда хранилище состояний отправляет ответ, оно включает данные корреляции начального запроса.
На следующей схеме показано расширенное представление запроса и ответа:
Поддерживаемые команды
Команды SET
GET
и DEL
поведение, как ожидалось.
Значения, полученные SET
наборами команд и GET
извлекаемой командой, являются произвольными двоичными данными. Размер значений ограничен максимальным размером полезных данных MQTT и ограничениями ресурсов MQQ и клиента.
SET
options
Команда SET
предоставляет более необязательные флаги за рамки основных keyValue
и keyName
:
NX
. Позволяет задать ключ только в том случае, если он еще не существует.NEX <value>
. Позволяет задать ключ только в том случае, если ключ не существует или значение ключа уже задано значением<>. ОбычноNEX
флаг используется для клиента, обновляющего срок действия ключа (PX
).PX
. Как долго ключ должен сохраняться до истечения срока его действия в миллисекундах.
VDEL
options
Команда VDEL
— это особый случай DEL
команды. DEL
безусловно удаляет указанный keyName
. VDEL
требует другого аргумента, вызываемого keyValue
. VDEL
удаляет только указанный keyName
параметр, если он имеет то же самое keyValue
.
Формат полезных данных
Формат полезных данных хранилища PUBLISH
состояний вдохновлен RESP3, который является базовым протоколом, используемым Redis. RESP3 кодирует как команду, так и параметры, такие как SET
keyName
и keyValue
.GET
Учет регистра
Клиент должен отправлять как команды, так и параметры в верхнем регистре.
Формат запроса
Запросы форматируются, как показано в следующем примере. После RESP3 отображается *
количество элементов в массиве. Символ $
— это число символов в следующей строке, за исключением конечного CRLF.
Поддерживаемые команды в формате RESP3: GET
, SET
DEL
и VDEL
.
*{NUMBER-OF-ARGUMENTS}<CR><LF>
${LENGTH-OF-NEXT-LINE}<CR><LF>
{COMMAND-NAME}<CR><LF>
${LENGTH-OF-NEXT-LINE}<CR><LF> // This is always the keyName with the current supported verbs.
{KEY-NAME}<CR><LF>
// Next lines included only if command has additional arguments
${LENGTH-OF-NEXT-LINE}<CR><LF> // This is always the keyValue for set
{KEY-VALUE}<CR><LF>
В следующем примере выходных данных показаны полезные данные хранилища состояний RESP3:
*3<CR><LF>$3<CR><LF>set<CR><LF>$7<CR><LF>SETKEY2<CR><LF>$6<CR><LF>VALUE5<CR><LF>
*2<CR><LF>$3<CR><LF>get<CR><LF>$7<CR><LF>SETKEY2<CR><LF>
*2<CR><LF>$3<CR><LF>del<CR><LF>$7<CR><LF>SETKEY2<CR><LF>
*3<CR><LF>$4<CR><LF>vdel<CR><LF>$7<CR><LF>SETKEY2<CR><LF>$3<CR><LF>ABC<CR><LF>
Примечание.
Обратите внимание, что SET
требуются дополнительные свойства MQTT5, как описано в разделе "Управление версиями" и гибридные логические часы.
Формат ответа
Когда хранилище состояний обнаруживает недопустимую полезные данные RESP3, он по-прежнему возвращает ответ на запрос.Response Topic
Примеры недопустимых полезных данных включают недопустимую команду, недопустимую команду RESP3 или целый поток переполнения. Недопустимая полезные данные начинается со строки -ERR
и содержит дополнительные сведения.
Примечание.
DEL
VDEL
Значение или GET
запрос на несуществующий ключ не считается ошибкой.
Если клиент отправляет недопустимую полезные данные, хранилище состояний отправляет полезные данные, как показано в следующем примере:
-ERR syntax error
SET
ответ
При успешном выполнении запроса хранилище состояний SET
возвращает следующие полезные данные:
+OK<CR><LF>
Если запрос SET завершается ошибкой, так как проверка условия, указанная в параметрах набора NX или NEX, то это означает, что ключ не может быть задан, хранилище состояний возвращает следующую полезные данные:
-1<CR><LF>
GET
ответ
GET
При выполнении запроса на несуществующий ключ хранилище состояний возвращает следующие полезные данные:
$-1<CR><LF>
При обнаружении ключа хранилище состояний возвращает значение в следующем формате:
${NumberOfBytes}<CR><LF>
{KEY-VALUE}
Выходные данные хранилища состояний, возвращающего значение 1234
, выглядят следующим образом:
$4<CR><LF>1234<CR><LF>
DEL
и VDEL
ответ
Хранилище состояний возвращает количество значений, которые он удаляет при запросе на удаление. В настоящее время хранилище состояний может удалять только одно значение за раз.
:{NumberOfDeletes}<CR><LF> // Will be 1 on successful delete or 0 if the keyName is not present
Ниже приведен пример успешной DEL
команды:
:1<CR><LF>
Если запрос VDEL завершается ошибкой, так как указанное значение не соответствует значению, связанному с ключом, хранилище состояний возвращает следующие полезные данные:
-1<CR><LF>
-ERR
Ответы
Ниже приведен текущий список строк ошибок. Клиентское приложение должно обрабатывать неизвестные строки ошибок для поддержки обновлений в хранилище состояний.
Строка ошибки, возвращенная из хранилища состояний | Описание |
---|---|
Метка времени запроса слишком далеко в будущем; убедитесь, что системные часы клиента и брокера синхронизируются | Непредвиденная метка времени запроса, вызванная хранилищем состояний и часами клиента, не синхронизируются. |
Для этого запроса требуется маркер ограждения | Ошибка возникает, если ключ помечается маркером ограждения, но клиент не указывает маркер ограждения. |
Метка времени маркера ограждения запроса слишком далеко в будущем; убедитесь, что системные часы клиента и брокера синхронизируются | Непредвиденная метка времени маркера ограждения, вызванная хранилищем состояний и часами клиента, не синхронизируются. |
Маркер ограждения запроса является более низкой версией, что маркер ограждения, защищающий ресурс. | Неправильная версия маркера ограждения запроса. Дополнительные сведения см. в разделе [Управление версиями и гибридные логические часы]. (#versioning-и-гибридные-логические часы) |
Превышена квота | Хранилище состояний имеет квоту на количество ключей, которые он может хранить, в зависимости от профиля памяти брокера MQTT, указанного. |
синтаксическая ошибка | Полезные данные, отправленные, не соответствуют определению хранилища состояний. |
не авторизовано | Ошибка авторизации |
Неизвестная команда | Команда не распознана. |
неправильное число аргументов | Неправильное число ожидаемых аргументов. |
отсутствует метка времени | Когда клиенты выполняют НАБОР, они должны задать свойство пользователя MQTT5 __ts как HLC, представляющее метку времени. |
неправильно сформированная метка времени | Метка времени в __ts или маркере ограждения не является законным. |
Длина ключа равна нулю | Ключи не могут быть нулевой длиной в хранилище состояний. |
Управление версиями и гибридные логические часы
В этом разделе описывается, как хранилище состояний обрабатывает управление версиями.
Версии в виде гибридных логических часов
Хранилище состояний поддерживает версию для каждого значения, которое он сохраняет. Хранилище состояний может использовать монотонный счетчик для поддержания версий. Вместо этого хранилище состояний использует гибридные логические часы (HLC) для представления версий. Дополнительные сведения см. в статьях о первоначальном проектировании HLCs и намерении, лежащих в основе HLCs.
Хранилище состояний использует следующий формат для определения HLCs:
{wallClock}:{counter}:{node-Id}
Число wallClock
миллисекунда с эпохи Unix. counter
и node-Id
работайте в качестве HLCs в целом.
При выполнении SET
клиентами необходимо задать свойство __ts
пользователя MQTT5 как HLC, представляющее метку времени на основе текущих часов клиента. Хранилище состояний возвращает версию значения в своем ответном сообщении. Ответ также указывается как HLC, а также использует __ts
свойство пользователя MQTT5. Возвращаемый HLC всегда больше HLC начального запроса.
Пример установки и получения версии значения
В этом разделе показан пример настройки и получения версии для значения.
Наборы keyName=value
клиентов. Часы клиента — 3 октября 11:07:05 GMT. Значение часов — 1696374425000
миллисекунда с эпохи Unix. Предположим, что системные часы хранилища состояний идентичны часам клиентской системы. Клиент выполняет SET
команду, как описано ранее.
На следующей схеме показана SET
команда:
Свойство __ts
(timestamp) в начальном наборе содержит как 1696374425000
клиентские стенные часы, счетчик как 0
и идентификатор узла CLIENT
. В ответе __ts
свойство, возвращаемое хранилищем состояний, содержит wallClock
счетчик, который увеличивается по одному, а идентификатор узла — как StateStore
. Хранилище состояний может вернуть более высокое wallClock
значение, если его часы были впереди, исходя из способа работы обновлений HLC.
Эта версия также возвращается при успешном GET
выполнении VDEL
и DEL
запросах. В этих запросах клиент не указывает __ts
.
На следующей схеме показана GET
команда:
Примечание.
Метка __ts
времени, возвращаемая хранилищем состояний, совпадает с тем, что она возвращается при первоначальном SET
запросе.
Если указанный SET
ключ позже обновляется новым, процесс аналогичен. Клиент должен задать свой запрос __ts
на основе текущих часов. Хранилище состояний обновляет версию значения и возвращает __ts
значение, следуя правилам обновления HLC.
Разница в показаниях часов
Хранилище состояний отклоняет __ts
(а также __ft
) больше минуты впереди местных часов хранилища состояний.
Хранилище состояний принимает значение __ts
, которое находится за локальными часами хранилища состояний. Как указано в алгоритме HLC, хранилище состояний задает версию ключа в локальные часы, так как это больше.
Блокировка и ограждение маркеров
В этом разделе описывается назначение и использование маркеров блокировки и ограждения.
Общие сведения
Предположим, что существует два или более клиентов MQTT с помощью хранилища состояний. Оба клиента хотят записать в заданный ключ. Клиенты хранилища состояний нуждаются в механизме блокировки ключа таким образом, что только один клиент за раз может изменить заданный ключ.
Пример этого сценария возникает в активных и резервных системах. Может быть два клиента, которые выполняют одну и ту же операцию, и операция может включать один и тот же набор ключей хранилища состояний. В данный момент один из клиентов активен, а другой стоит немедленно взять на себя, если активная система зависает или сбой. В идеале только один клиент должен записывать данные в хранилище состояний в определенное время. Однако в распределенных системах возможно, что оба клиента могут вести себя так, как будто они активны, и они могут одновременно пытаться записать в одни и те же ключи. Этот сценарий создает условие гонки.
Хранилище состояний предоставляет механизмы предотвращения этого состояния гонки с помощью маркеров ограждения. Дополнительные сведения о маркерах ограждения и классе условий гонки, которые они предназначены для защиты от, см. в этой статье.
Получение маркера ограждения
В этом примере предполагается, что у нас есть следующие элементы:
Client1
иClient2
. Эти клиенты являются клиентами хранилища состояний, которые действуют как активная и резервная пара.LockName
. Имя ключа в хранилище состояний, которое выступает в качестве блокировки.ProtectedKey
. Ключ, который необходимо защитить от нескольких писателей.
Клиенты пытаются получить блокировку в качестве первого шага. Они получают блокировку, делая SET LockName {CLIENT-NAME} NEX PX {TIMEOUT-IN-MILLISECONDS}
. Отзыв из параметра задания , что флаг означает, что NEX
флаг успешно выполняется только в том случае, SET
если выполняется одно из следующих условий:
- Ключ был пустым
- Значение ключа уже задано <как значение> и
PX
указывает время ожидания в миллисекундах.
Предположим, что сначала Client1
выполняется запрос SET LockName Client1 NEX PX 10000
. Этот запрос предоставляет ему право собственности LockName
на 10 000 миллисекундах. Если Client2
выполняется попытка в SET LockName Client2 NEX ...
некоторое время Client1
владеет блокировкой, NEX
флаг означает, что запрос завершается ошибкой Client2
. Client1
необходимо продлить эту блокировку, отправив ту же SET
команду, которая использовалась для получения блокировки, если Client1
хотите продолжить владение.
Примечание.
A SET NX
концептуально эквивалентен AcquireLock()
.
Использование маркеров ограждения для запросов SET
При Client1
успешном SET
выполнении операции ("AquireLock") хранилище состояний возвращает версию LockName
в качестве гибридной логической часы (HLC) LockName
в свойстве __ts
пользователя MQTT5.
При выполнении SET
запроса клиент может дополнительно включить свойство __ft
пользователя MQTT5 для представления маркера ограждения. Он __ft
представлен как HLC. Маркер ограждения, связанный с заданной парой "ключ-значение", обеспечивает проверку владения блокировкой. Маркер ограждения может поступать из любого места. Для этого сценария она должна быть получена из версии LockName
.
На следующей SET
схеме показан процесс Client1
выполнения запроса поLockName
:
Client1
Затем использует __ts
свойство (Property=1696374425000:1:StateStore
) неизмененные в качестве основы __ft
свойства в запросе для измененияProtectedKey
. Как и все SET
запросы, клиент должен задать __ts
свойство ProtectedKey
.
На следующей SET
схеме показан процесс Client1
выполнения запроса поProtectedKey
:
Если запрос выполнен успешно, с этого момента ProtectedKey
требуется маркер ограждения, равный или больше указанного в запросе SET
.
Алгоритм маркера ограждения
Хранилище состояний принимает любое HLC для __ts
пары "ключ-значение", если значение находится в пределах максимального количества часов. Однако то же самое не верно для маркеров ограждения.
Алгоритм хранилища состояний для маркеров ограждения выглядит следующим образом:
- Если пара "ключ-значение" не связана с ним маркером ограждения и
SET
набором__ft
запросов, хранилище состояний сохраняет связанное__ft
с парой "ключ-значение". - Если пара "ключ-значение" имеет маркер ограждения, связанный с ним:
SET
Если запрос не указан__ft
, отклоните запрос.SET
Если запрос указал__ft
более старое значение HLC, чем маркер ограждения, связанный с парой "ключ-значение", отклоните запрос.SET
Если запрос указал__ft
значение, равное или более новое значение HLC, чем маркер ограждения, связанный с парой "ключ-значение", примите запрос. Хранилище состояний обновляет маркер ограждения пары "ключ-значение", чтобы он был одним из наборов в запросе, если он более новый.
После того как ключ помечается маркером ограждения, для успешного выполнения запроса и DEL
VDEL
запросов также требуется __ft
включить свойство. Алгоритм идентичен предыдущему, за исключением того, что маркер ограждения не хранится, так как ключ удаляется.
Поведение клиента
Эти механизмы блокировки полагаются на то, что клиенты хорошо ведут себя. В предыдущем примере неправильное поведение Client2
не может принадлежать LockName
и по-прежнему успешно выполнять SET ProtectedKey
его, выбрав маркер ограждения, который является более новым, чем ProtectedKey
токен. Хранилище состояний не знает, что LockName
и ProtectedKey
имеет какие-либо отношения. В результате хранилище состояний не выполняет проверку, которая Client2
фактически владеет значением.
Клиенты, способные писать ключи, для которых они фактически не принадлежат блокировке, нежелательно. Вы можете защититься от таких ошибок клиента, правильно реализуя клиенты и используя проверку подлинности, чтобы ограничить доступ к ключам только доверенным клиентам.
Notifications
Клиенты могут регистрироваться в хранилище состояний для получения уведомлений об изменении ключей. Рассмотрим сценарий, в котором термостат использует ключ {thermostatName}\setPoint
хранилища состояний. Другие клиенты хранилища состояний могут изменить значение этого ключа, чтобы изменить setPoint термостата. Вместо опроса изменений термостат может регистрироваться в хранилище состояний для получения сообщений при {thermostatName}\setPoint
изменении.
Сообщения запроса KEYNOTIFY
Клиенты хранилища состояний запрашивают хранилище состояний для отслеживания изменений keyName
, отправляя KEYNOTIFY
сообщение. Как и все запросы хранилища состояний, клиенты публикуют сообщение QoS1 с этим сообщением через MQTT5 в системном разделе statestore/v1/FA9AE35F-2F64-47CD-9BFF-08E2B32A0FE8/command/invoke
хранилища состояний.
Полезные данные запроса имеют следующую форму:
KEYNOTIFY<CR><LF>
{keyName}<CR><LF>
{optionalFields}<CR><LF>
Где:
- KEYNOTIFY — это строковый литерал, указывающий команду.
{keyName}
— это ключевое имя для прослушивания уведомлений. Подстановочные знаки в настоящее время не поддерживаются.{optionalFields}
Поддерживаемые в настоящее время необязательные значения полей:{STOP}
Если есть существующее уведомление с таким жеkeyName
запросом,clientId
хранилище состояний удаляет его.
В следующем примере выходных данных показан KEYNOTIFY
запрос на мониторинг ключа SOMEKEY
:
*2<CR><LF>
$9<CR><LF>
KEYNOTIFY<CR><LF>
$7<CR><LF>
SOMEKEY<CR><LF>
Ответное сообщение KEYNOTIFY
Как и все запросы RPC хранилища состояний, хранилище состояний возвращает ответ на Response Topic
его и использует Correlation Data
свойства, указанные в исходном запросе. Для KEYNOTIFY
успешного ответа указывается, что хранилище состояний обрабатывает запрос. После успешного обработки запроса хранилище состояний отслеживает ключ для текущего клиента или останавливает мониторинг.
При успешном выполнении ответ хранилища состояний совпадает с успешным SET
.
+OK<CR><LF>
Если клиент отправляет KEYNOTIFY SOMEKEY STOP
запрос, но хранилище состояний не отслеживает этот ключ, ответ хранилища состояний совпадает с попыткой удалить ключ, который не существует.
:0<CR><LF>
Любой другой сбой следует общему шаблону отчетов об ошибках хранилища состояний:
-ERR: <DESCRIPTION OF ERROR><CR><LF>
Разделы и жизненный цикл уведомлений KEYNOTIFY
keyName
При изменении или удалении отслеживаемого KEYNOTIFY
хранилища состояний отправляется клиенту уведомление. Тема определяется соглашением. Клиент не указывает тему во время KEYNOTIFY
процесса.
Раздел определен в следующем примере. Это clientId
шестнадцатеричное представление идентификатора клиента MQTT клиента, инициирующего KEYNOTIFY
запрос, и keyName
является шестнадцатеричным представлением измененного ключа. Хранилище состояний следует правилам кодировки Base 16 RFC 4648 — Base16, Base32 и Base64 Data Encodings для этой кодировки .
clients/statestore/v1/FA9AE35F-2F64-47CD-9BFF-08E2B32A0FE8/{clientId}/command/notify/{keyName}
Например, MQ публикует NOTIFY
сообщение, отправленное client-id1
с измененным именем SOMEKEY
ключа в раздел:
clients/statestore/v1/FA9AE35F-2F64-47CD-9BFF-08E2B32A0FE8/636C69656E742D696431/command/notify/534F4D454B4559`
Клиент, использующий уведомления, должен SUBSCRIBE
быть получен в этом разделе и дождитесь SUBACK
получения , прежде чем отправлять все KEYNOTIFY
запросы, чтобы сообщения не были потеряны.
Если клиент отключается, он должен повторно подписаться на KEYNOTIFY
раздел уведомлений и повторно отправить KEYNOTIFY
команду для любых ключей, которые необходимо продолжить мониторинг. В отличие от подписок MQTT, которые могут быть сохранены в неклианном сеансе, хранилище состояний внутренне удаляет все KEYNOTIFY
сообщения при отключении определенного клиента.
Формат сообщения уведомления KEYNOTIFY
При изменении ключа, отслеживаемого с помощью KEYNOTIFY
, хранилище состояний будет PUBLISH
отправлено сообщение в раздел уведомлений после формата для клиентов хранилища состояний, зарегистрированных для изменения.
NOTIFY<CR><LF>
{operation}<CR><LF>
{optionalFields}<CR><LF>
Следующие сведения включаются в сообщение:
NOTIFY
— строковый литерал, включенный в качестве первого аргумента в полезные данные, указывающий на уведомление.{operation}
— это событие, которое произошло. В настоящее время эти операции:SET
значение было изменено. Эта операция может происходить только в результатеSET
команды из клиента хранилища состояний.DEL
Значение было удалено. Эта операция может произойти из-заDEL
илиVDEL
команды из клиента хранилища состояний.
optionalFields
VALUE
и{MODIFIED-VALUE}
.VALUE
— строковый литерал, указывающий, что следующее поле содержит значение,{MODIFIED-VALUE}
на которое был изменен ключ. Это значение отправляется только в ответ на изменения ключей из-заSET
изменения.
В следующем примере выходных данных показано сообщение уведомления, отправленное при изменении ключа SOMEKEY
в значение abc
, с VALUE
включенным, так как первоначальный запрос указал GET
параметр:
*4<CR><LF>
$6<CR><LF>
NOTIFY<CR><LF>
$3<CR><LF>
SET<CR><LF>
$5<CR><LF>
VALUE<CR><LF>
$3<CR><LF>
abc<CR><LF>