Compartir a través de


Consideraciones acerca de la codificación de mensajes

Muchas aplicaciones en la nube usan mensajes asincrónicos para intercambiar información entre los componentes del sistema. Un aspecto importante de la mensajería es el formato que se usa para codificar los datos de la carga útil. Después de elegir una tecnología de mensajería, el siguiente paso consiste en definir cómo se codificarán los mensajes. Hay muchas opciones disponibles, pero la opción adecuada dependerá de su caso de uso.

En este artículo se describen algunas de las consideraciones.

Necesidades del intercambio de mensajes

Un intercambio de mensajes entre un productor y un consumidor necesita:

  • Una forma o estructura que defina la carga útil del mensaje.
  • Un formato de codificación que represente la carga útil.
  • Bibliotecas de serialización para leer y escribir la carga codificada.

El productor del mensaje define la forma de mensaje en función de la lógica del negocio y la información que desea enviar a los consumidores. Para estructurar la forma, divida la información en temas discretos o relacionados (campos). Decida las características de los valores de esos campos. Pregúntese: ¿Cuál es el tipo de datos más eficiente? ¿La carga útil siempre tendrá ciertos campos? ¿La carga útil tiene un único registro o un conjunto repetido de valores?

A continuación, elija un formato de codificación en función de sus necesidades. Algunos factores incluyen la posibilidad de crear datos muy estructurados si es necesario, el tiempo que se tarda en codificar y transferir el mensaje y la capacidad de analizar la carga útil. En función del formato de codificación, elija una biblioteca de serialización que sea compatible.

El consumidor del mensaje debe ser consciente de esas decisiones para saber cómo leer los mensajes entrantes.

Para transferir mensajes, el productor serializa el mensaje a un formato de codificación. En el extremo receptor, el consumidor deserializa la carga útil para utilizar los datos. De este modo, ambas entidades comparten el modelo y, siempre y cuando la forma no cambie, la mensajería continuará sin problemas. Cuando el contrato cambie, el formato de codificación debe ser capaz de controlar el cambio sin provocar interrupciones para el consumidor.

Algunos formatos de codificación, como JSON, son autodescriptivos, lo que significa que se pueden analizar sin hacer referencia a un esquema. Sin embargo, estos formatos tienden a producir mensajes más grandes. Con otros formatos, es posible que los datos no se analicen con facilidad, pero los mensajes son compactos. En este artículo se destacan algunos factores que pueden ayudarle a elegir un formato.

Consideraciones sobre el formato de codificación

El formato de codificación define cómo se representa un conjunto de datos estructurados como bytes. El tipo de mensaje puede influir en la elección de formato. Los mensajes relacionados con transacciones empresariales suelen contener datos muy estructurados. Además, puede que desee recuperarlos posteriormente si son necesarios para una auditoría. En el caso de una transmisión de eventos, es posible que desee leer una secuencia de registros lo más rápidamente posible y almacenarla para realizar análisis estadísticos.

Aquí se indican algunos aspectos que considerar al elegir un formato de codificación.

Legibilidad para los usuarios

La codificación de mensajes se puede dividir de manera generalizada entre formatos binarios y basados en texto.

Con la codificación basada en texto, la carga útil del mensaje está en texto sin formato y, por lo tanto, puede ser inspeccionada por una persona sin usar ninguna biblioteca de código. Los formatos legibles por un usuario son adecuados para los datos de archivo. Además, dado que un usuario puede leer la carga útil, los formatos basados en texto son más fáciles de depurar y enviar a los registros para solucionar errores.

La desventaja es que la carga útil suele ser más grande. Un formato basado en texto común es JSON.

Cifrado

Si hay datos confidenciales en los mensajes, considere si esos mensajes deben cifrarse en su totalidad, tal como se describe en esta guía sobre el cifrado de datos en reposo de Azure Service Bus. Como alternativa, si solo es necesario cifrar determinados campos y prefiere reducir los costos en la nube, considere la posibilidad de usar una biblioteca como NServiceBus para ello.

Tamaño de codificación

El tamaño del mensaje afecta al rendimiento de E/S de red a través de la conexión. Los formatos binarios son más compactos que los de texto. Los formatos binarios requieren bibliotecas de serialización/deserialización. No se puede leer la carga útil a menos que se descodifique.

Use un formato binario si desea reducir la superficie de conexión y transferir mensajes con mayor rapidez. Esta categoría de formato se recomienda en escenarios donde el almacenamiento o el ancho de banda de red suponen un problema. Las opciones de los formatos binarios incluyen Apache Avro, Google Protocol Buffers (protobuf), MessagePack y Concise Binary Object Representation (CBOR). Las ventajas y desventajas de estos formatos se describen en esta sección.

El inconveniente es que la carga útil no es legible. La mayoría de los formatos binarios usan sistemas complejos que pueden ser costosos de mantener. Además, necesitan bibliotecas especializadas para descodificar, que puede que no sean compatibles si desea recuperar datos de archivo.

Descripción de la carga útil

Una carga útil de mensaje llega como una secuencia de bytes. Para analizar esta secuencia, el consumidor debe tener acceso a los metadatos que describen los campos de datos de la carga útil. Existen dos enfoques principales para almacenar y distribuir metadatos:

Metadatos etiquetados. En algunas codificaciones, en especial JSON, los campos se etiquetan con el tipo de datos y el identificador, dentro del cuerpo del mensaje. Estos formatos son autodescriptivos porque se pueden analizar en un diccionario de valores sin hacer referencia a un esquema. Una manera de que el consumidor comprenda los campos es consultar los valores esperados. Por ejemplo, el productor envía una carga útil en JSON. El consumidor analiza el JSON en un diccionario y comprueba la existencia de campos para comprender la carga útil. Otra manera es que el consumidor aplique un modelo de datos compartido por el productor. Por ejemplo, si utiliza un lenguaje con establecimiento estático de tipos, muchas bibliotecas de serialización de JSON pueden analizar una cadena JSON en una clase con tipo.

Esquema. Un esquema define formalmente la estructura y los campos de datos de un mensaje. En este modelo, el productor y el consumidor tienen un contrato mediante un esquema bien definido. El esquema puede definir los tipos de datos, los campos obligatorios y opcionales, la información de la versión y la estructura de la carga útil. El productor envía la carga útil según el esquema de escritura. El consumidor recibe la carga útil mediante la aplicación de un esquema de lectura. El mensaje se serializa o deserializa mediante las bibliotecas específicas de la codificación. Existen dos maneras de distribuir esquemas:

  • Almacene el esquema como preámbulo o encabezado en el mensaje pero separado de la carga útil.

  • Almacene el esquema externamente.

Algunos formatos de codificación definen el esquema y usan herramientas que generan clases a partir del esquema. El productor y el consumidor usan esas clases y bibliotecas para serializar y deserializar la carga útil. Las bibliotecas también proporcionan comprobaciones de compatibilidad entre el esquema de escritura y el de lectura. Tanto protobuf como Apache Avro siguen este enfoque. La principal diferencia es que protobuf tiene una definición de esquema independiente del lenguaje, pero Avro usa JSON compacto. Otra diferencia radica en la forma en que ambos formatos proporcionan comprobaciones de compatibilidad entre los esquemas de lectura y escritura.

Otra manera de almacenar el esquema externamente es hacerlo en un registro de esquema. El mensaje contiene una referencia al esquema y la carga útil. El productor envía el identificador de esquema en el mensaje y el consumidor recupera el esquema especificando ese identificador desde un almacén externo. Ambas partes usan la biblioteca específica del formato para leer y escribir mensajes. Además de almacenar el esquema, un registro puede proporcionar comprobaciones de compatibilidad para asegurarse de que el contrato entre el productor y el consumidor se siga cumpliendo a medida que evolucione el esquema.

Antes de elegir un enfoque, decida qué es más importante: el tamaño de los datos de transferencia o la capacidad de analizar los datos archivados más adelante.

El almacenamiento del esquema junto con la carga útil genera un tamaño de codificación mayor y es la opción preferida para mensajes intermitentes. Elija este enfoque si es importante transferir fragmentos más pequeños de bytes o espera una secuencia de registros. El costo de mantener un almacén de esquema externo puede ser alto.

Sin embargo, si la descodificación a petición de la carga útil es más importante que el tamaño, la inclusión del esquema con la carga útil o el enfoque de metadatos etiquetados garantiza la descodificación posteriormente. El tamaño del mensaje puede aumentar considerablemente y el coste de almacenamiento podría verse afectado.

Versión del esquema

A medida que cambien los requisitos empresariales, se espera que la forma cambie y el esquema evolucione. El control de versiones permite que el productor indique actualizaciones del esquema que pueden incluir nuevas características. Deben tenerse en cuenta dos aspectos con respecto al control de versiones:

  • El consumidor debe conocer los cambios.

    Una manera de conseguirlo es que el consumidor compruebe todos los campos para determinar si el esquema ha cambiado. Otra manera es que el productor publique un número de versión de esquema con el mensaje. Cuando el esquema evolucione, el productor incrementa la versión.

  • Los cambios no deben afectar ni interrumpir la lógica de negocios de los consumidores.

    Supongamos que se agrega un campo a un esquema existente. Si los consumidores que usan la nueva versión obtienen una carga útil conforme a la versión anterior, su lógica podría interrumpirse si no son capaces de pasar por alto la falta del nuevo campo. Imaginemos el caso inverso, en el que un campo se quita del nuevo esquema. Es posible que los consumidores que usan el esquema anterior no puedan leer los datos.

    Los formatos de codificación, como Avro, ofrecen la posibilidad de definir valores predeterminados. En el ejemplo anterior, si el campo se agrega con un valor predeterminado, el campo que falta se rellenará con el valor predeterminado. Otros formatos, como protobuf, proporcionan una funcionalidad similar mediante campos obligatorios y opcionales.

Estructura de la carga útil

Tenga en cuenta la forma en que los datos se organizan en la carga útil. ¿Es una secuencia de registros o una carga útil única discreta? La estructura de la carga útil se puede clasificar en uno de estos modelos:

  • Matriz/diccionario/valor: define entradas que contienen valores en matrices unidimensionales o multidimensionales. Las entradas tienen pares clave-valor únicos. Se puede extender para representar las estructuras complejas. Algunos ejemplos incluyen JSON, Apache Avro y MessagePack.

    Este diseño es adecuado si los mensajes están codificados de forma individual con distintos esquemas. Si tiene varios registros, la carga útil puede llegar a ser excesivamente redundante, lo que provoca un sobredimensionamiento de la carga.

  • Datos tabulares: la información se divide en filas y columnas. Cada columna indica un campo o el asunto de la información y cada fila contiene valores para esos campos. Este diseño es eficaz para un conjunto de información repetido, como los datos de serie temporal.

    CSV es uno de los formatos más sencillos basados en texto. Presenta los datos como una secuencia de registros con un encabezado común. En el caso de la codificación binaria, Apache Avro tiene un preámbulo similar a un encabezado CSV, pero genera un tamaño de codificación compacto.

Compatibilidad con bibliotecas

Considere la posibilidad de usar formatos conocidos en un modelo propio.

Los formatos conocidos son compatibles mediante bibliotecas que la comunidad admite universalmente. Con formatos especializados, necesita bibliotecas específicas. Es posible que la lógica de negocios tenga que evitar algunas de las opciones de diseño de API proporcionadas por las bibliotecas.

En el caso del formato basado en esquema, elija una biblioteca de codificación que realice comprobaciones de compatibilidad entre el esquema de lectura y de escritura. Algunas bibliotecas de codificación, como Apache Avro, esperan que el consumidor especifique tanto el esquema de escritura como el de lectura antes de deserializar el mensaje. Esta comprobación garantiza que el consumidor conozca las versiones del esquema.

Interoperabilidad

La elección de los formatos puede depender de la carga de trabajo determinada o el ecosistema de tecnología.

Por ejemplo:

  • Azure Stream Analytics tiene compatibilidad nativa con JSON, CSV y Avro. Al usar Stream Analytics, es lógico elegir uno de estos formatos si es posible. Si no es así, puede proporcionar un deserializador personalizado, pero esto agrega cierta complejidad adicional a la solución.

  • JSON es un formato de intercambio estándar para las API REST de HTTP. Si la aplicación recibe cargas útiles de JSON de clientes y luego las coloca en una cola de mensajes para el procesamiento asincrónico, puede que tenga sentido usar JSON para la mensajería, en lugar de volver a codificar en un formato diferente.

Estos son solo dos ejemplos de consideraciones de interoperabilidad. En general, los formatos estandarizados serán más interoperables que los formatos personalizados. En las opciones basadas en texto, JSON es uno de los más interoperables.

Opciones para formatos de codificación

Los siguientes son algunos formatos de codificación conocidos. Tenga en cuenta las consideraciones antes de elegir un formato.

JSON

JSON es un estándar abierto (IETF RFC8259). Se trata de un formato basado en texto que sigue el modelo de matriz/diccionario/valor.

JSON se puede usar para etiquetar metadatos y puede analizar la carga útil sin un esquema. JSON admite la opción de especificar campos opcionales, lo que contribuye a la compatibilidad con versiones anteriores y posteriores.

La mayor ventaja es que está disponible universalmente. Es más interoperable y es el formato de codificación predeterminado para muchos servicios de mensajería.

Como formato basado en texto, no es eficaz a través de la conexión y no es ideal en los casos en los que el almacenamiento sea limitado. Si devuelve elementos almacenados en caché directamente a un cliente mediante HTTP, almacenar el código JSON podría ahorrar el costo de deserializar desde otro formato y serializar a JSON.

Use JSON para los mensajes de registro único o para una secuencia de mensajes en la que cada mensaje tenga un esquema diferente. Evite usar JSON para una secuencia de registros, como datos de series temporales.

Hay otras variaciones de JSON, como JSON binario (BSON), que es una codificación binaria alineada para trabajar con MongoDB.

Valores separados por comas (CSV)

CSV es un formato tabular basado en texto. El encabezado de la tabla indica los campos. Es la opción preferida si el mensaje contiene un conjunto de registros.

La desventaja es la falta de estandarización. Hay muchas maneras de expresar separadores, encabezados y campos vacíos.

Protocol Buffers (protobuf)

Búferes de protocolo (o protobuf) es un formato de serialización que utiliza archivos de definición fuertemente tipados para definir esquemas en pares clave-valor. Estos archivos de definición se compilan en clases específicas del lenguaje que se usan para serializar y deserializar los mensajes.

El mensaje contiene una carga útil binaria pequeña y comprimida, lo que resulta en una transferencia más rápida. La desventaja es que la carga útil no es legible. Además, dado que el esquema es externo, no se recomienda para los casos en los que tenga que recuperar datos archivados.

Apache Avro

Apache Avro es un formato de serialización binaria que usa un archivo de definición similar a protobuf, pero no hay ningún paso de compilación. En su lugar, los datos serializados siempre incluyen un preámbulo de esquema.

El preámbulo puede contener el encabezado o un identificador de esquema. Debido a su tamaño de codificación más pequeño, se recomienda Avro para la transmisión de datos. Además, dado que tiene un encabezado que se aplica a un conjunto de registros, es una buena opción para los datos tabulares.

MessagePack

MessagePack es un formato de serialización binario que está diseñado para ser compacto para su transmisión. No hay esquemas de mensaje ni comprobación del tipo de mensaje. Este formato no se recomienda para el almacenamiento masivo.

CBOR

Concise Binary Object Representation (CBOR) (especificación) es un formato binario que ofrece un tamaño de codificación pequeño. La ventaja de CBOR sobre MessagePack es que es compatible con IETF en RFC7049.

Pasos siguientes