Erfahrung in der Entwicklung des Refund Tool-Dienstes mit einer asynchronen API auf Kafka

Was könnte ein großes Unternehmen wie Lamoda mit einem gut etablierten Prozess und Dutzenden miteinander verbundener Dienste dazu zwingen, seinen Ansatz deutlich zu ändern? Die Motivation kann ganz unterschiedlich sein: von der Gesetzgebung bis hin zur Experimentierfreude, die allen Programmierern innewohnt.

Dies bedeutet jedoch nicht, dass Sie nicht mit zusätzlichen Vorteilen rechnen können. Sergey Zaika erklärt Ihnen, welche genauen Vorteile die Implementierung einer ereignisgesteuerten API auf Kafka bietet (fewald). Dabei wird es mit Sicherheit auch einige Überraschungen und interessante Entdeckungen geben – ohne die kommt kein Experiment aus.

Erfahrung in der Entwicklung des Refund Tool-Dienstes mit einer asynchronen API auf Kafka

Haftungsausschluss: Dieser Artikel basiert auf Materialien eines Meetups, das Sergey im November 2018 bei HighLoad++ abgehalten hat. Lamodas Live-Erlebnis der Arbeit mit Kafka zog nicht weniger Zuhörer an als die anderen Präsentationen auf dem Programm. Wir denken, dass dies ein großartiges Beispiel dafür ist, dass es immer möglich und notwendig ist, Gleichgesinnte zu finden, und die Organisatoren von HighLoad++ werden weiterhin versuchen, eine Atmosphäre zu schaffen, die dies fördert.

Über den Prozess

Lamoda ist eine große E-Commerce-Plattform mit eigenem Kontaktcenter, Lieferservice (und vielen Partnern), Fotostudio, riesigem Lager und all dem läuft auf eigener Software. Es gibt Dutzende von Zahlungsmethoden und B2B-Partner, die einige oder alle dieser Dienste nutzen und aktuelle Informationen zu ihren Produkten wünschen. Außerdem ist Lamoda neben der Russischen Föderation in drei weiteren Ländern aktiv, und dort ist alles ein wenig anders. Insgesamt gibt es vermutlich über hundert Möglichkeiten, einen neuen Auftrag zu konfigurieren, der auf seine eigene Art und Weise abgewickelt werden muss. All dies funktioniert mit Hilfe von Dutzenden von Diensten, die auf manchmal nicht offensichtliche Weise kommunizieren. Darüber hinaus gibt es ein zentrales System, dessen Hauptaufgabe die Verwaltung des Auftragsstatus ist. Wir nennen sie BOB, ich arbeite mit ihr.

Rückerstattungstool mit ereignisgesteuerter API

Der Begriff „ereignisgesteuert“ wird ziemlich überstrapaziert. Wir werden etwas genauer definieren, was damit gemeint ist. Lassen Sie mich mit dem Kontext beginnen, in dem wir beschlossen haben, den ereignisgesteuerten API-Ansatz in Kafka auszuprobieren.

Erfahrung in der Entwicklung des Refund Tool-Dienstes mit einer asynchronen API auf Kafka

In jedem Geschäft kommt es neben kostenpflichtigen Bestellungen auch vor, dass das Geschäft Geld zurückerstatten muss, weil das Produkt dem Kunden nicht zusagt. Der Vorgang ist relativ kurz: Wir klären ggf. Informationen und überweisen das Geld.

Aufgrund von Gesetzesänderungen wurde die Rückgabe jedoch komplizierter und wir mussten dafür einen separaten Microservice implementieren.

Erfahrung in der Entwicklung des Refund Tool-Dienstes mit einer asynchronen API auf Kafka

Unsere Motivation:

  1. Gesetz FZ-54 – kurz gesagt, das Gesetz verlangt, dass jede Geldtransaktion, sei es eine Rückerstattung oder ein Beleg, dem Finanzamt innerhalb einer relativ kurzen Frist von wenigen Minuten gemeldet wird. Wir als E-Commerce-Unternehmen führen ziemlich viele Transaktionen durch. Technisch gesehen bedeutet dies eine neue Verantwortung (und daher einen neuen Service) und Verbesserungen in allen zugehörigen Systemen.
  2. BOB-Split — ein unternehmensinternes Projekt, um BOB von einer großen Zahl nicht zum Kerngeschäft gehörender Aufgaben zu entlasten und die Gesamtkomplexität zu reduzieren.

Erfahrung in der Entwicklung des Refund Tool-Dienstes mit einer asynchronen API auf Kafka

Dieses Diagramm zeigt die Hauptsysteme von Lamoda. Jetzt sind die meisten von ihnen eher wie eine Konstellation von 5-10 Microservices um einen schrumpfenden Monolithen. Sie wachsen nach und nach, aber wir versuchen, sie kleiner zu machen, da die Bereitstellung eines in der Mitte ausgewählten Fragments beängstigend ist – wir können nicht zulassen, dass es abstürzt. Wir sind gezwungen, alle Austauschmöglichkeiten (Pfeile) zu reservieren und gehen davon aus, dass einige davon möglicherweise nicht mehr verfügbar sind.

BOB verfügt außerdem über eine ganze Reihe von Austauschmöglichkeiten: Zahlungssysteme, Lieferung, Benachrichtigungen usw.

Technisch gesehen ist BOB:

  • ~150 Zeilen Code + ~100 Zeilen Tests;
  • php7.2 + Zend 1 und Symfony-Komponenten 3;
  • >100 APIs und ~50 ausgehende Integrationen;
  • 4 Länder mit eigener Geschäftslogik.

Die Bereitstellung von BOB ist teuer und mühsam. Die Menge an Code und die Probleme, die er löst, sind so groß, dass niemand alles in den Kopf bekommen kann. Generell gibt es viele Gründe, es zu vereinfachen.

Rückgabeprozess

Zunächst sind zwei Systeme in den Prozess involviert: BOB und Payment. Nun erscheinen zwei weitere:

  • Fiskalisierungsdienst, der sich um Fiskalisierungsfragen und die Kommunikation mit externen Diensten kümmert.
  • Rückerstattungstool, das einfach neue Umtausche vornimmt, um BOB nicht aufzublähen.

Nun sieht der Ablauf folgendermaßen aus:

Erfahrung in der Entwicklung des Refund Tool-Dienstes mit einer asynchronen API auf Kafka

  1. BOB erhält eine Rückerstattungsanfrage.
  2. BOB spricht über dieses Rückerstattungstool.
  3. Das Rückerstattungstool teilt der Zahlung mit: „Erstatten Sie das Geld zurück.“
  4. Zahlung gibt Geld zurück.
  5. Das Rückerstattungstool und BOB synchronisieren ihre Status miteinander, da beide dies derzeit benötigen. Wir sind noch nicht bereit, vollständig auf das Rückerstattungstool umzusteigen, da BOB über eine Benutzeroberfläche, Buchhaltungsberichte und generell viele Daten verfügt, die nicht so einfach übertragen werden können. Sie müssen auf zwei Stühlen sitzen.
  6. Der Antrag auf Fiskalisierung wird versendet.

Als Ergebnis haben wir auf Kafka eine Art Eventbus erstellt – einen Eventbus, an dem alles festgebunden war. Hurra, jetzt haben wir einen Single Point of Failure (Sarkasmus).

Erfahrung in der Entwicklung des Refund Tool-Dienstes mit einer asynchronen API auf Kafka

Die Vor- und Nachteile liegen auf der Hand. Wir haben einen Bus gebaut, was bedeutet, dass jetzt alle Dienste davon abhängen. Dies vereinfacht das Design, führt jedoch zu einer einzelnen Fehlerquelle im System. Kafka stürzt ab und der Prozess wird gestoppt.

Was ist eine ereignisgesteuerte API?

Eine gute Antwort auf diese Frage findet sich im Bericht von Martin Fowler (GOTO 2017). „Die vielen Bedeutungen der ereignisgesteuerten Architektur“.

Kurz gesagt, hier ist, was wir getan haben:

  1. Alle asynchronen Austausche wurden geschlossen Ereignisspeicher. Anstatt jeden interessierten Verbraucher über das Netzwerk über eine Statusänderung zu benachrichtigen, schreiben wir ein Statusänderungsereignis in einen zentralen Speicher, und die am Thema interessierten Verbraucher lesen alles, was von dort erscheint.
  2. Das Ereignis ist in diesem Fall eine Benachrichtigung (Benachrichtigungen), dass sich irgendwo etwas geändert hat. Beispielsweise hat sich der Bestellstatus geändert. Ein Verbraucher, der Daten zur Statusänderung benötigt, die nicht in der Benachrichtigung enthalten sind, kann sich selbst über deren Status informieren.
  3. Die maximale Option ist vollwertiges Event Sourcing, staatliche Übertragung, in dem das Ereignis alle für die Verarbeitung notwendigen Informationen enthält: woher es kam und welchen Status es hatte, wie genau sich die Daten geändert haben usw. Die einzige Frage ist die Machbarkeit und die Menge an Informationen, die Sie sich leisten können zu speichern.

Im Rahmen der Einführung des Refund Tools haben wir die dritte Option genutzt. Dies vereinfachte die Ereignisverarbeitung, da keine detaillierten Informationen abgerufen werden mussten. Außerdem wurde das Szenario eliminiert, bei dem jedes neue Ereignis eine Flut von klärenden Get-Anfragen von Verbrauchern erzeugte.

Rückerstattungstool-Service nicht geladen, daher ist Kafka dort eher ein Probelauf als eine Notwendigkeit. Ich glaube nicht, dass die Unternehmen glücklich wären, wenn der Rückerstattungsservice zu einem hochfrequentierten Projekt würde.

Asynchroner Austausch IM ISTZUSTAND

Für die asynchrone Kommunikation verwendet die PHP-Abteilung normalerweise RabbitMQ. Sie sammelten Daten für die Anfrage, stellten sie in eine Warteschlange und ein Verbraucher desselben Dienstes zählte sie und sendete sie (oder sendete sie nicht). Für die API selbst verwendet Lamoda aktiv Swagger. Wir entwerfen eine API, beschreiben sie in Swagger und generieren Client- und Servercode. Wir verwenden auch ein leicht erweitertes JSON RPC 2.0.

Einige Orte verwenden ESB-Busse, andere leben auf ActiveMQ, aber im Allgemeinen RabbitMQ – Standard.

Asynchroner Austausch

Bei der Gestaltung des Austauschs über den Event-Bus lässt sich eine Analogie erkennen. In ähnlicher Weise beschreiben wir den zukünftigen Datenaustausch durch Beschreibungen der Ereignisstruktur. Das Format ist YAML, die Codegenerierung mussten wir selbst durchführen, der Generator erstellt DTOs gemäß der Spezifikation und bringt Clients und Servern bei, mit ihnen zu arbeiten. Generation geht in zwei Sprachen - Golang und PHP. Dadurch bleibt die Konsistenz der Bibliotheken gewährleistet. Der Generator ist in Golang geschrieben, weshalb er den Namen Gogi erhielt.

Event Sourcing auf Kafka ist eine typische Sache. Es gibt eine Lösung aus der Haupt-Enterprise-Version von Kafka Confluent, es gibt Nakadi, eine Lösung unserer „Brüder“ im Domain-Bereich Zalando. Unser Motivation, mit Vanilla Kafka zu beginnen – ist, die Lösung frei zu lassen, bis wir endgültig entschieden haben, ob wir sie überall einsetzen, und uns auch Spielraum für Manöver und Verbesserungen zu lassen: Wir wollen Unterstützung für unsere JSON RPC 2.0, Generatoren für zwei Sprachen und mal sehen, was es sonst noch gibt.

Es ist ironisch, dass wir selbst in diesem glücklichen Fall, in dem es ein ähnlich gelagertes Unternehmen gibt, nämlich Zalando, das eine ähnliche Entscheidung getroffen hat, diese nicht effektiv nutzen können.

Das Architekturmuster beim Start ist wie folgt: Wir lesen direkt von Kafka, schreiben aber nur über den Event-Bus. In Kafka gibt es vieles Fertige zum Lesen: Broker, Balancer, und es ist mehr oder weniger bereit für die horizontale Skalierung, das wollte ich beibehalten. Wir wollten die Aufzeichnung über ein Gateway, auch bekannt als Events-Bus, abschließen, und hier ist der Grund.

Eventbus

Oder ein Eventbus. Es handelt sich lediglich um ein zustandsloses HTTP-Gateway, das mehrere wichtige Rollen übernimmt:

  • Validierung erstellen — wir prüfen, ob die Ereignisse unseren Spezifikationen entsprechen.
  • Event-Master-System, das heißt, dies ist das zentrale und einzige System im Unternehmen, das die Frage beantwortet, welche Ereignisse mit welchen Strukturen als gültig angesehen werden. Die Validierung umfasst lediglich Datentypen und Enumerationen zur starren Spezifikation des Inhalts.
  • Hash-Funktion für Sharding – die Struktur einer Kafka-Nachricht ist Schlüssel-Wert, und der Hash des Schlüssels wird verwendet, um zu berechnen, wo sie abgelegt werden soll.

Warum

Wir arbeiten in einem großen Unternehmen mit einem eingespielten Prozess. Warum etwas ändern? Dies ist ein Experiment., und wir erwarten mehrere Vorteile.

1:n+1-Austausch (eins zu viele)

Mit Kafka ist es sehr einfach, neue Verbraucher mit Ihrer API zu verbinden.

Angenommen, Sie haben ein Verzeichnis, das in mehreren Systemen gleichzeitig (und in einigen neuen) auf dem neuesten Stand gehalten werden muss. Zuvor haben wir ein Bundle erfunden, das die Set-API implementierte, und dem Mastersystem wurden die Adressen der Verbraucher zugewiesen. Nun sendet das Mastersystem Updates zum Thema und jeder Interessierte liest sie. Ein neues System ist erschienen – das Thema abonniert. Ja, auch ein Bündel, aber einfacher.

Im Fall des Rückerstattungstools, das im Wesentlichen ein Teil von BOB ist, ist es für uns praktisch, sie über Kafka synchron zu halten. In der Zahlung heißt es, dass das Geld zurückgegeben wurde: BOB und RT haben davon erfahren und ihren Status geändert, der Fiskaldienst hat davon erfahren und einen Scheck ausgestellt.

Erfahrung in der Entwicklung des Refund Tool-Dienstes mit einer asynchronen API auf Kafka

Wir planen, einen einzigen Benachrichtigungsdienst zu erstellen, der den Kunden über Neuigkeiten zu seiner Bestellung/Rücksendung informiert. Derzeit ist diese Verantwortung auf mehrere Systeme verteilt. Es reicht aus, wenn wir dem Benachrichtigungsdienst beibringen, relevante Informationen von Kafka abzufangen und darauf zu reagieren (und diese Benachrichtigungen in anderen Systemen zu deaktivieren). Es sind keine neuen Direktbörsen erforderlich.

Datensteuerung

Informationen zwischen Systemen werden transparent – ​​egal, wie „blutiges Unternehmen“ Sie haben und egal, wie groß Ihr Rückstand ist. Lamoda verfügt über eine Abteilung für Datenanalyse, die Daten aus Systemen sammelt und in eine wiederverwendbare Form für geschäftliche und intelligente Systeme umwandelt. Mit Kafka können Sie ihnen schnell viele Daten bereitstellen und den Informationsfluss auf dem neuesten Stand halten.

Replikationsprotokoll

Nachrichten verschwinden nicht, nachdem sie gelesen wurden, wie in RabbitMQ. Wenn ein Ereignis genügend Informationen zur Verarbeitung enthält, verfügen wir über einen Verlauf der letzten Änderungen am Objekt und können diese Änderungen bei Bedarf anwenden.

Die Speicherdauer des Replikationsprotokolls hängt von der Schreibintensität zu diesem Thema ab; Mit Kafka können Sie Speicherdauer und Datenvolumen flexibel begrenzen. Bei intensiven Themen ist es wichtig, dass alle Verbraucher auch bei kurzfristiger Funktionsunfähigkeit Zeit haben, die Informationen zu lesen, bevor diese verschwinden. Die Datenspeicherung ist in der Regel möglich für Einheiten von Tagen, was zur Unterstützung völlig ausreicht.

Erfahrung in der Entwicklung des Refund Tool-Dienstes mit einer asynchronen API auf Kafka

Hier ist eine kleine Nacherzählung der Dokumentation für diejenigen, die mit Kafka nicht vertraut sind (das Bild stammt auch aus der Dokumentation).

AMQP hat Warteschlangen: Wir schreiben Nachrichten für den Verbraucher in eine Warteschlange. Normalerweise wird eine Warteschlange von einem System mit derselben Geschäftslogik verarbeitet. Wenn Sie mehrere Systeme benachrichtigen müssen, können Sie der Anwendung beibringen, in mehrere Warteschlangen zu schreiben oder einen Austausch mit einem Fanout-Mechanismus einzurichten, der sie selbst klont.

Kafka hat eine ähnliche Abstraktion Thema, in dem Sie Nachrichten schreiben, die aber nach dem Lesen nicht verschwinden. Wenn Sie eine Verbindung mit Kafka herstellen, erhalten Sie standardmäßig alle Nachrichten und haben die Möglichkeit, dort zu speichern, wo Sie aufgehört haben. Das heißt, Sie lesen der Reihe nach, können die Nachricht also nicht als gelesen markieren, sondern die ID speichern, ab der Sie dann weiterlesen. Die ID, bei der Sie angehalten haben, wird als Offset bezeichnet, und der Mechanismus ist der Commit-Offset.

Dementsprechend können unterschiedliche Logiken implementiert werden. Beispielsweise haben wir BOB in 4 Instanzen für verschiedene Länder – Lamoda gibt es in Russland, Kasachstan, der Ukraine und Weißrussland. Da sie separat bereitgestellt werden, haben sie leicht unterschiedliche Konfigurationen und ihre eigene Geschäftslogik. Wir geben in der Nachricht an, auf welches Land sie sich bezieht. Jeder BOB-Verbraucher in jedem Land liest mit einer anderen Gruppen-ID, und wenn die Nachricht nicht auf ihn zutrifft, wird sie übersprungen, d. h. es wird sofort ein Offset +1 festgeschrieben. Wenn dasselbe Thema von unserem Zahlungsdienst gelesen wird, geschieht dies mit einer separaten Gruppe, und daher überschneiden sich die Offsets nicht.

Veranstaltungsanforderungen:

  • Vollständigkeit der Daten. Ich möchte, dass für die Veranstaltung genügend Daten zur Verarbeitung vorliegen.

  • Integrität. Wir delegieren die Überprüfung, ob das Ereignis konsistent ist und verarbeitet werden kann, an Events-bus.
  • Ordnung ist wichtig. Im Falle einer Rückkehr sind wir gezwungen, mit der Geschichte zu arbeiten. Bei Benachrichtigungen ist die Reihenfolge nicht wichtig, wenn es sich um homogene Benachrichtigungen handelt, ist die E-Mail immer gleich, unabhängig davon, welche Bestellung zuerst eintrifft. Im Falle einer Rücksendung gibt es einen klaren Ablauf. Wenn Sie die Bestellung ändern, treten Ausnahmen auf, die Rückerstattung wird nicht erstellt oder bearbeitet – wir landen in einem anderen Status.
  • Konsistenz. Wir haben einen Store und erstellen jetzt anstelle einer API Ereignisse. Wir benötigen eine Möglichkeit, Informationen über neue Veranstaltungen und Änderungen an bestehenden Veranstaltungen schnell und kostengünstig an unsere Dienste zu übermitteln. Dies wird durch eine gemeinsame Spezifikation in einem separaten Git-Repository und Codegeneratoren erreicht. Dabei werden Clients und Server in den unterschiedlichen Diensten mit uns abgestimmt.

Kafka in Lamoda

Wir haben drei Kafka-Installationen:

  1. Protokolle;
  2. F&E;
  3. Veranstaltungsbus.

Heute sprechen wir nur über den letzten Punkt. Im Events-Bus haben wir keine sehr großen Installationen – 3 Broker (Server) und nur 27 Themen. Ein Thema ist in der Regel ein Prozess. Dies ist jedoch ein subtiler Punkt, und wir werden jetzt darauf eingehen.

Erfahrung in der Entwicklung des Refund Tool-Dienstes mit einer asynchronen API auf Kafka

Oben ist das RPS-Diagramm. Der Rückerstattungsprozess ist mit einer türkisfarbenen Linie markiert (ja, die auf der X-Achse) und der Inhaltsaktualisierungsprozess ist rosa markiert.

Der Lamoda-Katalog enthält Millionen von Produkten und die Daten werden ständig aktualisiert. Manche Kollektionen kommen aus der Mode, werden durch neue ersetzt und ständig erscheinen neue Modelle im Katalog. Wir versuchen vorherzusehen, was unsere Kunden morgen interessieren wird, deshalb kaufen wir ständig neue Dinge ein, fotografieren sie und aktualisieren die Vitrine.

Rosafarbene Spitzen stellen Produktaktualisierungen dar, also Änderungen an Produkten. Es ist klar, dass die Jungs Fotos und Fotos gemacht haben, und dann, bumm! – eine Reihe von Ereignissen geladen.

Anwendungsfälle für Lamoda Events

Wir verwenden die erstellte Architektur für folgende Operationen:

  • Rücksendestatus verfolgen: Call-to-Action und Statusverfolgung aus allen beteiligten Systemen. Zahlung, Status, Fiskalisierung, Benachrichtigungen. Hier haben wir den Ansatz getestet, Tools erstellt, alle Fehler gesammelt, Dokumentationen geschrieben und Kollegen erklärt, wie sie ihn verwenden sollen.
  • Aktualisieren von Produktkarten: Konfiguration, Metadaten, Eigenschaften. Ein System liest (das, das anzeigt), aber mehrere schreiben.
  • E-Mail, Push und SMS: Die Bestellung wurde zusammengestellt, die Bestellung ist angekommen, die Rücksendung wurde akzeptiert usw., davon gibt es viele.
  • Lagerbestand, Lageraktualisierung — quantitative Aktualisierung der Namen, nur Zahlen: Eingang im Lager, Rückgabe. Es ist notwendig, dass alle Systeme im Zusammenhang mit der Produktreservierung mit den aktuellsten Daten arbeiten. Derzeit ist das Bestandsaktualisierungssystem recht komplex, Kafka wird es vereinfachen.
  • Datenanalyse (F&E-Abteilung), ML-Tools, Analytik, Statistik. Wir möchten, dass Informationen transparent sind, und Kafka eignet sich hierfür gut.

Der interessantere Teil betrifft nun die Überraschungen und interessanten Entdeckungen der letzten sechs Monate.

Designprobleme

Nehmen wir an, wir möchten etwas Neues tun – zum Beispiel den gesamten Lieferprozess auf Kafka verlagern. Ein Teil des Prozesses ist derzeit in der Auftragsabwicklung in BOB implementiert. Hinter der Übergabe einer Bestellung an den Lieferdienst, ihrer Bewegung in ein Zwischenlager usw. steht ein Statusmodell. Es gibt einen ganzen Monolithen, sogar zwei, plus eine Reihe von APIs, die speziell für die Bereitstellung bestimmt sind. Sie wissen viel mehr über den Versand.

Dies scheinen ähnliche Bereiche zu sein, aber die Status sind für die Auftragsabwicklung in BOB und für das Versandsystem unterschiedlich. Manche Kurierdienste übermitteln beispielsweise keine Zwischenstatusmeldungen, sondern nur die endgültigen: „zugestellt“ oder „verloren“. Andere wiederum berichten sehr detailliert über den Warenverkehr. Jeder hat seine eigenen Validierungsregeln: Für manche ist die E-Mail gültig, was bedeutet, dass sie verarbeitet wird; für andere ist es nicht gültig, aber die Bestellung wird trotzdem bearbeitet, weil eine Telefonnummer zum Kontaktieren vorhanden ist, und jemand wird sagen, dass eine solche Bestellung überhaupt nicht bearbeitet wird.

Datenstrom

Im Fall von Kafka stellt sich die Frage nach der Organisation des Datenflusses. Diese Aufgabe hängt in mehreren Punkten mit der Wahl der Strategie zusammen. Lassen Sie uns sie alle durchgehen.

In einem Thema oder in verschiedenen?

Wir haben eine Veranstaltungsspezifikation. In BOB schreiben wir, dass diese oder jene Bestellung geliefert werden muss, und geben an: die Bestellnummer, ihre Zusammensetzung, einige SKU- und Barcodes usw. Wenn die Waren im Lager ankommen, kann die Lieferung Status, Zeitstempel und alles andere Notwendige erhalten. Aber dann möchten wir Updates zu diesen Daten in BOB erhalten. Wir haben einen umgekehrten Prozess zum Abrufen von Daten aus der Lieferung. Handelt es sich hierbei um dasselbe Ereignis? Oder handelt es sich hierbei um einen separaten Austausch, der ein eigenes Thema verdient?

Höchstwahrscheinlich werden sie sehr ähnlich sein, und die Versuchung, ein einziges Thema zu erstellen, ist nicht unbegründet, da ein separates Thema separate Verbraucher, separate Konfigurationen und eine separate Generierung all dessen bedeutet. Aber das ist keine Tatsache.

Neues Fachgebiet oder neue Veranstaltung?

Wenn Sie jedoch dieselben Ereignisse verwenden, tritt ein anderes Problem auf. Beispielsweise können nicht alle Liefersysteme ein DTO generieren, das BOB generieren kann. Wir senden ihnen IDs, aber sie speichern sie nicht, weil sie sie nicht benötigen, und aus Sicht des Startens des Event-Bus-Prozesses ist dieses Feld obligatorisch.

Wenn wir für den Event-Bus eine Regel einführen, dass dieses Feld obligatorisch ist, sind wir gezwungen, zusätzliche Validierungsregeln in BOB oder im Startereignishandler festzulegen. Die Validierung breitet sich zunehmend über den gesamten Dienst aus – das ist nicht sehr praktisch.

Ein weiteres Problem ist die Versuchung einer inkrementellen Entwicklung. Uns wird gesagt, dass dem Ereignis etwas hinzugefügt werden muss und dass es, wenn wir darüber nachdenken, vielleicht ein separates Ereignis hätte sein sollen. Aber in unserem Schema ist ein separates Ereignis ein separates Thema. Ein separates Thema ist der gesamte Prozess, den ich oben beschrieben habe. Der Entwickler ist versucht, dem JSON-Schema einfach ein weiteres Feld hinzuzufügen und es neu zu generieren.

Bei Rückerstattungen sind wir im Falle von Ereignissen in sechs Monaten angekommen. Wir hatten ein Metaereignis namens „Rückerstattungsaktualisierung“, das über ein Typfeld verfügte, das beschrieb, was diese Aktualisierung eigentlich war. Aus diesem Grund hatten wir „wunderbare“ Schalter mit Validierern, die sagten, wie dieses Ereignis mit diesem Typ validiert werden soll.

Ereignisversionierung

Um Nachrichten in Kafka zu validieren, können Sie verwenden Avro, aber es war notwendig, dies sofort einzuplanen und Confluent zu verwenden. In unserem Fall müssen wir bei der Versionierung vorsichtig sein. Es ist nicht immer möglich, Nachrichten aus dem Replikationsprotokoll erneut zu lesen, da das Modell „verlassen“ wurde. Grundsätzlich geht es darum, Versionen zu erstellen, sodass das Modell abwärtskompatibel ist: zum Beispiel, indem ein Feld vorübergehend optional gemacht wird. Wenn die Unterschiede zu groß sind, beginnen wir mit dem Schreiben eines neuen Themas und übertragen die Kunden, wenn sie mit dem Lesen des alten Themas fertig sind.

Garantie der Lesereihenfolge von Partitionen

Themen innerhalb von Kafka sind in Partitionen unterteilt. Dies spielt keine wirkliche Rolle, wenn wir Entitäten und Börsen entwerfen, aber es spielt eine Rolle, wenn wir entscheiden, wie wir sie nutzen und skalieren.

Im Normalfall schreiben Sie ein Thema an Kafka. Standardmäßig wird eine Partition verwendet, in die alle Nachrichten dieses Themas eingehen. Und der Verbraucher liest diese Nachrichten dementsprechend sequenziell. Nehmen wir jetzt an, wir müssen das System so erweitern, dass Nachrichten von zwei verschiedenen Verbrauchern gelesen werden. Wenn Sie beispielsweise eine SMS senden, können Sie Kafka anweisen, eine zusätzliche Partition zu erstellen. Kafka beginnt dann, die Nachrichten in zwei Teile aufzuteilen – die Hälfte dort und die Hälfte hier.

Wie teilt Kafka sie auf? Jede Nachricht hat einen Textkörper (in dem wir JSON speichern) und einen Schlüssel. Auf diesen Schlüssel kann eine Hash-Funktion angewendet werden, die bestimmt, in welcher Partition die Nachricht landet.

In unserem Fall mit Rückerstattungen ist dies wichtig. Wenn wir zwei Partitionen nehmen, besteht die Möglichkeit, dass der parallele Verbraucher das zweite Ereignis vor dem ersten verarbeitet und es zu Problemen kommt. Die Hash-Funktion stellt sicher, dass Nachrichten mit demselben Schlüssel in derselben Partition landen.

Ereignisse vs. Befehle

Dies ist ein weiteres Problem, mit dem wir konfrontiert waren. Ein Ereignis ist ein bestimmtes Vorkommnis: Wir sagen, dass irgendwo etwas passiert ist (something_happened), zum Beispiel, dass ein Artikel storniert wurde oder eine Rückerstattung erfolgte. Wenn jemand diese Ereignisse abhört, wird für „Artikel storniert“ die Rückerstattungsentität erstellt und „Rückerstattung erfolgt“ irgendwo in den Einstellungen aufgezeichnet.

Aber wenn Sie Veranstaltungen konzipieren, möchten Sie diese normalerweise nicht umsonst schreiben – Sie verlassen sich darauf, dass sie jemand liest. Die Versuchung ist groß, nicht „etwas ist passiert“ (Artikel storniert, Rückerstattung erstattet) zu schreiben, sondern „etwas sollte getan werden“. Beispiel: Der Artikel ist zur Rückgabe bereit.

Dies lässt einerseits darauf schließen, wie die Veranstaltung genutzt werden soll. Andererseits sieht es viel weniger wie ein normaler Veranstaltungstitel aus. Außerdem ist es von hier nicht weit zum do_something-Befehl. Aber Sie haben keine Garantie, dass jemand dieses Ereignis gelesen hat; und wenn Sie es lesen, dann haben Sie es erfolgreich gelesen; und wenn Sie es erfolgreich gelesen haben, dann haben Sie etwas getan, und dieses Etwas war erfolgreich. Sobald aus einem Ereignis etwas wird, das man tun möchte, wird Feedback notwendig, und das ist ein Problem.

Erfahrung in der Entwicklung des Refund Tool-Dienstes mit einer asynchronen API auf Kafka

Wenn Sie bei der asynchronen Kommunikation in RabbitMQ eine Nachricht lesen und zu http gehen, erhalten Sie eine Antwort – zumindest, dass die Nachricht empfangen wurde. Wenn Sie an Kafka schreiben, gibt es eine Nachricht, die Sie an Kafka geschrieben haben, aber Sie wissen nichts darüber, wie sie verarbeitet wurde.

Daher mussten wir in unserem Fall ein Antwortereignis einführen und die Überwachung so konfigurieren, dass, wenn so viele Ereignisse ausgelöst wurden, nach einer bestimmten Zeit die gleiche Anzahl von Antwortereignissen eintreffen sollte. Wenn dies nicht geschieht, scheint etwas schiefgelaufen zu sein. Wenn wir beispielsweise das Ereignis „item_ready_to_refund“ senden, erwarten wir, dass die Rückerstattung erstellt wird, der Kunde sein Geld zurückerhält und wir das Ereignis „money_refunded“ erhalten. Dies ist jedoch nicht sicher, daher ist eine Überwachung erforderlich.

Nuancen

Es gibt ein ziemlich offensichtliches Problem: Wenn Sie ein Thema ständig wiederholen und dabei eine schlechte Botschaft vermitteln, verliert der Verbraucher das Interesse und Sie kommen nicht weiter. Sie benötigen Stoppen Sie alle Verbraucher, Commit-Offset als Nächstes, um weiterzulesen.

Wir wussten davon, wir haben damit gerechnet und es ist trotzdem passiert. Und dies geschah, weil das Ereignis aus der Sicht des Ereignisbusses gültig war, das Ereignis aus der Sicht des Anwendungsvalidators gültig war, aber aus der Sicht von PostgreSQL nicht gültig war, weil wir in einem System MySQL mit UNSIGNED INT hatten und im neu geschriebenen System PostgreSQL einfach mit INT war. Es ist etwas kleiner und der Ausweis passte nicht. Symfony ist mit einer Ausnahme gestorben. Wir haben die Ausnahme natürlich abgefangen, weil wir uns darauf verlassen haben, und wollten diesen Offset festschreiben, aber vorher wollten wir den Problemzähler erhöhen, da die Nachricht nicht erfolgreich verarbeitet wurde. Die Zähler in diesem Projekt befinden sich auch in der Datenbank, und Symfony hat die Kommunikation mit der Datenbank bereits geschlossen, und die zweite Ausnahme hat den gesamten Prozess beendet, ohne dass die Möglichkeit bestand, den Offset festzuschreiben.

Der Dienst lag eine Zeit lang brach – bei Kafka ist das zum Glück nicht so schlimm, denn die Nachrichten bleiben erhalten. Wenn die Arbeit wiederhergestellt ist, können sie mit dem Lesen fertig sein. Es ist praktisch.

Kafka bietet die Möglichkeit, über Tools einen beliebigen Offset festzulegen. Dazu müssen Sie jedoch alle Verbraucher stoppen. In unserem Fall müssen Sie eine separate Version vorbereiten, in der es keine Verbraucher und keine erneuten Bereitstellungen gibt. Anschließend können Sie den Offset in Kafka über Tools verschieben und die Nachricht wird weitergeleitet.

Eine weitere Nuance ist Replikationsprotokoll vs. rdkafka.so — hängt mit den Besonderheiten unseres Projekts zusammen. Wir haben PHP, und in PHP kommunizieren in der Regel alle Bibliotheken mit Kafka über das Repository rdkafka.so, und dann gibt es eine Art Wrapper. Vielleicht liegt es an unseren persönlichen Schwierigkeiten, aber es stellt sich heraus, dass es gar nicht so einfach ist, einen Teil von etwas, das man bereits gelesen hat, einfach noch einmal zu lesen. Generell gab es Softwareprobleme.

Zurück zu den Besonderheiten der Arbeit mit Partitionen, es steht direkt in der Dokumentation Verbraucher >= Themenpartitionen. Davon habe ich allerdings erst viel später erfahren, als mir lieb war. Wenn Sie skalieren möchten und zwei Verbraucher haben, benötigen Sie mindestens zwei Partitionen. Das heißt, wenn Sie eine Partition hatten, in der sich 20 Nachrichten angesammelt hatten, und Sie eine neue erstellen, wird sich die Anzahl der Nachrichten nicht gleich schnell ausgleichen. Um zwei parallele Verbraucher zu haben, müssen Sie daher mit Partitionen arbeiten.

Überwachung

Ich denke, dass die Art und Weise unseres Monitorings noch deutlicher zeigen wird, welche Probleme der bestehende Ansatz birgt.

Beispielsweise berechnen wir, wie viele Produkte in der Datenbank kürzlich ihren Status geändert haben und welche Ereignisse aufgrund dieser Änderungen aufgetreten sein sollten, und senden diese Zahl an unser Überwachungssystem. Dann erhalten wir von Kafka die zweite Zahl, wie viele Ereignisse tatsächlich aufgezeichnet wurden. Offensichtlich muss die Differenz zwischen diesen beiden Zahlen immer Null sein.

Erfahrung in der Entwicklung des Refund Tool-Dienstes mit einer asynchronen API auf Kafka

Darüber hinaus muss überwacht werden, wie es dem Produzenten geht, ob der Ereignisbus Nachrichten empfangen hat und wie es dem Konsumenten geht. In den folgenden Diagrammen beispielsweise schneidet Refund Tool gut ab, BOB hat jedoch eindeutig einige Probleme (blaue Spitzen).

Erfahrung in der Entwicklung des Refund Tool-Dienstes mit einer asynchronen API auf Kafka

Ich habe die Verzögerung bei der Verbrauchergruppe bereits erwähnt. Grob gesagt handelt es sich hierbei um die Anzahl der ungelesenen Nachrichten. Im Allgemeinen arbeiten unsere Verbraucher schnell, sodass die Verzögerung normalerweise 0 beträgt, manchmal kann es jedoch zu einer kurzfristigen Spitze kommen. Kafka kann dies sofort, Sie müssen jedoch ein bestimmtes Intervall festlegen.

Es gibt ein Projekt Bau, wo Sie weitere Informationen zu Kafka erhalten. Es sendet einfach den Status zur Leistung dieser Gruppe über die Consumer-Group-API. Zusätzlich zu „OK“ und „Fehlgeschlagen“ gibt es eine Warnung, und Sie werden feststellen, dass Ihre Verbraucher mit dem Produktionstempo nicht zurechtkommen – sie haben keine Zeit, das Geschriebene Korrektur zu lesen. Das System ist ziemlich intelligent und einfach zu bedienen.

Erfahrung in der Entwicklung des Refund Tool-Dienstes mit einer asynchronen API auf Kafka

So sieht die API-Antwort aus. Hier ist die Gruppe bob-live-fifa, Partition refund.update.v1, Status OK, Lag 0 – der letzte endgültige Offset ist soundso viel.

Erfahrung in der Entwicklung des Refund Tool-Dienstes mit einer asynchronen API auf Kafka

Überwachung updated_at SLA (steckengeblieben) Habe ich bereits erwähnt. Beispielsweise hat das Produkt seinen Status auf „Rückgabebereit“ geändert. Wir haben einen Cron eingerichtet, der besagt, dass, wenn dieses Objekt nicht innerhalb von 5 Minuten zur Rückerstattung verschoben wurde (wir erstatten Geld sehr schnell über Zahlungssysteme), dann ist definitiv etwas schiefgelaufen und dies ist definitiv ein Fall für den Support. Also nehmen wir einfach Cron, das solche Dinge liest und wenn sie größer als 0 sind, sendet es eine Warnung.

Zusammenfassend lässt sich sagen, dass Ereignisse nützlich sind, wenn:

  • Informationen werden von mehreren Systemen benötigt;
  • das Ergebnis der Verarbeitung ist nicht wichtig;
  • es gibt wenige Veranstaltungen oder die Veranstaltungen sind klein.

Es scheint, dass der Artikel ein sehr spezifisches Thema behandelt – asynchrone API auf Kafka, aber in diesem Zusammenhang möchte ich viele Dinge gleichzeitig empfehlen.
Zunächst einmal der nächste HighLoad ++ wir müssen nicht bis November warten, im April wird es eine St. Petersburger Version geben und im Juni werden wir über hohe Belastungen in Nowosibirsk sprechen.
Zweitens ist der Autor des Berichts, Sergey Zaika, Mitglied des Programmkomitees unserer neuen Konferenz zum Wissensmanagement WissenConf. Die Konferenz findet am 26. April statt, hat aber ein sehr umfangreiches Programm.
Und im Mai gibt es noch einen PHP Russland и RIT++ (mit DevOpsConf inklusive) – auch dort könnt ihr euer Thema vorschlagen, von euren Erfahrungen berichten und euch über eure Schrammen beschweren.

Source: habr.com

Kommentar hinzufügen