Partilhar via


Considerações sobre codificação de mensagens

Muitas aplicações na nuvem utilizam mensagens assíncronas para trocar informações entre componentes do sistema. Um aspeto importante das mensagens é o formato usado para codificar os dados de carga útil. Depois de escolher uma tecnologia de mensagens, a próxima etapa é definir como as mensagens serão codificadas. Existem muitas opções disponíveis, mas a escolha certa depende do seu caso de uso.

Este artigo descreve algumas das considerações.

Necessidades de troca de mensagens

Uma troca de mensagens entre um produtor e um consumidor necessita:

  • Uma forma ou estrutura que define a carga útil da mensagem.
  • Um formato de codificação para representar a carga útil.
  • Bibliotecas de serialização para ler e gravar a carga codificada.

O produtor da mensagem define a forma da mensagem com base na lógica de negócios e nas informações que deseja enviar aos consumidores. Para estruturar a forma, divida a informação em assuntos discretos ou relacionados (campos). Decida as características dos valores para esses campos. Considere: Qual é o tipo de dados mais eficiente? A carga útil terá sempre determinados campos? A carga útil terá um único registo ou um conjunto repetido de valores?

Em seguida, escolha um formato de codificação dependendo da sua necessidade. Alguns fatores incluem a capacidade de criar dados altamente estruturados, se necessário, o tempo necessário para codificar e transferir a mensagem e a capacidade de analisar a carga útil. Dependendo do formato de codificação, escolha uma biblioteca de serialização que seja bem suportada.

Um consumidor da mensagem deve estar ciente dessas decisões para saber ler as mensagens recebidas.

Para transferir mensagens, o produtor serializa a mensagem para um formato de codificação. Na extremidade recetora, o consumidor desserializa a carga para usar os dados. Dessa forma, ambas as entidades compartilham o modelo e, desde que a forma não mude, as mensagens continuam sem problemas. Quando o contrato é alterado, o formato de codificação deve ser capaz de lidar com a alteração sem perturbar o consumidor.

Alguns formatos de codificação, como JSON, são autodescritos, o que significa que podem ser analisados sem fazer referência a um esquema. No entanto, tais formatos tendem a produzir mensagens maiores. Com outros formatos, os dados podem não ser analisados tão facilmente, mas as mensagens são compactas. Este artigo destaca alguns fatores que podem ajudá-lo a escolher um formato.

Considerações sobre o formato de codificação

O formato de codificação define como um conjunto de dados estruturados é representado como bytes. O tipo de mensagem pode influenciar a escolha do formato. As mensagens relacionadas a transações comerciais provavelmente conterão dados altamente estruturados. Além disso, você pode querer recuperá-lo mais tarde para fins de auditoria. Para um fluxo de eventos, convém ler uma sequência de registros o mais rápido possível e armazená-la para análise estatística.

Aqui estão alguns pontos a considerar ao escolher um formato de codificação.

Legibilidade humana

A codificação de mensagens pode ser amplamente dividida em formatos binários e baseados em texto.

Com a codificação baseada em texto, a carga útil da mensagem é em texto simples e, portanto, pode ser inspecionada por uma pessoa sem usar nenhuma biblioteca de código. Formatos legíveis por humanos são adequados para dados de arquivo. Além disso, como um ser humano pode ler a carga útil, os formatos baseados em texto são mais fáceis de debugar e enviar para logs para solução de problemas.

A desvantagem é que a carga útil tende a ser maior. Um formato comum baseado em texto é JSON.

Encriptação

Se houver dados confidenciais nas mensagens, considere se essas mensagens devem ser criptografadas em sua totalidade, conforme descrito nesta orientação sobre criptografando dados do Barramento de Serviço do Azure em repouso. Como alternativa, se apenas alguns campos precisarem ser criptografados e você preferir reduzir os custos de nuvem, considere usar uma biblioteca como NServiceBus para isso.

Tamanho da codificação

O tamanho da mensagem afeta o desempenho de E/S da rede através do fio. Os formatos binários são mais compactos do que os formatos baseados em texto. Os formatos binários requerem bibliotecas de serialização/desserialização. A carga não pode ser lida a menos que seja decodificada.

Use um formato binário se quiser reduzir a pegada do fio e transferir mensagens mais rapidamente. Essa categoria de formato é recomendada em cenários em que o armazenamento ou a largura de banda da rede são motivo de preocupação. As opções para formatos binários incluem Apache Avro, Google Protocol Buffers (protobuf), MessagePack e Concise Binary Object Representation (CBOR). Os prós e contras desses formatos são descritos nesta seção .

A desvantagem é que a carga útil não é legível por humanos. A maioria dos formatos binários usa sistemas complexos que podem ser caros de manter. Além disso, eles precisam de bibliotecas especializadas para decodificar, o que pode não ser suportado se você quiser recuperar dados de arquivamento.

Compreender a carga útil

Uma carga útil de mensagem chega como uma sequência de bytes. Para analisar essa sequência, o consumidor deve ter acesso a metadados que descrevem os campos de dados na carga útil. Existem duas abordagens principais para armazenar e distribuir metadados:

Tagged metadados. Em algumas codificações, nomeadamente JSON, os campos são marcados com o tipo de dados e o identificador, dentro do corpo da mensagem. Esses formatos são autodescritivos porque podem ser analisados em um dicionário de valores sem se referir a um esquema. Uma forma de o consumidor entender os campos é consultar os valores esperados. Por exemplo, o produtor envia uma carga útil em JSON. O consumidor analisa o JSON em um dicionário e verifica a existência de campos para entender a carga útil. Outra forma é o consumidor aplicar um modelo de dados compartilhado pelo produtor. Por exemplo, ao usar uma linguagem de tipagem estática, muitas bibliotecas de serialização JSON conseguem converter uma string JSON em uma classe tipada.

Esquema. Um esquema define formalmente a estrutura e os campos de dados de uma mensagem. Neste modelo, produtor e consumidor têm um contrato através de um esquema bem definido. O esquema pode definir os tipos de dados, campos obrigatórios/opcionais, informações de versão e a estrutura da carga útil. O produtor envia a carga segundo o esquema do escritor. O consumidor recebe o conteúdo aplicando um esquema de leitura. A mensagem é serializada/desserializada usando bibliotecas de codificação específicas. Há duas maneiras de distribuir esquemas:

  • Armazene o esquema como um preâmbulo ou cabeçalho na mensagem, mas separado da carga útil.

  • Armazene o esquema externamente.

Alguns formatos de codificação definem o esquema e usam ferramentas que geram classes a partir do esquema. O produtor e o consumidor usam essas classes e bibliotecas para serializar e desserializar a carga útil. As bibliotecas também fornecem verificações de compatibilidade entre o esquema de escrita e de leitura. Tanto o protobuf quanto o Apache Avro seguem essa abordagem. A principal diferença é que o protobuf tem uma definição de esquema independente de linguagem, mas Avro usa JSON compacto. Outra diferença está na forma como os formatos fornecem verificações de compatibilidade entre os esquemas do leitor e do escritor.

Outra maneira de armazenar o esquema externamente em um registro de esquema. A mensagem contém uma referência ao esquema e à carga útil. O produtor envia o identificador de esquema na mensagem e o consumidor recupera o esquema especificando esse identificador a partir de um repositório externo. Ambas as partes usam a biblioteca específica do formato para ler e escrever mensagens. Além de armazenar o esquema, um registro pode fornecer verificações de compatibilidade para garantir que o contrato entre o produtor e o consumidor não seja quebrado à medida que o esquema evolui.

Antes de escolher uma abordagem, decida o que é mais importante: o tamanho dos dados de transferência ou a capacidade de analisar os dados arquivados posteriormente.

O armazenamento do esquema junto com a carga útil produz um tamanho de codificação maior e é preferível para mensagens intermitentes. Escolha essa abordagem se a transferência de blocos menores de bytes for crucial ou se você esperar uma sequência de registros. O custo de manutenção de um armazenamento de esquema externo pode ser alto.

No entanto, se a decodificação sob demanda da carga útil for mais importante do que o tamanho, incluir o esquema com a carga útil ou a abordagem de metadados marcados garante a decodificação posteriormente. Pode haver um aumento significativo no tamanho da mensagem e pode afetar o custo de armazenamento.

Controle de versão do esquema

À medida que os requisitos de negócios mudam, espera-se que a forma mude e o esquema evolua. O controle de versão permite que o produtor indique atualizações de esquema que podem incluir novos recursos. Há dois aspetos no controle de versão:

  • O consumidor deve estar ciente das mudanças.

    Uma maneira é o consumidor verificar se o esquema foi alterado, verificando todos os campos. Outra maneira é o produtor publicar um número de versão do esquema com a mensagem. Quando o esquema evolui, o produtor incrementa a versão.

  • As alterações não devem afetar nem quebrar a lógica empresarial dos consumidores.

    Suponha que um campo seja adicionado a um esquema existente. Se os consumidores que usam a nova versão receberem dados conforme a versão antiga, a lógica deles pode falhar se não conseguirem desconsiderar a ausência do novo campo. Considerando o caso inverso, suponha que um campo seja removido no novo esquema. Os consumidores que usam o esquema antigo podem não conseguir ler os dados.

    Formatos de codificação como Avro oferecem a capacidade de definir valores padrão. No exemplo anterior, se o campo for adicionado com um valor padrão, o campo ausente será preenchido com o valor padrão. Outros formatos como protobuf fornecem funcionalidade semelhante através de campos obrigatórios e opcionais.

Estrutura da carga útil

Considere a forma como os dados são organizados na carga útil. É uma sequência de registos ou uma carga útil única discreta? A estrutura de carga útil pode ser categorizada em um destes modelos:

  • Matriz/dicionário/valor: define entradas que contêm valores em matrizes uma ou multidimensionais. As entradas têm pares chave-valor distintos. Pode ser estendido para representar as estruturas complexas. Alguns exemplos incluem, JSON, Apache Avro e MessagePack.

    Esse layout é adequado se as mensagens forem codificadas individualmente com esquemas diferentes. Se tiveres vários registos, a carga útil pode tornar-se excessivamente redundante, causando o inchamento da carga útil.

  • Dados tabulares: As informações são divididas em linhas e colunas. Cada coluna indica um campo ou o assunto das informações e cada linha contém valores para esses campos. Esse layout é eficiente para um conjunto repetitivo de informações, como dados de séries temporais.

    CSV é um dos formatos baseados em texto mais simples. Apresenta os dados como uma sequência de registos com um cabeçalho comum. Para codificação binária, o Apache Avro tem um preâmbulo que é semelhante a um cabeçalho CSV, mas gera um tamanho de codificação mais compacto.

Apoio à biblioteca

Considere o uso de formatos bem conhecidos em vez de um modelo proprietário.

Formatos bem conhecidos são suportados através de bibliotecas que são universalmente suportadas pela comunidade. Com formatos especializados, você precisa de bibliotecas específicas. Sua lógica de negócios pode ter que contornar algumas das opções de design de API fornecidas pelas bibliotecas.

Para o formato baseado em esquema, escolha uma biblioteca de codificação que faça verificações de compatibilidade entre o esquema do leitor e do gravador. Certas bibliotecas de codificação de dados, como o Apache Avro, esperam que o consumidor especifique tanto o esquema de escrita como o de leitura antes de desserializar a mensagem. Essa verificação garante que o consumidor esteja ciente das versões do esquema.

Interoperabilidade

Sua escolha de formatos pode depender da carga de trabalho específica ou do ecossistema de tecnologia.

Por exemplo:

  • O Azure Stream Analytics tem suporte nativo para JSON, CSV e Avro. Ao usar o Stream Analytics, faz sentido escolher um desses formatos, se possível. Caso contrário, você pode fornecer um desserializador personalizado, mas isso adiciona alguma complexidade adicional à sua solução.

  • JSON é um formato de intercâmbio padrão para APIs HTTP REST. Se seu aplicativo recebe cargas JSON de clientes e, em seguida, as coloca em uma fila de mensagens para processamento assíncrono, pode fazer sentido usar JSON para as mensagens, em vez de recodificar em um formato diferente.

Estes são apenas dois exemplos de considerações em matéria de interoperabilidade. Em geral, os formatos padronizados serão mais interoperáveis do que os formatos personalizados. Nas opções baseadas em texto, o JSON é um dos mais interoperáveis.

Opções para formatos de codificação

Aqui estão alguns formatos de codificação populares. Considere as considerações antes de escolher um formato.

JSON

JSON é um padrão aberto (IETF RFC8259). É um formato baseado em texto que segue o modelo de matriz/dicionário/valor.

JSON pode ser usado para marcar metadados e você pode analisar a carga útil sem um esquema. O JSON suporta a opção de especificar campos opcionais, o que ajuda na compatibilidade direta e retroativa.

A maior vantagem é que está universalmente disponível. É mais interoperável e o formato de codificação padrão para muitos serviços de mensagens.

Sendo um formato baseado em texto, não é eficiente sobre o fio e não é uma escolha ideal nos casos em que o armazenamento é uma preocupação. Se estiver a retornar elementos em cache diretamente para um cliente via HTTP, armazenar em JSON pode poupar o custo de desserializar de outro formato e, de seguida, serializar para JSON.

Use JSON para mensagens de registro único ou para uma sequência de mensagens em que cada mensagem tem um esquema diferente. Evite usar JSON para uma sequência de registros, como para dados de séries temporais.

Existem outras variações de JSON, como binário JSON (BSON), que é uma codificação binária alinhada para trabalhar com o MongoDB.

Comma-Separated - Valores (CSV)

CSV é um formato tabular baseado em texto. O cabeçalho da tabela indica os campos. É uma escolha preferida onde a mensagem contém um conjunto de registros.

A desvantagem é a falta de padronização. Há muitas maneiras de expressar separadores, cabeçalhos e campos vazios.

Buffers de protocolo (protobuf)

Protocol Buffers (ou protobuf) é um formato de serialização que usa arquivos de definição fortemente tipados para definir esquemas em pares chave/valor. Esses arquivos de definição são então compilados para classes específicas do idioma que são usadas para serializar e desserializar mensagens.

A mensagem contém uma pequena carga binária comprimida, o que resulta numa transferência mais rápida. A desvantagem é que a carga útil não é legível por humanos. Além disso, como o esquema é externo, ele não é recomendado para casos em que você precisa recuperar dados arquivados.

Apache Avro

Apache Avro é um formato de serialização binário que usa um arquivo de definição semelhante ao protobuf, mas não há uma etapa de compilação. Em vez disso, os dados serializados sempre incluem um preâmbulo de esquema.

O preâmbulo pode conter o cabeçalho ou um identificador de esquema. Devido ao tamanho menor da codificação, o Avro é recomendado para streaming de dados. Além disso, como ele tem um cabeçalho que se aplica a um conjunto de registros, é uma boa opção para dados tabulares.

Pacote de mensagens

MessagePack é um formato de serialização binário projetado para ser compacto para transmissão por fio. Não há esquemas de mensagem ou verificação de tipo de mensagem. Este formato não é recomendado para armazenamento em massa.

CBOR

de representação de objeto binário conciso (CBOR) (especificação) é um formato binário que oferece tamanho de codificação pequeno. A vantagem do CBOR sobre o MessagePack é que é compatível com IETF em RFC7049.