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

Was könnte ein so großes Unternehmen wie Lamoda mit einem optimierten Prozess und Dutzenden miteinander verbundenen Diensten dazu zwingen, seinen Ansatz erheblich zu ändern? Die Motivation kann völlig unterschiedlich sein: von der Gesetzgebung bis zur Experimentierfreude, die allen Programmierern innewohnt.

Dies bedeutet jedoch nicht, dass Sie nicht mit zusätzlichen Vorteilen rechnen können. Sergey Zaika wird Ihnen sagen, was genau Sie gewinnen können, wenn Sie die ereignisgesteuerte API auf Kafka implementieren (Wenigwald). Es wird auf jeden Fall auch von großen Tieren und interessanten Entdeckungen die Rede sein – ohne sie kommt das Experiment nicht aus.

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

Haftungsausschluss: Dieser Artikel basiert auf Materialien eines Treffens, das Sergey im November 2018 auf HighLoad++ abgehalten hat. Lamodas Live-Erlebnis der Zusammenarbeit mit Kafka zog die Zuhörer nicht weniger an als andere Berichte auf dem Programm. Wir finden, dass dies ein hervorragendes Beispiel dafür ist, dass man immer Gleichgesinnte finden kann und sollte, und die Organisatoren von HighLoad++ werden weiterhin versuchen, eine entsprechende Atmosphäre zu schaffen.

Über den Prozess

Lamoda ist eine große E-Commerce-Plattform, die über ein eigenes Contact Center, einen Lieferservice (und viele Partner), ein Fotostudio und ein riesiges Lager verfügt und alles auf einer eigenen Software läuft. Es gibt Dutzende Zahlungsmethoden, B2B-Partner, die möglicherweise einige oder alle dieser Dienste nutzen und aktuelle Informationen zu ihren Produkten erhalten möchten. Darüber hinaus ist Lamoda neben der Russischen Föderation in drei Ländern tätig und dort ist alles etwas anders. Insgesamt gibt es vermutlich mehr als hundert Möglichkeiten, eine neue Bestellung zu konfigurieren, die auf ihre eigene Weise abgewickelt werden muss. All dies funktioniert mit Hilfe Dutzender Dienste, die manchmal auf nicht offensichtliche Weise kommunizieren. Es gibt auch ein zentrales System, dessen Hauptaufgabe der Auftragsstatus ist. Wir nennen sie BOB, ich arbeite mit ihr.

Rückerstattungstool mit ereignisgesteuerter API

Das Wort ereignisgesteuert ist ziemlich abgedroschen; etwas weiter unten werden wir genauer definieren, was damit gemeint ist. Ich beginne mit dem Kontext, 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 gibt es neben Bestellungen, für die der Kunde bezahlt, auch Fälle, in denen das Geschäft Geld zurückgeben muss, weil das Produkt dem Kunden nicht gepasst hat. Dies ist ein relativ kurzer Prozess: Wir klären ggf. die 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 schreibt vor, dass jede Geldtransaktion, sei es eine Rückgabe oder eine Quittung, dem Finanzamt innerhalb einer relativ kurzen SLA von wenigen Minuten gemeldet werden muss. Als E-Commerce-Unternehmen führen wir eine ganze Reihe von Operationen durch. Technisch bedeutet dies eine neue Verantwortung (und damit einen neuen Service) und Verbesserungen in allen beteiligten Systemen.
  2. BOB-Split ist ein internes Projekt des Unternehmens, um BOB von einer Vielzahl nicht zum Kerngeschäft gehörender Aufgaben zu entlasten und seine Gesamtkomplexität zu reduzieren.

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

Dieses Diagramm zeigt die wichtigsten Lamoda-Systeme. Mittlerweile sind es bei den meisten mehr eine Konstellation von 5–10 Mikrodiensten um einen schrumpfenden Monolithen. Sie wachsen langsam, aber wir versuchen, sie kleiner zu machen, denn der Einsatz des in der Mitte ausgewählten Fragments ist beängstigend – wir können nicht zulassen, dass es herunterfällt. Wir sind gezwungen, alle Börsen (Pfeile) zu reservieren und berücksichtigen die Tatsache, dass sich herausstellen kann, dass eine davon nicht verfügbar ist.

BOB verfügt auch über zahlreiche Börsen: Zahlungssysteme, Liefersysteme, Benachrichtigungssysteme usw.

Technisch gesehen ist BOB:

  • ~150 Codezeilen + ~100 Testzeilen;
  • php7.2 + Zend 1 & Symfony Components 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 dadurch gelösten Probleme sind so groß, dass sich niemand alles in den Kopf setzen kann. Generell gibt es viele Gründe, es zu vereinfachen.

Rückgabeprozess

An dem Prozess sind zunächst zwei Systeme beteiligt: ​​BOB und Payment. Nun erscheinen zwei weitere:

  • Fiskalisierungsdienst, der sich um Probleme bei der Fiskalisierung und der Kommunikation mit externen Diensten kümmert.
  • Rückerstattungstool, das lediglich neue Umtausche enthält, um den BOB nicht aufzublähen.

Nun sieht der Vorgang so aus:

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

  1. BOB erhält einen Antrag auf Rückerstattung.
  2. BOB spricht über dieses Rückerstattungstool.
  3. Das Rückerstattungstool teilt der Zahlung mit: „Geben Sie das Geld zurück.“
  4. Die Zahlung gibt das Geld zurück.
  5. Refund Tool und BOB synchronisieren den Status miteinander, da beide dies vorerst benötigen. Wir sind noch nicht bereit, vollständig auf das Rückerstattungstool umzusteigen, da BOB über eine Benutzeroberfläche, Berichte für die Buchhaltung und generell viele Daten verfügt, die nicht so einfach übertragen werden können. Man muss auf zwei Stühlen sitzen.
  6. Der Antrag auf Fiskalisierung entfällt.

Als Ergebnis haben wir eine Art Event-Bus auf Kafka gemacht – Event-Bus, mit dem alles begann. Hurra, jetzt haben wir einen einzigen Fehlerpunkt (Sarkasmus).

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

Die Vor- und Nachteile liegen ziemlich 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 einem Single Point of Failure im System. Kafka wird abstürzen, der Prozess wird gestoppt.

Was ist eine ereignisgesteuerte API?

Eine gute Antwort auf diese Frage finden Sie im Bericht von Martin Fowler (GOTO 2017). „Die vielen Bedeutungen ereignisgesteuerter Architektur“.

Kurz gesagt, was wir gemacht haben:

  1. Schließen Sie alle asynchronen Austausche über ab Speicherung von Ereignissen. Anstatt jeden interessierten Verbraucher über eine Statusänderung über das Netzwerk zu informieren, schreiben wir eine Veranstaltung über eine Statusänderung in einen zentralen Speicher, und an dem Thema interessierte 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 an einigen Daten zur Statusänderung interessiert ist, die nicht in der Benachrichtigung enthalten sind, kann sich selbst über deren Status informieren.
  3. Die maximale Option ist ein vollwertiges Event-Sourcing, staatliche Übertragung, wobei 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 der Informationen, die Sie sich leisten können, um sie zu speichern.

Im Rahmen der Einführung des Rückerstattungstools haben wir die dritte Option genutzt. Dadurch wurde die Ereignisverarbeitung vereinfacht, da keine detaillierten Informationen extrahiert werden mussten. Darüber hinaus wurde das Szenario beseitigt, in dem jedes neue Ereignis eine Flut klärender Get-Anfragen von Verbrauchern generiert.

Rückerstattungs-Tool-Service nicht geladenFür Kafka ist es eher ein Geschmack der Feder als eine Notwendigkeit. Ich glaube nicht, dass das Unternehmen zufrieden wäre, wenn der Rückerstattungsservice zu einem Hochlastprojekt würde.

Asynchroner Austausch wie besehen

Für den asynchronen Austausch verwendet die PHP-Abteilung normalerweise RabbitMQ. Wir haben die Daten für die Anfrage gesammelt, sie in eine Warteschlange gestellt und der Verbraucher desselben Dienstes hat sie gelesen und gesendet (oder nicht gesendet). Für die API selbst nutzt Lamoda aktiv Swagger. Wir entwerfen eine API, beschreiben sie in Swagger und generieren Client- und Servercode. Wir verwenden auch einen leicht erweiterten JSON RPC 2.0.

An manchen Orten werden ESB-Busse verwendet, einige leben auf activeMQ, aber im Allgemeinen RabbitMQ – Standard.

Asynchroner Austausch TO BE

Bei der Gestaltung des Austauschs über den Ereignisbus lässt sich eine Analogie verfolgen. In ähnlicher Weise beschreiben wir den zukünftigen Datenaustausch durch Beschreibungen der Ereignisstruktur. Beim Yaml-Format mussten wir die Codegenerierung selbst durchführen, der Generator erstellt DTOs gemäß der Spezifikation und bringt Clients und Servern bei, damit zu arbeiten. Die Generation geht in zwei Sprachen – Golang und PHP. Dies trägt dazu bei, die Bibliotheken konsistent zu halten. Der Generator ist in Golang geschrieben, weshalb er den Namen Gogi erhielt.

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

Es ist ironisch, dass wir selbst in einem so glücklichen Fall, wenn es ein ungefähr ähnliches Unternehmen, Zalando, gibt, das eine ungefähr ähnliche Lösung entwickelt hat, diese nicht effektiv nutzen können.

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

Veranstaltungsbus

Oder ein Eventbus. Dabei handelt es sich einfach um ein zustandsloses HTTP-Gateway, das mehrere wichtige Rollen übernimmt:

  • Validierung erstellen — Wir prüfen, ob die Veranstaltungen unseren Vorgaben 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 gelten. Die Validierung umfasst lediglich Datentypen und Aufzählungen, um den Inhalt genau zu spezifizieren.
  • Hash-Funktion für Sharding – die Kafka-Nachrichtenstruktur ist ein Schlüsselwert und anhand des Hashs des Schlüssels wird berechnet, wo er abgelegt werden soll.

Warum

Wir arbeiten in einem großen Unternehmen mit einem optimierten Prozess. Warum etwas ändern? Das ist ein Experiment, und wir gehen davon aus, dass wir mehrere Vorteile daraus ziehen werden.

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

Kafka macht es sehr einfach, neue Verbraucher an die API anzubinden.

Nehmen wir an, Sie haben ein Verzeichnis, das Sie in mehreren Systemen gleichzeitig (und in einigen neuen) auf dem neuesten Stand halten müssen. Zuvor haben wir ein Bundle erfunden, das die Set-API implementierte und das Mastersystem über Verbraucheradressen informierte. Jetzt sendet das Mastersystem Aktualisierungen zum Thema und alle Interessierten lesen es. Ein neues System ist aufgetaucht – wir haben es für das Thema angemeldet. Ja, auch Bundle, aber einfacher.

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

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

Wir planen die Schaffung eines einheitlichen Benachrichtigungsdienstes, der den Kunden über Neuigkeiten zu seiner Bestellung/Rücksendung informiert. Jetzt ist diese Verantwortung zwischen den Systemen verteilt. Es wird ausreichen, 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 direkten Umtausche erforderlich.

Datengesteuert

Informationen zwischen Systemen werden transparent – ​​egal, was für ein „verdammtes Unternehmen“ Sie haben und wie dick Ihr Auftragsbestand ist. Lamoda verfügt über eine Abteilung für Datenanalyse, die Daten aus Systemen sammelt und sie in eine wiederverwendbare Form bringt, sowohl für Unternehmen als auch für intelligente Systeme. Mit Kafka können Sie ihnen schnell viele Daten zur Verfügung stellen 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 für die Verarbeitung enthält, verfügen wir über einen Verlauf der letzten Änderungen am Objekt und, falls gewünscht, über die Möglichkeit, diese Änderungen anzuwenden.

Die Speicherdauer des Replikationsprotokolls hängt von der Intensität des Schreibens zu diesem Thema ab; Kafka ermöglicht es Ihnen, Speicherzeit und Datenvolumen flexibel zu begrenzen. Bei intensiven Themen ist es wichtig, dass alle Verbraucher Zeit haben, die Informationen zu lesen, bevor sie verschwinden, auch bei kurzfristiger Funktionsunfähigkeit. Es ist normalerweise möglich, Daten für zu speichern Einheiten von Tagen, was zur Unterstützung völlig ausreicht.

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

Als nächstes eine kleine Nacherzählung der Dokumentation für diejenigen, die mit Kafka nicht vertraut sind (das Bild stammt ebenfalls aus der Dokumentation)

AMQP hat Warteschlangen: Wir schreiben Nachrichten in eine Warteschlange für den Verbraucher. 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 den Austausch mit dem Fanout-Mechanismus konfigurieren, der sie selbst klont.

Kafka hat eine ähnliche Abstraktion Thema, in dem Sie Nachrichten schreiben, diese jedoch nach dem Lesen nicht verschwinden. Wenn Sie eine Verbindung zu 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, Sie markieren die Nachricht möglicherweise nicht als gelesen, sondern speichern die ID, von der aus Sie dann weiterlesen können. Die von Ihnen festgelegte ID wird Offset genannt, und der Mechanismus ist Commit-Offset.

Dementsprechend kann eine unterschiedliche Logik implementiert werden. Zum Beispiel haben wir BOB in 4 Fällen 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, um welches Land es sich handelt. Jeder BOB-Verbraucher in jedem Land liest mit einer anderen Gruppen-ID, und wenn die Nachricht nicht auf ihn zutrifft, überspringt er sie, d. h. Commit sofort Offset +1. Wenn unser Zahlungsdienst dasselbe Thema liest, erfolgt dies mit einer separaten Gruppe, sodass sich die Offsets nicht überschneiden.

Veranstaltungsvoraussetzungen:

  • Datenvollständigkeit. Ich möchte, dass das Ereignis über genügend Daten verfügt, damit es verarbeitet werden kann.

  • Integrität Wir delegieren die Überprüfung, ob das Ereignis konsistent ist und verarbeitet werden kann, an Events-bus.
  • Die Reihenfolge 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 dieselbe, unabhängig davon, welche Bestellung zuerst eingegangen ist. Im Falle einer Rückerstattung gibt es einen klaren Ablauf; wenn wir die Bestellung ändern, kommt es zu Ausnahmen, die Rückerstattung wird nicht erstellt oder bearbeitet – wir landen in einem anderen Status.
  • Konsistenz. Wir haben einen Shop und erstellen jetzt Ereignisse anstelle einer API. Wir benötigen eine Möglichkeit, schnell und kostengünstig Informationen über neue Ereignisse und Änderungen bestehender Ereignisse an unsere Dienste zu übermitteln. Dies wird durch eine gemeinsame Spezifikation in einem separaten Git-Repository und Codegeneratoren erreicht. Daher werden Clients und Server in verschiedenen Diensten koordiniert.

Kafka in Lamoda

Wir haben drei Kafka-Installationen:

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

Heute reden wir nur über den letzten Punkt. Bei events-bus haben wir keine sehr großen Installationen – 3 Broker (Server) und nur 27 Themen. In der Regel ist ein Thema ein Prozess. Aber das ist ein subtiler Punkt, und wir werden ihn jetzt ansprechen.

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 die rosa Linie ist der Inhaltsaktualisierungsprozess.

Der Lamoda-Katalog enthält Millionen von Produkten und die Daten werden ständig aktualisiert. Einige Kollektionen kommen aus der Mode, neue werden als Ersatz herausgebracht und im Katalog erscheinen ständig neue Modelle. Wir versuchen vorherzusagen, was morgen für unsere Kunden interessant sein wird, deshalb kaufen wir ständig neue Dinge, fotografieren sie und aktualisieren die Vitrine.

Pink Peaks sind Produktaktualisierungen, also Änderungen an Produkten. Es ist zu sehen, dass die Jungs Fotos gemacht haben, Fotos gemacht haben und dann noch einmal! – hat ein Paket mit Ereignissen geladen.

Anwendungsfälle für Lamoda Events

Wir verwenden die konstruierte Architektur für die folgenden Operationen:

  • Rücksendestatusverfolgung: Call-to-Action und Statusverfolgung aus allen beteiligten Systemen. Zahlung, Status, Fiskalisierung, Benachrichtigungen. Hier haben wir den Ansatz getestet, Tools erstellt, alle Fehler gesammelt, Dokumentation geschrieben und unseren Kollegen erklärt, wie man ihn verwendet.
  • Produktkarten aktualisieren: Konfiguration, Metadaten, Eigenschaften. Ein System liest (was angezeigt wird) und mehrere schreiben.
  • E-Mail, Push und SMS: Die Bestellung wurde abgeholt, die Bestellung ist eingetroffen, die Rücksendung wurde angenommen usw., davon gibt es viele.
  • Lagerbestand, Lagererneuerung — quantitative Aktualisierung der Artikel, nur Zahlen: Ankunft im Lager, Rückgabe. Es ist notwendig, dass alle mit der Warenreservierung verbundenen Systeme mit den aktuellsten Daten arbeiten. Derzeit ist das Bestandsaktualisierungssystem recht komplex; Kafka wird es vereinfachen.
  • Datenanalyse (F&E-Abteilung), ML-Tools, Analysen, Statistiken. Wir wollen, dass Informationen transparent sind – dafür ist Kafka gut geeignet.

Nun der interessantere Teil über die großen Überraschungen und interessanten Entdeckungen, die in den letzten sechs Monaten gemacht wurden.

Designprobleme

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

Dies scheinen ähnliche Bereiche zu sein, aber die Auftragsabwicklung in BOB und das Versandsystem haben unterschiedliche Status. Einige Kurierdienste versenden beispielsweise keine Zwischenstatus, sondern nur die Endstatus: „zugestellt“ oder „verloren“. Andere hingegen berichten sehr detailliert über den Warenverkehr. Jeder hat seine eigenen Validierungsregeln: Für einige ist die E-Mail gültig, was bedeutet, dass sie verarbeitet wird; Für andere ist es ungültig, aber die Bestellung wird trotzdem bearbeitet, da es eine Telefonnummer zur Kontaktaufnahme gibt, 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. Bei dieser Aufgabe geht es darum, eine Strategie auszuwählen, die auf mehreren Punkten basiert. 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 SKUs und Barcodes usw. Wenn die Ware im Lager ankommt, kann die Lieferung Status, Zeitstempel und alles Notwendige erhalten. Aber dann möchten wir Updates zu diesen Daten in BOB erhalten. Wir haben einen umgekehrten Prozess des Empfangens von Daten von der Lieferung. Ist das das gleiche Ereignis? Oder handelt es sich hier um einen separaten Austausch, der ein eigenes Thema verdient?

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

Neues Feld oder neue Veranstaltung?

Wenn Sie jedoch dieselben Ereignisse verwenden, entsteht ein weiteres Problem. Beispielsweise können nicht alle Liefersysteme die Art von DTO generieren, die BOB generieren kann. Wir senden ihnen die ID, aber sie speichern sie nicht, weil sie sie nicht benötigen, und aus der Sicht des Startens des Event-Bus-Prozesses ist dieses Feld ein Pflichtfeld.

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

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

Im Falle von Rückerstattungen kamen wir im Falle von Ereignissen in einem halben Jahr an. Wir hatten ein Metaereignis namens Rückerstattungsaktualisierung, das ein Typfeld hatte, das beschrieb, was dieses Update eigentlich war. Aus diesem Grund hatten wir „wunderbare“ Gespräche mit Validatoren, die uns erklärten, wie wir dieses Ereignis mit diesem Typ validieren können.

Ereignisversionierung

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

Garantierte Lesereihenfolge der Partitionen

Die Themen innerhalb von Kafka sind in Abschnitte unterteilt. Dies ist nicht sehr wichtig, während wir Entitäten und Börsen entwerfen, aber es ist wichtig, wenn wir entscheiden, wie wir sie nutzen und skalieren.

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

Wie teilt Kafka sie ein? Jede Nachricht hat einen Text (in dem wir JSON speichern) und einen Schlüssel. Sie können diesem Schlüssel eine Hash-Funktion hinzufügen, die bestimmt, in welche Partition die Nachricht gesendet wird.

In unserem Fall mit Rückerstattungen ist dies wichtig. Wenn wir zwei Partitionen verwenden, besteht die Möglichkeit, dass ein paralleler 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, auf das wir gestoßen sind. Ein Ereignis ist ein bestimmtes Ereignis: Wir sagen, dass irgendwo etwas passiert ist (something_happened), zum Beispiel, dass ein Artikel storniert wurde oder eine Rückerstattung erfolgt ist. Wenn jemand auf diese Ereignisse hört, wird gemäß „Artikel storniert“ die Rückerstattungseinheit erstellt und irgendwo in den Einstellungen wird „Rückerstattung erfolgt“ geschrieben.

Aber wenn man Veranstaltungen entwirft, möchte man sie normalerweise nicht umsonst schreiben – man verlässt sich darauf, dass jemand sie liest. Die Versuchung ist groß, nicht „something_happened“ (item_canceled, „refund_refunded“), sondern „something_should_be_done“ zu schreiben. Beispielsweise ist der Artikel zur Rückgabe bereit.

Einerseits gibt es Hinweise darauf, wie die Veranstaltung genutzt werden soll. Andererseits klingt es viel weniger wie ein normaler Veranstaltungsname. Außerdem ist es von hier aus nicht mehr weit bis zum Befehl do_something. Sie haben jedoch keine Garantie dafür, 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. In dem Moment, in dem ein Ereignis zu „do_something“ wird, wird Feedback notwendig, und das ist ein Problem.

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

Wenn Sie beim asynchronen Austausch in RabbitMQ die Nachricht lesen, 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.

Deshalb mussten wir in unserem Fall ein Antwortereignis einführen und die Überwachung so einrichten, dass, wenn so viele Ereignisse gesendet wurden, nach dieser oder jener Zeit die gleiche Anzahl von Antwortereignissen eintreffen sollte. Geschieht dies nicht, dann scheint etwas schief gelaufen zu sein. Wenn wir beispielsweise das Ereignis „item_ready_to_refund“ gesendet haben, erwarten wir, dass eine Rückerstattung erfolgt, das Geld an den Kunden zurückerstattet wird und das Ereignis „money_refunded“ an uns gesendet wird. Dies ist jedoch nicht sicher, daher ist eine Überwachung erforderlich.

Nuancen

Es gibt ein ziemlich offensichtliches Problem: Wenn Sie ein Thema nacheinander lesen und eine schlechte Nachricht erhalten, wird der Verbraucher fallen und Sie werden nicht weitermachen. Sie brauchen Stoppen Sie alle Verbraucher, begehen Sie den Offset weiter, um mit dem Lesen fortzufahren.

Wir wussten davon, wir haben damit gerechnet, und doch ist es passiert. Und das 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 in unserem einzigen System MySQL mit UNSIGNED INT, und im neu geschriebenen System hatte PostgreSQL nur mit INT. Seine Größe 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 erfolglos 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 abgebrochen, ohne dass eine Möglichkeit zum Commit-Offset bestand.

Der Dienst legte für einige Zeit nach – bei Kafka ist das zum Glück nicht so schlimm, denn die Nachrichten bleiben bestehen. Wenn die Arbeit wiederhergestellt ist, können Sie sie zu Ende lesen. Das ist bequem.

Kafka hat die Möglichkeit, durch Werkzeuge 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 Neubereitstellungen gibt. Dann können Sie in Kafka den Versatz durch Tools verschieben, und die Nachricht wird weitergeleitet.

Eine weitere Nuance - Replikationsprotokoll vs. rdkafka.so - hängt mit den Besonderheiten unseres Projekts zusammen. Wir verwenden PHP, und in PHP kommunizieren in der Regel alle Bibliotheken mit Kafka über das Repository rdkafka.so, und dann gibt es noch eine Art Wrapper. Vielleicht sind das unsere persönlichen Schwierigkeiten, aber es stellte sich heraus, dass es nicht so einfach ist, einen Teil des bereits Gelesenen noch einmal zu lesen. Im Allgemeinen gab es Softwareprobleme.

Zurück zu den Besonderheiten der Arbeit mit Partitionen: Dies wird direkt in der Dokumentation beschrieben Verbraucher >= Themenpartitionen. Aber ich erfuhr davon viel später, 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 Partition erstellt haben, wird die Anzahl der Nachrichten nicht so schnell ausgeglichen. Um zwei parallele Verbraucher zu haben, müssen Sie sich daher mit Partitionen befassen.

Überwachung

Ich denke, die Art und Weise, wie wir es überwachen, wird noch deutlicher machen, welche Probleme es im bestehenden Ansatz gibt.

Wir berechnen beispielsweise, wie viele Produkte in der Datenbank kürzlich ihren Status geändert haben und dementsprechend Ereignisse aufgrund dieser Änderungen hätten eintreten müssen, und senden diese Zahl an unser Überwachungssystem. Von Kafka erhalten wir dann die zweite Zahl, wie viele Ereignisse tatsächlich aufgezeichnet wurden. Offensichtlich sollte 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 müssen Sie überwachen, wie es dem Produzenten geht, ob der Ereignisbus Nachrichten empfangen hat und wie es dem Verbraucher geht. In den folgenden Diagrammen geht es beispielsweise dem Refund Tool gut, aber BOB hat eindeutig einige Probleme (blaue Spitzen).

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

Ich habe die Verzögerung der Verbrauchergruppe bereits erwähnt. Grob gesagt ist dies die Anzahl der ungelesenen Nachrichten. Im Allgemeinen arbeiten unsere Verbraucher schnell, daher ist die Verzögerung normalerweise 0, aber manchmal kann es zu einer kurzfristigen Spitze kommen. Kafka kann dies sofort tun, Sie müssen jedoch ein bestimmtes Intervall festlegen.

Es gibt ein Projekt BauHier finden Sie weitere Informationen zu Kafka. Es verwendet einfach die Consumer-Group-API, um den Status der Leistung dieser Gruppe anzugeben. Zusätzlich zu „OK“ und „Fehlgeschlagen“ gibt es eine Warnung, und Sie können feststellen, dass Ihre Verbraucher mit dem Tempo der Produktion nicht zurechtkommen – sie haben keine Zeit, das Geschriebene Korrektur zu lesen. Das System ist recht 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, Partitionserstattung.update.v1, Status OK, Verzögerung 0 – der letzte endgültige Offset so und so.

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

Überwachung aktualisiertes_at SLA (hängt) Ich habe es bereits erwähnt. Beispielsweise hat sich das Produkt in den Status geändert, dass es zur Rückgabe bereit ist. Wir installieren Cron, das besagt, dass, wenn dieses Objekt innerhalb von 5 Minuten nicht zur Rückerstattung gelangt ist (wir geben Geld über Zahlungssysteme sehr schnell zurück), dann definitiv etwas schief gelaufen ist, und dies ist definitiv ein Fall für den Support. Deshalb 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 die Verwendung von Ereignissen praktisch ist, wenn:

  • Informationen werden von mehreren Systemen benötigt;
  • das Ergebnis der Verarbeitung ist nicht wichtig;
  • Es gibt wenige Veranstaltungen oder kleine Veranstaltungen.

Es scheint, dass der Artikel ein sehr spezifisches Thema hat – die asynchrone API für Kafka, aber im Zusammenhang damit möchte ich viele Dinge auf einmal empfehlen.
Zuerst, dann als nächstes HighLoad ++ Wir müssen bis November warten, im April wird es eine St. Petersburger Version geben und im Juni werden wir über hohe Auslastungen in Nowosibirsk sprechen.
Zweitens ist der Autor des Berichts, Sergei Zaika, Mitglied des Programmausschusses unserer neuen Konferenz zum Thema Wissensmanagement WissenConf. Die Konferenz ist eintägig und findet am 26. April statt, ihr Programm ist jedoch sehr intensiv.
Und es wird im Mai sein PHP Russland и RIT++ (inklusive DevOpsConf) – Sie können dort auch Ihr Thema vorschlagen, über Ihre Erfahrungen sprechen und sich über Ihre gefüllten Zapfen beschweren.

Source: habr.com

Kommentar hinzufügen