Protocolo de almacén de estados
El almacén de estados es un sistema de almacenamiento distribuido dentro del clúster de Operaciones de IoT de Azure. El almacén de estado ofrece las mismas garantías de alta disponibilidad que los mensajes MQTT en el corredor MQTT. Según las directrices del protocolo MQTT5/RPC, los clientes deben usar MQTT5 para interactuar con el almacén de estados. En este artículo se proporcionan instrucciones de protocolo para los desarrolladores que necesitan implementar sus propios clientes de almacén de estados.
Información general
El almacén de estados admite los siguientes comandos:
SET
<keyName><keyValue><setOptions>GET
<keyName>DEL
<keyName>VDEL
<keyName><keyValue> ## Elimina un <keyName> determinado si y solo si su valor es <keyValue>
El protocolo usa el siguiente modelo de solicitud-respuesta:
- Solicitud. Los clientes publican una solicitud en un tema del sistema de almacén de estado bien definido. Para publicar la solicitud, los clientes usan las propiedades y la carga necesarias que se describen en las secciones siguientes.
- Response. El almacén de estados procesa de forma asincrónica la solicitud y responde en el tema de respuesta que proporcionó inicialmente el cliente.
En el diagrama siguiente se muestra la vista básica de la solicitud y la respuesta:
Tema del sistema de almacén de estado, QoS y propiedades de MQTT5 necesarias
Para comunicarse con el almacén de estado, los clientes deben cumplir los siguientes requisitos:
- Usar MQTT5. Para obtener más información, consulte la especificación MQTT 5.
- Usar QoS 1 (calidad del nivel servicio 1). QoS 1 se describe en la especificación MQTT 5.
- Tener un reloj que esté dentro de un minuto del reloj del agente MQTT.
Para comunicarse con el almacén de estado, los clientes deben PUBLISH
solicitudes al tema del sistema statestore/v1/FA9AE35F-2F64-47CD-9BFF-08E2B32A0FE8/command/invoke
. Dado que el almacén de estado forma parte de operaciones de Azure IoT, realiza un SUBSCRIBE
implícito en este tema al iniciarse.
Para compilar una solicitud, se requieren las siguientes propiedades MQTT5. Si estas propiedades no están presentes o la solicitud no es de tipo QoS 1, se produce un error en la solicitud.
- Tema de respuestas. El almacén de estado responde a la solicitud inicial con este valor. Como procedimiento recomendado, dé formato al tema de respuesta como
clients/{clientId}/services/statestore/_any_/command/invoke/response
. Establecer el tema de respuesta comostatestore/v1/FA9AE35F-2F64-47CD-9BFF-08E2B32A0FE8/command/invoke
o como uno que empieza porclients/statestore/v1/FA9AE35F-2F64-47CD-9BFF-08E2B32A0FE8
no está permitido en una solicitud de almacén de estados. El almacén de estado desconecta los clientes MQTT que usan un tema de respuesta no válido. - Datos de correlación. Cuando el almacén de estado envía una respuesta, incluye los datos de correlación de la solicitud inicial.
En el diagrama siguiente se muestra una vista expandida de la solicitud y la respuesta:
Comandos admitidos:
Los comandos SET
, GET
y DEL
se comportan según lo previsto.
Los valores que establece el comando SET
y el comando GET
recupera son datos binarios arbitrarios. El tamaño de los valores solo está limitado por el tamaño máximo de la carga útil de MQTT y las limitaciones de recursos del corredor MQTT y del cliente.
Opciones de SET
El comando SET
proporciona más marcas opcionales más allá de las básicas keyValue
y keyName
:
NX
. Permite establecer la clave solo si aún no existe.NEX <value>
. Permite establecer la clave solo si la clave no existe o si el valor de la clave ya está establecido en <valor>. La marcaNEX
se usa normalmente para un cliente que renueva la expiración (PX
) en una clave.PX
. Cuánto tiempo debe conservar la clave antes de que expire, en milisegundos.
Opciones de VDEL
El comando VDEL
es un caso especial del comando DEL
. DEL
elimina incondicionalmente el objeto especificado keyName
. VDEL
requiere otro argumento denominado keyValue
. VDEL
solo elimina el keyName
especificado si tiene el mismo keyValue
.
Formato de carga
El formato de carga PUBLISH
del almacén de estado está inspirado en RESP3, que es el protocolo subyacente que Usa Redis. RESP3 codifica el verbo, como SET
o GET
, y los parámetros como keyName
y keyValue
.
Distinción entre mayúsculas y minúsculas
El cliente debe enviar los verbos y las opciones en mayúsculas.
Formato de solicitud
Las solicitudes tienen el formato en el ejemplo siguiente. Después de RESP3, *
representa el número de elementos de una matriz. El carácter $
es el número de caracteres de la línea siguiente, excepto la CRLF final.
Los comandos admitidos en formato RESP3 son GET
, SET
, DEL
y 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>
En la salida de ejemplo siguiente se muestran las cargas RESP3 del almacén de estado:
*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>
Nota:
SET
requiere más propiedades MQTT5, como se explica en la sección Control de versiones y relojes lógicos híbridos.
Formato de respuesta
Cuando el almacén de estado detecta una carga RESP3 no válida, sigue devolviendo una respuesta al Response Topic
del solicitante. Algunos ejemplos de cargas no válidas incluyen un comando no válido, un RESP3 no válido o un desbordamiento entero. Una carga no válida comienza con la cadena -ERR
y contiene más detalles.
Nota:
Una solicitud GET
, DEL
o VDEL
en una clave inexistente no se considera un error.
Si un cliente envía una carga no válida, el almacén de estado envía una carga como en el ejemplo siguiente:
-ERR syntax error
Respuesta SET
Cuando una solicitud SET
se realiza correctamente, el almacén de estado devuelve la siguiente carga:
+OK<CR><LF>
Si se produce un error en una solicitud SET debido a una comprobación de condición especificada en las opciones set de NX o NEX que significa que la clave no puede establecerse, el almacén de estado devuelve la siguiente carga útil:
-1<CR><LF>
Respuesta GET
Cuando se realiza una solicitud GET
en una clave inexistente, el almacén de estado devuelve la siguiente carga:
$-1<CR><LF>
Cuando se encuentra la clave, el almacén de estado devuelve el valor en el formato siguiente:
${NumberOfBytes}<CR><LF>
{KEY-VALUE}
La salida del almacén de estado que devuelve el valor 1234
es similar al ejemplo siguiente:
$4<CR><LF>1234<CR><LF>
Respuesta DEL
y VDEL
El almacén de estado devuelve el número de valores que elimina en una solicitud de eliminación. Actualmente, el almacén de estado solo puede eliminar un valor a la vez.
:{NumberOfDeletes}<CR><LF> // Will be 1 on successful delete or 0 if the keyName is not present
La salida siguiente es un ejemplo de un comando DEL
correcto:
:1<CR><LF>
Si se produce un error en una solicitud VDEL porque el valor especificado no coincide con el valor asociado a la clave, el almacén de estado devuelve la siguiente carga útil:
-1<CR><LF>
respuestas de -ERR
A continuación se muestra la lista actual de cadenas de error. Su aplicación cliente debe controlar las cadenas de error desconocidas para soportar las actualizaciones del almacén de estado.
Cadena de error devuelta desde el almacén de estado | Explicación |
---|---|
la marca de tiempo de solicitud está demasiado lejos en el futuro; asegúrese de que los relojes del sistema del corredor y cliente están sincronizados | La marca de tiempo de solicitud inesperada causada por el almacén de estado y los relojes del cliente no están sincronizados. |
se requiere un token de barrera para esta solicitud | Se produce un error si una clave está marcada con un token de barrera, pero el cliente no especifica el token de barrera. |
la marca de tiempo del token de barrera de solicitud está demasiado lejos en el futuro; asegúrese de que los relojes del sistema del corredor y cliente están sincronizados | Marca de tiempo de token de barrera inesperada causada porque los relojes del almacén de estado y del cliente no están sincronizados. |
el token de barrera de solicitud es una versión inferior que el token de barrera protege el recurso | Versión incorrecta del token de barrera de solicitud. Para obtener más información, consulte [Control de versiones y relojes lógicos híbridos]. (#versioning-and-hybrid-logical-clocks) |
se ha superado la cuota | El almacén de estado tiene una cuota de cuántas claves puede almacenar, que se basa en el perfil de memoria del corredor MQTT especificado. |
error de sintaxis | La carga útil enviada no se ajusta a la definición del almacén de estado. |
no autorizado | Error de autorización |
comando desconocido | No se reconoce el comando. |
número incorrecto de argumentos | Número incorrecto de argumentos esperados. |
falta la marca de tiempo | Cuando los clientes hacen un SET, deben establecer la propiedad de usuario MQTT5 __ts como un HLC que representa su marca de tiempo. |
marca de tiempo con formato incorrecto | La marca de tiempo de la __ts o el token de barrera no es legal. |
la longitud de la clave es cero | Las claves no pueden ser de longitud cero en el almacén de estado. |
Control de versiones y relojes lógicos híbridos
En esta sección se describe cómo el almacén de estado controla el control de versiones.
Versiones como relojes lógicos híbridos
El almacén de estado mantiene una versión para cada valor que almacena. El almacén de estado podría usar un contador que aumenta monotónicamente para mantener las versiones. En su lugar, el almacén de estados usa un reloj lógico híbrido (HLC) para representar versiones. Para obtener más información, consulte los artículos sobre el diseño original de HLC y la intención detrás de HLC.
El almacén de estado usa el siguiente formato para definir HLC:
{wallClock}:{counter}:{node-Id}
wallClock
es el número de milisegundos desde la época de Unix. counter
y node-Id
funcionan como HLC en general.
Cuando los clientes realizan una SET
, deben establecer la propiedad de usuario MQTT5 __ts
como un HLC que representa su marca de tiempo, en función del reloj actual del cliente. El almacén de estado devuelve la versión del valor en su mensaje de respuesta. La respuesta también se especifica como HLC y también usa la propiedad __ts
de usuario MQTT5. El HLC devuelto siempre es mayor que el HLC de la solicitud inicial.
Ejemplo de configuración y recuperación de la versión de un valor
En esta sección se muestra un ejemplo de configuración y obtención de la versión de un valor.
Un cliente establece keyName=value
. El reloj del cliente es el 3 de octubre a las 11:07:05 p.m. GMT. El valor del reloj es 1696374425000
milisegundos desde la época de Unix. Supongamos que el reloj del sistema del almacén de estado es idéntico al reloj del sistema cliente. El cliente realiza el comando SET
como se ha descrito anteriormente.
En el diagrama siguiente se muestra el comando SET
:
La propiedad __ts
(marca de tiempo) del conjunto inicial contiene 1696374425000
como reloj de pared del cliente, el contador como 0
y su identificador de nodo como CLIENT
. En la respuesta, la propiedad __ts
que devuelve el almacén de estado contiene wallClock
, el contador incrementó en uno y el node-Id como StateStore
. El almacén de estado podría devolver un valor mayor de wallClock
si su reloj estaba por delante, en función de la forma en que funcionan las actualizaciones de HLC.
Esta versión también se devuelve en solicitudes correctas GET
, DEL
y VDEL
. En estas solicitudes, el cliente no especifica un __ts
.
En el diagrama siguiente se muestra el comando GET
:
Nota:
La marca de tiempo __ts
que devuelve el almacén de estado es la misma que la que devolvió en la solicitud de SET
inicial.
Si una clave determinada se actualiza más adelante con un nuevo SET
, el proceso es similar. El cliente debe establecer su solicitud __ts
en función de su reloj actual. El almacén de estado actualiza la versión del valor y devuelve __ts
, siguiendo las reglas de actualización de HLC.
Sesgo del reloj
El almacén de estado rechaza __ts
(y también__ft
) que es más de un minuto antes del reloj local del almacén de estado.
El almacén de estado acepta un __ts
que está detrás del reloj local del almacén de estado. Como se especifica en el algoritmo HLC, el almacén de estado establece la versión de la clave en su reloj local porque es mayor.
Tokens de bloqueo y barrera
En esta sección se describe el propósito y el uso de tokens de bloqueo y barrera.
Fondo
Supongamos que hay dos o más clientes MQTT que usan el almacén de estado. Ambos clientes quieren escribir en una clave determinada. Los clientes del almacén de estado necesitan un mecanismo para bloquear la clave de modo que solo un cliente a la vez pueda modificar una clave determinada.
Un ejemplo de este escenario se produce en sistemas activos y en espera. Podría haber dos clientes que realicen la misma operación y la operación podría incluir el mismo conjunto de claves de almacén de estado. En un momento dado, uno de los clientes está activo y el otro está preparado para funcionar si el sistema activo se bloquea o deja de funcionar. Idealmente, solo un cliente debe escribir en el almacén de estado en un momento dado. Sin embargo, en los sistemas distribuidos es posible que ambos clientes se comporten como si estuvieran activos y que intentaran escribir simultáneamente en las mismas claves. Este escenario crea una condición de carrera.
El almacén de estados proporciona mecanismos para evitar esta condición de carrera mediante tokens de barrera. Para obtener más información sobre los tokens de barrera y la clase de condiciones de carrera que están diseñados para evitar, consulte este artículo.
Obtención de un token de barrera
En este ejemplo se supone que tenemos los siguientes elementos:
Client1
yClient2
. Estos clientes son clientes de almacén de estado que actúan como un par activo y en espera.LockName
. Nombre de una clave en el almacén de estado que actúa como bloqueo.ProtectedKey
. Clave que debe protegerse de varios escritores.
Los clientes intentan obtener un bloqueo como primer paso. Obtienen un bloqueo haciendo un SET LockName {CLIENT-NAME} NEX PX {TIMEOUT-IN-MILLISECONDS}
. Recuerde de Establecimiento de opciones que la marca NEX
significa que solo SET
se realiza correctamente si se cumple una de las condiciones siguientes:
- La clave estaba vacía
- El valor de la clave ya está establecido en <value> y
PX
especifica el tiempo de espera en milisegundos.
Supongamos que Client1
va primero con una solicitud de SET LockName Client1 NEX PX 10000
. Esta solicitud le concede la propiedad de LockName
durante 10 000 milisegundos. Si Client2
intenta un SET LockName Client2 NEX ...
mientras Client1
posee el bloqueo, la marca NEX
significa que se produce un error en la solicitud Client2
. Client1
debe renovar este bloqueo enviando el mismo comando SET
usado para adquirir el bloqueo, si Client1
desea continuar teniendo la propiedad.
Nota:
Un SET NX
es conceptualmente equivalente a AcquireLock()
.
Uso de los tokens de barrera en las solicitudes SET
Cuando Client1
realiza correctamente un SET
("AcquireLock") en LockName
, el almacén de estados devuelve la versión de LockName
como un reloj lógico híbrido (HLC) en la propiedad de usuario MQTT5 __ts
.
Cuando un cliente realiza una solicitud SET
, puede incluir opcionalmente la propiedad de usuario MQTT5 __ft
para representar un «token de barrera». __ft
se representa como un HLC. El token de barrera asociado a un par clave-valor determinado proporciona la comprobación de la propiedad del bloqueo. El token de barrera puede provenir de cualquier lugar. En este escenario, debe proceder de la versión de LockName
.
En el diagrama siguiente se muestra el proceso de Client1
realizando una solicitud SET
en LockName
:
A continuación, Client1
usa la propiedad __ts
(Property=1696374425000:1:StateStore
) sin modificar como base de la propiedad __ft
en la solicitud para modificar ProtectedKey
. Al igual que todas las solicitudes SET
, el cliente debe establecer la propiedad __ts
de ProtectedKey
.
En el diagrama siguiente se muestra el proceso de Client1
realizando una solicitud SET
en ProtectedKey
:
Si la solicitud se realiza correctamente, desde este punto ProtectedKey
requiere un token de barrera igual o mayor que el especificado en la solicitud SET
.
Algoritmo de token de barrera
El almacén de estado acepta cualquier HLC para el __ts
de un par clave-valor, si el valor está dentro del sesgo máximo del reloj. Sin embargo, lo mismo no es cierto para los tokens de barrera.
El algoritmo de almacén de estado para los tokens de barrera es el siguiente:
- Si un par clave-valor no tiene un token de barrera asociado a él y una solicitud
SET
establece__ft
, el almacén de estado almacena el__ft
asociado al par clave-valor. - Si un par clave-valor tiene un token de barrera asociado:
- Si una solicitud
SET
no especificó__ft
, rechace la solicitud. - Si una solicitud
SET
especificó un__ft
que tiene un valor HLC anterior al token de barrera asociado al par clave-valor, rechace la solicitud. - Si una solicitud
SET
especificó un__ft
que tiene un valor HLC igual o más reciente que el token de barrera asociado al par clave-valor, acepte la solicitud. El almacén de estado actualiza el token de barrera del par clave-valor para que sea el que se establece en la solicitud, si es más reciente.
- Si una solicitud
Después de marcar una clave con un token de barrera, para que una solicitud se realice correctamente, las solicitudes DEL
y VDEL
también requieren que se incluya la propiedad __ft
. El algoritmo es idéntico al anterior, salvo que el token de barrera no se almacena porque se elimina la clave.
Comportamiento del cliente
Estos mecanismos de bloqueo dependen de que los clientes se comporten bien. En el ejemplo anterior, un Client2
que se comporta incorrectamente no pudo poseer LockName
y pudo realizar correctamente un SET ProtectedKey
mediante la elección de un token de barrera que sea más reciente que el token ProtectedKey
. El almacén de estado no es consciente de que LockName
y ProtectedKey
tienen una relación. Como resultado, el almacén de estado no realiza la validación que Client2
posee realmente el valor.
Que los clientes puedan escribir claves para las que realmente no poseen el bloqueo es un comportamiento no deseado. Puede protegerse contra este cliente que se comporta de forma incorrecta mediante la implementación correcta de clientes y el uso de la autenticación para limitar el acceso a las claves solo a los clientes de confianza.
Notificaciones
Los clientes pueden registrarse en el almacén de estado para recibir notificaciones de las claves que se modifican. Considere el escenario en el que un termostato usa la clave de almacén de estado {thermostatName}\setPoint
. Otros clientes del almacén de estado pueden cambiar el valor de esta clave para cambiar el setPoint del termostato. En lugar de sondear los cambios, el termostato puede registrarse con el almacén de estado para recibir mensajes cuando se modifica {thermostatName}\setPoint
.
Mensajes de solicitud KEYNOTIFY
Los clientes del almacén de estado solicitan al almacén de estado que supervise un keyName
determinado para ver los cambios mediante el envío de un mensaje de KEYNOTIFY
. Al igual que todas las solicitudes de almacén de estado, los clientes publican un mensaje QoS1 con este mensaje a través de MQTT5 al tema del sistema de almacén de estado statestore/v1/FA9AE35F-2F64-47CD-9BFF-08E2B32A0FE8/command/invoke
.
La carga de solicitud tiene el siguiente formato:
KEYNOTIFY<CR><LF>
{keyName}<CR><LF>
{optionalFields}<CR><LF>
Donde:
- KEYNOTIFY es un literal de cadena que especifica el comando.
{keyName}
es el nombre de clave en el que se van a escuchar las notificaciones. Actualmente no se admiten caracteres comodín.{optionalFields}
Los valores de campo opcional admitidos actualmente son:{STOP}
Si hay una notificación existente con el mismokeyName
yclientId
que esta solicitud, el almacén de estado lo quita.
En la salida de ejemplo siguiente se muestra una solicitud de KEYNOTIFY
para supervisar la clave SOMEKEY
:
*2<CR><LF>
$9<CR><LF>
KEYNOTIFY<CR><LF>
$7<CR><LF>
SOMEKEY<CR><LF>
Mensaje de respuesta KEYNOTIFY
Al igual que todas las solicitudes RPC del almacén de estado, el almacén de estado devuelve su respuesta a la Response Topic
y usa las propiedades Correlation Data
especificadas a partir de la solicitud inicial. Para KEYNOTIFY
, una respuesta correcta indica que el almacén de estado procesó la solicitud. Una vez que el almacén de estado procesa correctamente la solicitud, supervisa la clave del cliente actual o detiene la supervisión.
En caso de éxito, la respuesta del almacén de estado es la misma que un SET
exitoso.
+OK<CR><LF>
Si un cliente envía una solicitud de KEYNOTIFY SOMEKEY STOP
pero el almacén de estado no supervisa esa clave, la respuesta del almacén de estado es la misma que intentar eliminar una clave que no existe.
:0<CR><LF>
Cualquier otro error sigue el patrón general de informes de errores del almacén de estado:
-ERR: <DESCRIPTION OF ERROR><CR><LF>
Temas y ciclo de vida de las notificaciones KEYNOTIFY
Cuando se modifica o elimina un keyName
supervisado a través de KEYNOTIFY
, el almacén de estados envía una notificación al cliente. El tema viene determinado por convención: el cliente no especifica el tema durante el proceso de KEYNOTIFY
.
El tema se define en el siguiente ejemplo. El clientId
es una representación codificada hexadecimal en mayúsculas del MQTT ClientId del cliente que inició la solicitud KEYNOTIFY
y el keyName
es una representación codificada hexadecimal de la clave que cambió. El almacén de estados sigue las reglas de codificación Base 16 de RFC 4648: Las codificaciones de datos Base16, Base32 y Base64 para esta codificación.
clients/statestore/v1/FA9AE35F-2F64-47CD-9BFF-08E2B32A0FE8/{clientId}/command/notify/{keyName}
Por ejemplo, el corredor MQTT publica un mensaje NOTIFY
enviado a client-id1
con el nombre de clave modificado SOMEKEY
en el tema:
clients/statestore/v1/FA9AE35F-2F64-47CD-9BFF-08E2B32A0FE8/636C69656E742D696431/command/notify/534F4D454B4559`
Un cliente que utilice notificaciones debe SUBSCRIBE
a este tema y esperar a que se reciban las SUBACK
antes de enviar cualquier KEYNOTIFY
solicitud para que no se pierdan mensajes.
Si un cliente se desconecta, debe volver a suscribirse al tema de notificación de KEYNOTIFY
y volver a enviar el comando KEYNOTIFY
para las claves que necesita para continuar con la supervisión. A diferencia de las suscripciones MQTT, que pueden persistir a lo largo de una sesión no limpia, el almacén de estado elimina internamente cualquier mensaje KEYNOTIFY
cuando un cliente determinado se desconecta.
Formato del mensaje de notificación KEYNOTIFY
Cuando se modifica una clave que se está supervisando a través de KEYNOTIFY
, el almacén de estado PUBLISH
enviará un mensaje al tema de notificación siguiendo el formato a los clientes del almacén de estado registrados para el cambio.
NOTIFY<CR><LF>
{operation}<CR><LF>
{optionalFields}<CR><LF>
Los detalles siguientes se incluyen en el mensaje:
NOTIFY
es un literal de cadena incluido como primer argumento de la carga, lo que indica que llegó una notificación.{operation}
es el evento que se produjo. Actualmente, estas operaciones son:SET
el valor se modificó. Esta operación solo se puede producir como resultado de un comandoSET
desde un cliente de almacén de estado.DEL
el valor se eliminó. Esta operación puede producirse debido a un comandoDEL
oVDEL
desde un cliente de almacén de estado.
optionalFields
VALUE
y{MODIFIED-VALUE}
.VALUE
es una literal de cadena que indica que el siguiente campo,{MODIFIED-VALUE}
, contiene el valor por el que se cambió la clave. Este valor solo se envía en respuesta a las claves que se modifican debido a unSET
.
La siguiente salida de ejemplo muestra un mensaje de notificación enviado cuando la clave SOMEKEY
se modifica al valor abc
, con el VALUE
incluido porque la solicitud inicial especificaba la opción 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>
El mensaje de notificación KEYNOTIFY
contiene la marca de tiempo del valor al notificar a un cliente sobre una solicitud SET (valor actualizado) o al notificar a un cliente sobre una solicitud DEL o VDEL (valor eliminado). La marca de tiempo se incluye como parte de la propiedad de usuario MQTT v5 del mensaje __ts. Para más información, consulte la sección Versiones como relojes lógicos híbridos.