Überlegungen zur Nachrichtencodierung

Viele Cloudanwendungen verwenden asynchrone Nachrichten zum Austauschen von Informationen zwischen Komponenten des Systems. Ein wichtiger Aspekt des Messagings ist das Format, in dem die Nutzlastdaten codiert werden. Nach dem Auswählen einer Messagingtechnologie besteht der nächste Schritt darin, zu definieren, wie die Nachrichten codiert werden. Es stehen zahlreiche Optionen zur Verfügung, die richtige Auswahl hängt jedoch von Ihrem Anwendungsfall ab.

In diesem Artikel werden einige der Überlegungen beschrieben.

Anforderungen an den Nachrichtenaustausch

Für den Nachrichtenaustausch zwischen einem Producer und einem Consumer wird Folgendes benötigt:

  • Eine Form oder Struktur, welche die Nutzlast der Nachricht definiert.
  • Ein Codierungsformat, in dem die Nutzlast dargestellt wird.
  • Serialisierungsbibliotheken zum Lesen und Schreiben der codierten Nutzlast.

Der Producer der Nachricht definiert die Nachrichtenform entsprechend der Geschäftslogik und den Informationen, die an den bzw. die Consumer gesendet werden sollen. Um die Form zu strukturieren, unterteilen Sie die Informationen in eindeutige oder verwandte Themen (Felder). Legen Sie die Merkmale der Werte für diese Felder fest. Bedenken Sie: Welcher Datentyp ist am effizientesten? Weist die Nutzlast immer bestimmte Felder auf? Enthält die Nutzlast einen einzigen Datensatz oder eine wiederholte Reihe von Werten?

Wählen Sie anschließend ein Codierungsformat entsprechend den jeweiligen Anforderungen aus. Bestimmte Faktoren umfassen die Fähigkeit, stark strukturierte Daten zu erstellen, wenn diese benötigt werden, die zum Codieren und Übermitteln der Nachricht benötigte Zeit sowie die Fähigkeit, die Nutzlast zu analysieren. Wählen Sie je nach Codierungsformat eine Serialisierungsbibliothek aus, die umfassend unterstützt wird.

Einem Consumer der Nachricht müssen diese Entscheidungen bekannt sein, damit er weiß, wie eingehende Nachrichten gelesen werden können.

Zum Übertragen von Nachrichten serialisiert der Producer die Nachricht in einem Codierungsformat. Auf der Empfangsseite deserialisiert der Consumer die Nutzlast, um die Daten zu verwenden. Auf diese Weise wird das Modell von beiden Entitäten gemeinsam genutzt, und solange die Form nicht geändert wird, erfolgt das Messaging ohne Probleme. Ändert sich der Vertrag, sollte das Codierungsformat in der Lage sein, die Änderung zu verarbeiten, ohne dass ein Ausfall für den Consumer stattfindet.

Einige Codierungsformate (wie z. B. JSON) sind selbstbeschreibend. Das heißt, sie können ohne Verweis auf ein Schema analysiert werden. Solche Formate erzeugen jedoch tendenziell größere Nachrichten. Bei anderen Formaten werden die Daten möglicherweise weniger einfach analysiert, die Nachrichten sind jedoch kompakt. In diesem Artikel werden einige Faktoren erläutert, die Ihnen bei der Auswahl eines Formats behilflich sein können.

Überlegungen zum Codierungsformat

Das Codierungsformat definiert, wie eine Menge von strukturierten Daten als Bytes dargestellt wird. Der Nachrichtentyp kann die Auswahl des Formats beeinflussen. Nachrichten im Zusammenhang mit Geschäftstransaktionen enthalten höchstwahrscheinlich stark strukturierte Daten. Möglicherweise möchten Sie sie auch später zu Überprüfungszwecken abrufen. Für einen Ereignisdatenstrom empfiehlt es sich, eine Sequenz von Datensätzen so schnell wie möglich zu lesen und für die statistische Analyse zu speichern.

Im Folgenden werden einige Aspekte erläutert, die beim Auswählen eines Codierungsformats zu beachten sind.

Vom Menschen lesbares Format

Die Nachrichtencodierung kann im Wesentlichen in Textformate und Binärformate aufgeteilt werden.

Bei der textbasierten Codierung liegt die Nachrichtennutzlast im Klartext vor und kann daher von einer Person ohne Verwendung von Codebibliotheken gelesen werden. Vom Menschen lesbare Formate eignen sich für Archivdaten. Da die Nutzlast von einem Menschen gelesen werden kann, lassen sich textbasierte Formate leichter debuggen und für das Troubleshooting an Protokolle senden.

Ein Nachteil besteht darin, dass die Nutzlast tendenziell größer ist. Ein gängiges textbasiertes Format ist JSON.

Verschlüsselung

Wenn die Nachrichten vertrauliche Daten enthalten, empfiehlt es sich gegebenenfalls, diese Nachrichten vollständig zu verschlüsseln, wie in diesem Leitfaden zum Verschlüsseln ruhender Azure Service Bus-Daten beschrieben. Wenn nur bestimmte Felder verschlüsselt werden müssen und Sie die Cloudkosten reduzieren möchten, können Sie alternativ eine Bibliothek wie NServiceBus verwenden.

Codierungsgröße

Die Nachrichtengröße wirkt sich auf die Netzwerk-E/A-Leistung bei der Übertragung aus. Binärformate sind im Allgemeinen kompakter als textbasierte Formate. Für Binärformate sind Serialisierungs-/Deserialisierungsbibliotheken erforderlich. Die Nutzlast kann erst gelesen werden, nachdem sie decodiert wurde.

Verwenden Sie ein Binärformat, wenn Sie die Auslastung auf das Netzwerk mindern und Nachrichten schneller übertragen möchten. Diese Kategorie von Formaten empfiehlt sich in Szenarien, in denen Speicher und Netzwerkbandbreite von Relevanz sind. Optionen für Binärformate sind Apache Avro, Google Protocol Buffers (protobuf), MessagePack und Concise Binary Object Representation (CBOR). Die Vor-und Nachteile dieser Formate werden hier beschrieben.

Der Nachteil besteht darin, dass die Nutzlast nicht vom Menschen lesbar ist. Für die meisten Binärformate werden komplexe Systeme verwendet, deren Verwaltung sehr aufwändig ist. Außerdem werden spezielle Bibliotheken zum Decodieren benötigt, und dies wird möglicherweise nicht unterstützt, wenn Sie Archivdaten abrufen möchten.

Grundlegendes zur Nutzlast

Eine Nachrichtennutzlast wird als Sequenz von Bytes empfangen. Um diese Sequenz zu analysieren, muss der Consumer Zugriff auf Metadaten haben, welche die Datenfelder in der Nutzlast beschreiben. Es gibt zwei grundlegende Ansätze zum Speichern und Verteilen von Metadaten:

Markierte Metadaten. Bei einigen Codierungen, insbesondere JSON, werden Felder im Text der Nachricht mit dem Datentyp und dem Bezeichner markiert. Diese Formate sind selbstbeschreibend, da Sie ohne Verweis auf ein Schema in ein Wörterbuch mit Werten analysiert werden können. Eine Möglichkeit für den Consumer, die Felder zu verstehen, besteht darin, erwartete Werte abzufragen. Der Producer sendet beispielsweise eine Nutzlast in JSON. Der Consumer analysiert den JSON-Code in ein Wörterbuch und prüft auf das Vorhandensein Feldern, um die Nutzlast zu verstehen. Eine andere Möglichkeit für den Consumer besteht darin, ein Datenmodell anzuwenden, das gemeinsam mit dem Producer genutzt wird. Wenn Sie z. B. eine statisch typisierte Sprache verwenden, können viele JSON-Serialisierungsbibliotheken eine JSON-Zeichenfolge in eine typisierte Klasse analysieren.

Schema. Ein Schema definiert formal die Struktur und die Datenfelder einer Nachricht. In diesem Modell verfügen Producer und Consumer aufgrund eines klar definierten Schemas über einen Vertrag. Das Schema kann die Datentypen, erforderliche/optionale Felder, Versionsinformationen und die Struktur der Nutzlast definieren. Der Producer sendet die Nutzlast gemäß dem Writer-Schema. Der Consumer empfängt die Nutzlast durch Anwenden eines Reader-Schemas. Die Nachricht wird mithilfe der codierungsspezifischen Bibliotheken serialisiert bzw. deserialisiert. Es gibt zwei Möglichkeiten, Schemas zu verteilen:

  • Speichern Sie das Schema als Präambel oder Header in der Nachricht, jedoch getrennt von der Nutzlast.

  • Speichern Sie das Schema extern.

Einige Codierungsformate definieren das Schema und verwenden Tools, die Klassen aus dem Schema generieren. Producer und Consumer verwenden diese Klassen und Bibliotheken, um die Nutzlast zu serialisieren und zu deserialisieren. Die Bibliotheken bieten außerdem Prüfungen zur Kompatibilität von Writer-und Reader-Schema. Dieser Ansatz gilt sowohl für protobuf als auch für Apache Avro. Der grundlegende Unterschied besteht darin, dass protobuf über eine sprachunabhängige Schemadefinition verfügt, Avro jedoch ein kompaktes JSON verwendet. Ein weiterer Unterschied besteht darin, wie beide Formate Prüfungen der Kompatibilität zwischen Reader-und Writer-Schema bereitstellen.

Eine andere Möglichkeit besteht darin, das Schema extern in einer Schemaregistrierung zu speichern. Die Nachricht enthält einen Verweis auf das Schema und die Nutzlast. Der Producer sendet den Schemabezeichner in der Nachricht, und der Consumer ruft das Schema durch Angabe dieses Bezeichners aus einem externen Speicher ab. Beide Parteien verwenden eine formatspezifische Bibliothek zum Lesen und Schreiben von Nachrichten. Neben der Speicherung des Schemas kann eine Registrierung auch Kompatibilitätsprüfungen bieten, um sicherzustellen, dass der Vertrag zwischen Producer und Consumer bei Weiterentwicklung des Schemas weiterhin intakt ist.

Entscheiden Sie vor Auswahl eines Ansatzes, was wichtiger ist: die Datengröße bei der Übertragung oder die Möglichkeit, die archivierten Daten zu einem späteren Zeitpunkt zu analysieren.

Das Speichern des Schemas zusammen mit der Nutzlast bewirkt eine größere Codierungsgröße und wird für zeitweilige Nachrichten bevorzugt. Wählen Sie diesen Ansatz aus, wenn die Übertragung kleinerer Byteblöcke entscheidend ist oder wenn Sie eine Sequenz von Datensätzen erwarten. Die Kosten für die Pflege eines externen Schemaspeichers können hoch sein.

Ist jedoch die bedarfsgesteuerte Decodierung der Nutzlast wichtiger als die Größe, garantieren das Einschließen des Schemas in die Nutzlast oder der Ansatz mit markierten Metadaten die spätere Decodierung. Möglicherweise ist ein beträchtliches Anwachsen der Nachrichtengröße zu beobachten, und dies kann sich auf die Speicherkosten auswirken.

Versionsverwaltung des Schemas

Wenn sich Geschäftsanforderungen ändern, wird eine Änderung der Form erwartet, und das Schema entwickelt sich weiter. Die Versionsverwaltung ermöglicht es dem Producer, Schemaaktualisierungen anzugeben, die möglicherweise neue Features enthalten. Die Versionsverwaltung umfasst zwei Aspekte:

  • Dem Consumer sollten die Änderungen zur Kenntnis gebracht werden.

    Eine Möglichkeit besteht darin, dass der Consumer alle Felder prüft, um festzustellen, ob sich das Schema geändert hat. Eine andere Möglichkeit besteht darin, dass der Producer mit der Nachricht eine Versionsnummer des Schemas veröffentlicht. Bei Weiterentwicklung des Schemas erhöht der Producer die Versionsnummer.

  • Änderungen dürfen die Geschäftslogik von Consumern weder beeinträchtigen noch gegen diese verstoßen.

    Angenommen, einem vorhandenen Schema wird ein Feld hinzugefügt. Wenn Consumer, die die neue Version verwenden, eine Nutzlast gemäß der alten Version erhalten, wird möglicherweise gegen ihre Logik verstoßen, wenn sie das Fehlen des neuen Felds nicht bemerken. Betrachten wir den umgekehrten Fall: Angenommen, im neuen Schema wird ein Feld entfernt. Consumer, die das alte Schema verwenden, können die Daten möglicherweise nicht lesen.

    Codierungsformate wie Avro bieten die Möglichkeit, Standardwerte zu definieren. Wenn im vorstehenden Beispiel das Feld mit einem Standardwert hinzugefügt wird, wird das fehlende Feld mit dem Standardwert ausgefüllt. Andere Formate wie protobuf bieten mit erforderlichen und optionalen Felder eine ähnliche Funktionalität.

Nutzlaststruktur

Betrachten Sie die Anordnung von Daten in der Nutzlast. Handelt es sich um eine Sequenz von Datensätzen oder eine eindeutige einzelne Nutzlast? Die Nutzlaststruktur kann einem der folgenden Modelle zugeordnet werden:

  • Array/Wörterbuch/Wert: Definiert Einträge, die Werte in ein- oder mehrdimensionalen Arrays enthalten. Einträge verfügen über eindeutige Schlüssel-Wert-Paare. Die Erweiterung ist möglich, sodass komplexe Strukturen dargestellt werden. Beispiele hierfür sind JSON, Apache Avro und MessagePack.

    Dieses Layout ist geeignet, wenn Nachrichten einzeln mit unterschiedlichen Schemas codiert sind. Wenn Sie über mehrere Datensätze verfügen, kann die Nutzlast übermäßig redundant werden, wodurch die Nutzlast überfrachtet wird.

  • Tabellendaten: Informationen sind auf Zeilen und Spalten verteilt. Jede Spalte gibt ein Feld bzw. den Betreff der Informationen an, und jede Zeile enthält Werte für diese Felder. Dieses Layout ist effizient bei einer sich wiederholenden Menge von Informationen, z. B. Zeitreihendaten.

    CSV ist eines der einfachsten textbasierten Formate. Es stellt Daten als Sequenz von Datensätzen mit einem gemeinsamen Header dar. Bei der Binärcodierung hat Apache Avro eine Präambel, die einem CSV-Header ähnelt, es wird jedoch eine kompakte Codierungsgröße generiert.

Bibliotheksunterstützung

Erwägen Sie, für ein proprietäres Modell bekannte Formate zu verwenden.

Bekannte Formate werden durch Bibliotheken unterstützt, die universell von der Community unterstützt werden. Für spezialisierte Formate benötigen Sie spezielle Bibliotheken. Ihre Geschäftslogik muss möglicherweise einige der von den Bibliotheken bereitgestellten API-Designoptionen umgehen.

Wählen Sie für schemabasiertes Format eine Codierungsbibliothek aus, die Prüfungen der Kompatibilität zwischen dem Reader- und dem Writer-Schema durchführt. Bestimmte Codierungsbibliotheken, z. B. Apache Avro, erwarten, dass der Consumer sowohl das Writer- als auch das Reader-Schema angibt, bevor die Nachricht deserialisiert wird. Mit dieser Überprüfung wird sichergestellt, dass dem Consumer die Schemaversionen bekannt sind.

Interoperabilität

Die Formatauswahl hängt möglicherweise von der jeweiligen Workload oder dem Technologieökosystem ab.

Beispiel:

  • Azure Stream Analytics bietet native Unterstützung für JSON, CSV und Avro. Wenn Sie Stream Analytics verwenden, sollten Sie nach Möglichkeit eines dieser Formate auswählen. Andernfalls können Sie einen benutzerdefinierten Deserialisierer bereitstellen, dadurch steigt jedoch die Komplexität Ihrer Lösung.

  • JSON ist ein standardmäßiges Austauschformat für HTTP-REST-APIs. Wenn Ihre Anwendung JSON-Nutzlasten von Clients empfängt und diese dann für die asynchrone Verarbeitung in einer Nachrichtenwarteschlange platziert, ist es möglicherweise sinnvoll, JSON für das Messaging zu verwenden und keine Neucodierung in ein anderes Format vorzunehmen.

Dies sind nur zwei Beispiele für Überlegungen zur Interoperabilität. Im Allgemeinen zeichnen sich standardisierte Formate durch eine höhere Interoperabilität als benutzerdefinierte Formate aus. Für textbasierte Optionen bietet u. a. JSON die höchste Interoperabilität.

Auswahlmöglichkeiten für Codierungsformate

Im Folgenden finden Sie einige häufig verwendete Codierungsformate. Berücksichtigen Sie die Überlegungen, bevor Sie ein Format auswählen.

JSON

JSON ist ein offener Standard (IETF RFC8259). Es handelt sich um ein textbasiertes Format, das auf dem Modell Array/Wörterbuch/Wert aufbaut.

JSON kann zum Markieren von Metadaten verwendet werden, und Sie können die Nutzlast ohne ein Schema analysieren. JSON unterstützt die Option zum Angeben optionaler Felder; dies ist nützlich in Bezug auf Aufwärts- und Abwärtskompatibilität.

Der größte Vorteil des Formats besteht in seiner universellen Verfügbarkeit. Es bietet höchste Interoperabilität und ist das Standardcodierungsformat für viele Messagingdienste.

Als textbasiertes Format ist es bei der Übertragung über das Netzwerk nicht effizient und auch nicht die erste Wahl, wenn der Speicher wichtig ist. Wenn Sie zwischengespeicherte Elemente direkt per HTTP an einen Client zurückgeben, können mit der JSON-Speicherung die Kosten der Deserialisierung aus einem anderen Format und der anschließenden Serialisierung in JSON gespart werden.

Verwenden Sie JSON für Nachrichten mit einem einzigen Datensatz oder für eine Nachrichtensequenz, in der jede Nachricht ein anderes Schema hat. Vermeiden Sie die Verwendung von JSON für eine Sequenz von Datensätzen, z. B. für Zeitreihendaten.

Es gibt Variationen von JSON, z. B. BSON, eine Binärcodierung, die auf die Verwendung mit MongoDB ausgelegt ist.

Durch Trennzeichen getrennte Werte (CSV)

CSV ist ein textbasiertes Tabellenformat. Der Header der Tabelle gibt die Felder an. Die Auswahl dieses Formats empfiehlt sich, wenn die Nachricht eine Reihe von Datensätzen enthält.

Der Nachteil ist die fehlende Standardisierung. Es gibt eine Vielzahl von Möglichkeiten, Trennzeichen, Header und leere Felder auszudrücken.

Protocol Buffers (protobuf)

Protocol Buffers (oder protobuf) ist ein Serialisierungsformat, bei dem in stark typisierten Definitionsdateien Schemas in Schlüssel-Wert-Paaren definiert sind. Anschließend werden die Definitionsdateien in sprachspezifische Klassen zum Serialisieren und Deserialisieren von Nachrichten kompiliert.

Die Nachricht enthält eine komprimierte binäre kleine Nutzlast, die eine schnellere Übertragung bewirkt. Der Nachteil besteht darin, dass die Nutzlast vom Menschen nicht lesbar ist. Da das Schema extern ist, empfiehlt sich das Format auch nicht für Fälle, in denen archivierte Daten abgerufen werden müssen.

Apache Avro

Apache Avro ist ein binäres Serialisierungsformat, das wie protobuf eine Definitionsdatei verwendet; es gibt jedoch keinen Kompilierungsschritt. Stattdessen enthalten serialisierte Daten immer eine Schemapräambel.

Die Präambel kann den Header oder einen Schemabezeichner enthalten. Wegen der geringeren Codierungsgröße wird Avro für Streamingdaten empfohlen. Da das Format über einen Header verfügt, der für eine Reihe von Datensätzen gilt, ist es auch eine gute Wahl für Tabellendaten.

MessagePack

MessagePack ist ein binäres Serialisierungsformat mit einem kompakten Design für die Übertragung. Es werden keine Nachrichtenschemas oder Überprüfungen des Nachrichtentyps verwendet. Dieses Format empfiehlt sich nicht für Massenspeicher.

CBOR

Concise Binary Object Representation (CBOR) (Spezifikation) ist ein Binärformat, das eine geringe Codierungsgröße bietet. Der Vorteil von CBOR gegenüber MessagePack besteht in der Konformität mit IETF in RFC7049.

Nächste Schritte