{"id":30778,"date":"2019-10-31T21:37:23","date_gmt":"2019-10-31T18:37:23","guid":{"rendered":"https:\/\/prohoster.info\/blog\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka\/"},"modified":"2019-10-31T21:37:23","modified_gmt":"2019-10-31T18:37:23","slug":"opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka","status":"publish","type":"post","link":"https:\/\/prohoster.info\/de\/blog\/administrirovanie\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka","title":{"rendered":"Erfahrung in der Entwicklung des Refund Tool-Dienstes mit asynchroner API auf Kafka","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Was kann ein so gro\u00dfes Unternehmen wie Lamoda mit einem optimierten Prozess und Dutzenden miteinander verbundener Dienste dazu bewegen, seinen Ansatz grundlegend zu \u00e4ndern? Die Motivation kann ganz unterschiedlich sein: von gesetzlichen Vorschriften bis hin zum angeborenen Drang aller Programmierer, zu experimentieren.<\/p>\n<p>Doch das bedeutet keineswegs, dass man nicht auf zus\u00e4tzliche Vorteile hoffen kann. Wor\u00fcber man konkret profitieren kann, wenn man eine events-gesteuerte API auf Kafka einf\u00fchrt, wird Sergej Zaika erz\u00e4hlen (<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/users\/fewald\/\" class=\"user_link\">fewald<\/a><\/noindex>). Auch die gesammelten Erfahrungen und interessanten Entdeckungen werden nat\u00fcrlich nicht fehlen \u2013 Experimente kommen nicht ohne sie aus.<\/p>\n<p><img decoding=\"async\" alt=\"Erfahrung in der Entwicklung des Refund Tool-Dienstes mit asynchroner API auf Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/7ab959ab45ec5c6565b35b18b361c0ea.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\n<em>Haftungsausschluss: Dieser Artikel basiert auf Materialien eines Meetups, das Sergej im November 2018 auf HighLoad++ durchgef\u00fchrt hat. Die lebhaften Erfahrungen von Lamoda mit Kafka haben nicht weniger Zuh\u00f6rer angezogen als andere Vortr\u00e4ge im Zeitplan. Es scheint uns ein hervorragendes Beispiel daf\u00fcr zu sein, dass man immer Gleichgesinnte finden kann und dass die Organisatoren von HighLoad++ weiterhin bestrebt sind, eine einladende Atmosph\u00e4re daf\u00fcr zu schaffen.<\/em><br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>\u00dcber den Prozess<\/h2>\n<p>\nLamoda ist eine gro\u00dfe E-Commerce-Plattform, die \u00fcber ein eigenes Callcenter, einen Lieferservice (und viele Partner), ein Fotostudio, ein riesiges Lager und all das, was auf eigener Software basiert, verf\u00fcgt. Es gibt Dutzende von Zahlungsmethoden, B2B-Partner, die Teile oder alle diese Dienstleistungen nutzen k\u00f6nnen und aktuelle Informationen zu ihren Produkten erhalten m\u00f6chten. Dar\u00fcber hinaus ist Lamoda in drei L\u00e4ndern t\u00e4tig, abgesehen von der RF, wo alles ein wenig anders ist. Insgesamt gibt es wahrscheinlich mehr als hundert M\u00f6glichkeiten, eine neue Bestellung zu konfigurieren, die auf ihre jeweils eigene Weise verarbeitet werden muss. All dies funktioniert mit Hilfe von Dutzenden von Dienstleistungen, die manchmal auf nicht offensichtliche Weise miteinander kommunizieren. Au\u00dferdem gibt es ein zentrales System, dessen Hauptverantwortung die Bestellstatus ist. Wir nennen es BOB, und ich arbeite damit.<\/p>\n<h2>Refund Tool mit events-gesteuerter API <\/h2>\n<p>\nDer Begriff events-gesteuert ist ziemlich abgedroschen; gleich werden wir n\u00e4her definieren, was damit gemeint ist. Ich werde mit dem Kontext beginnen, in dem wir beschlossen haben, den Ansatz der events-gesteuerten API in Kafka auszuprobieren. <\/p>\n<p><img decoding=\"async\" alt=\"Erfahrung in der Entwicklung des Refund Tool-Dienstes mit asynchroner API auf Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/3bfdce47dd8420fc63d84645e76de647.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nIn jedem Gesch\u00e4ft, neben den Bestellungen, f\u00fcr die die K\u00e4ufer bezahlen, gibt es Momente, in denen der Laden verlangt, das Geld zur\u00fcckzugeben, weil das Produkt dem Kunden nicht zusagt. Dieser vergleichsweise kurze Prozess: Wir kl\u00e4ren die Informationen, falls n\u00f6tig, und \u00fcberweisen das Geld. <\/p>\n<p>Die R\u00fcckabwicklung hat sich aufgrund der \u00c4nderungen in der Gesetzgebung kompliziert gestaltet, und wir mussten daf\u00fcr einen eigenen Microservice implementieren.<\/p>\n<p><img decoding=\"async\" alt=\"Erfahrung in der Entwicklung des Refund Tool-Dienstes mit asynchroner API auf Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/0e358df5476e7448976e0f1147a103fb.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nUnsere Motivation:<\/p>\n<ol>\n<li><strong>Gesetz FZ-54<\/strong>\u00a0\u2014 kurz gesagt, das Gesetz erfordert, dass jede Geldtransaktion, sei es eine R\u00fcckzahlung oder ein Zahlungseingang, innerhalb eines recht kurzen SLAs von wenigen Minuten an das Finanzamt gemeldet wird. Wir als E-Commerce-Unternehmen f\u00fchren relativ viele Transaktionen durch. Technisch bedeutet das eine neue Verantwortung (und damit einen neuen Service) sowie Anpassungen in allen beteiligten Systemen.<\/li>\n<li><strong>BOB split<\/strong>\u00a0\u2014 ein internes Unternehmensprojekt zur Befreiung von BOB von vielen unangebrachten Verantwortlichkeiten und zur Reduzierung seiner Gesamtheit.<\/li>\n<\/ol>\n<p>\n<img decoding=\"async\" alt=\"Erfahrung in der Entwicklung des Refund Tool-Dienstes mit asynchroner API auf Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/d3ecf9961bdb372fc5f84ee9389f73ef.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nIn diesem Diagramm sind die Hauptsysteme von Lamoda dargestellt. Derzeit besteht der Gro\u00dfteil aus eher <strong>einem Sternenbild aus 5-10 Microservices rund um einen schrumpfenden Monolithen<\/strong>. Sie wachsen langsam, aber wir versuchen, sie kleiner zu halten, denn eine Ver\u00f6ffentlichung eines fokussierten Mittelteils ist be\u00e4ngstigend \u2013 wir d\u00fcrfen nicht zulassen, dass er ausf\u00e4llt. Alle Austausche (Pfeile) m\u00fcssen wir reservieren und darauf setzen, dass jeder von ihnen m\u00f6glicherweise nicht verf\u00fcgbar sein kann.<\/p>\n<p>Auch in BOB gibt es ziemlich viele Austausche: Zahlungssysteme, Lieferdienste, Benachrichtigungen usw. <\/p>\n<p>Technisch gesehen ist BOB:<\/p>\n<ul>\n<li>~150k Zeilen Code + ~100k Testzeilen;<\/li>\n<li>php7.2 + Zend 1 &amp; Symfony Components 3;<\/li>\n<li>&gt;100 APIs &amp; ~50 ausgehende Integrationen;<\/li>\n<li>4 L\u00e4nder mit ihrer eigenen Gesch\u00e4ftslogik. <\/li>\n<\/ul>\n<p>\nDie Bereitstellung von BOB ist kostspielig und schmerzhaft, die Menge an Code und die damit verbundenen Aufgaben sind so umfangreich, dass niemand sie vollst\u00e4ndig im Kopf behalten kann. Insgesamt gibt es viele Gr\u00fcnde, ihn zu vereinfachen.<\/p>\n<h2>R\u00fcckgabeprozess<\/h2>\n<p>\nUrspr\u00fcnglich sind zwei Systeme in den Prozess involviert: BOB und Payment. Jetzt kommen noch zwei hinzu:<\/p>\n<ul>\n<li>Fiscalization Service, der die Probleme mit der Fiskalisierung und der Kommunikation mit externen Diensten \u00fcbernehmen wird.<\/li>\n<li>Refund Tool, in dem einfach neue Austausche ausgelagert werden, um BOB nicht aufzubl\u00e4hen.<\/li>\n<\/ul>\n<p>\nJetzt sieht der Prozess so aus:<\/p>\n<p><img decoding=\"async\" alt=\"Erfahrung in der Entwicklung des Refund Tool-Dienstes mit asynchroner API auf Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/13c02975881ad35c61304053c604cda3.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<\/p>\n<ol>\n<li>An BOB wird eine Anfrage zur R\u00fcckzahlung gestellt.<\/li>\n<li>BOB informiert dar\u00fcber das Refund Tool.<\/li>\n<li>Refund Tool sagt zu Payment: \u201eErstatte das Geld\u201c.<\/li>\n<li>Payment erstattet das Geld.<\/li>\n<li>Refund Tool und BOB synchronisieren die Status untereinander, da sie das derzeit beide ben\u00f6tigen. Wir sind noch nicht bereit, vollst\u00e4ndig auf Refund Tool umzuschwenken, da es in BOB eine UI gibt, Berichte f\u00fcr die Buchhaltung und viele Daten, die sich nicht einfach \u00fcbertragen lassen. Wir m\u00fcssen auf zwei St\u00fchlen sitzen.<\/li>\n<li>Die Anfrage zur Fiskalisierung wird gesendet.<\/li>\n<\/ol>\n<p>\nWir haben letztendlich eine Art Ereignisbus auf Kafka geschaffen, auf dem alles basierte. Hurra, jetzt haben wir einen einzigen Fehlerpunkt (Sarkasmus).<\/p>\n<p><img decoding=\"async\" alt=\"Erfahrung in der Entwicklung des Refund Tool-Dienstes mit asynchroner API auf Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/674edd7972998b4985071f5250612c7e.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nDie Vor- und Nachteile sind ziemlich offensichtlich. Wir haben einen Bus erstellt, was bedeutet, dass jetzt alle Dienste davon abh\u00e4ngen. Das vereinfacht das Design, bringt aber einen einzigen Fehlerpunkt in das System. F\u00e4llt Kafka aus, steht der Prozess still.<\/p>\n<h2>Was ist ein ereignisgesteuertes API? <\/h2>\n<p>\nEine gute Antwort auf diese Frage finden Sie im Vortrag von Martin Fowler (GOTO 2017). <noindex><a rel=\"nofollow\" href=\"https:\/\/youtu.be\/STKCRSUsyPO\">\u201eDie vielen Bedeutungen der ereignisgesteuerten Architektur\u201c<\/a><\/noindex>. <\/p>\n<p>Kurz gesagt, was wir gemacht haben:<\/p>\n<ol>\n<li>Wir haben alle asynchronen Kommunikationen \u00fcber <strong>Ereignisspeicher<\/strong>verbunden. Anstatt allen interessierten Verbrauchern \u00fcber Status\u00e4nderungen zu berichten, schreiben wir in ein zentrales Repository ein Ereignis \u00fcber eine Zustands\u00e4nderung, und die interessierten Verbraucher lesen alles, was dort erscheint.<\/li>\n<li>Ein Ereignis (event) in diesem Fall ist eine Benachrichtigung (<strong>notifications<\/strong>) dar\u00fcber, dass sich etwas irgendwo ge\u00e4ndert hat. Zum Beispiel hat sich der Status einer Bestellung ge\u00e4ndert. Ein Verbraucher, dem bestimmte begleitende \u00c4nderungsdaten wichtig sind, die in der Benachrichtigung nicht enthalten sind, kann den aktuellen Stand selbst abfragen.<\/li>\n<li>Die maximale Option ist ein vollst\u00e4ndiges Event Sourcing, <strong>State Transfer<\/strong>, bei dem das Ereignis alle Informationen enth\u00e4lt, die zur Verarbeitung erforderlich sind: wohin und in welchen Status sie gewechselt haben, wie sich die Daten ge\u00e4ndert haben usw. Es geht nur um die Zweckm\u00e4\u00dfigkeit und das Volumen an Informationen, das Sie sich leisten k\u00f6nnen zu speichern.<\/li>\n<\/ol>\n<p>\nIm Rahmen des Starts des Refund Tools haben wir die dritte Option verwendet. Das hat die Behandlung von Ereignissen vereinfacht, da es nicht notwendig war, detaillierte Informationen zu beschaffen, und das Szenario ausgeschlossen hat, bei dem jedes neue Ereignis einen Anstieg von kl\u00e4renden GET-Anfragen von Verbrauchern ausl\u00f6st.<\/p>\n<p>Der Service Refund Tool <strong>ist nicht belastet,<\/strong>deshalb ist Kafka dort eher ein Versuch als eine Notwendigkeit. Ich glaube nicht, dass das Unternehmen gl\u00fccklich w\u00e4re, wenn der R\u00fcckerstattungsdienst ein Hochlastprojekt w\u00e4re.<\/p>\n<h4>Asynchroner Austausch AS IS<\/h4>\n<p>\nF\u00fcr asynchrone Austausche verwendet die PHP-Abteilung normalerweise RabbitMQ. Wir haben die Daten f\u00fcr die Anfrage gesammelt, diese in die Warteschlange gestellt, und der Verbraucher desselben Dienstes hat sie verarbeitet und gesendet (oder nicht gesendet). F\u00fcr die API verwendet Lamoda aktiv Swagger. Wir konzipieren die API, beschreiben sie in Swagger, generieren den Client- und Servercode. Au\u00dferdem nutzen wir eine etwas erweiterte JSON RPC 2.0. <\/p>\n<p>\u00dcberall werden ESB-Busse verwendet, manche arbeiten mit ActiveMQ, aber insgesamt <strong>RabbitMQ \u2013 Standard<\/strong>.<\/p>\n<h4>Async-Austausch TO BE<\/h4>\n<p>\nBei der Gestaltung des Austauschs \u00fcber den Events-Bus gibt es eine Analogie. Wir beschreiben auf \u00e4hnliche Weise den zuk\u00fcnftigen Datenaustausch anhand der Struktur des Events. Das YAML-Format, die Code-Generierung mussten wir selbst vornehmen, der Generator erstellt DTOs gem\u00e4\u00df der Spezifikation und lehrt Clients und Server, damit zu arbeiten. Die Generierung erfolgt in zwei Sprachen \u2013 <strong>Golang und PHP<\/strong>. Das erm\u00f6glicht es, die Bibliotheken konsistent zu halten. Der Generator wurde in Golang geschrieben, weshalb er den Namen gogi erhielt.<\/p>\n<p>Event-Sourcing auf Kafka ist eine g\u00e4ngige Praxis. Es gibt eine L\u00f6sung von der Haupt-Enterprise-Version von Kafka Confluent, es gibt <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/zalando\/nakadi\">Nakadi<\/a><\/noindex>, eine L\u00f6sung von unseren \u201eBr\u00fcdern\u201c im Domainbereich Zalando. Unsere <strong>Motivation, mit Vanilla Kafka zu beginnen,<\/strong>\u00a0besteht darin, die L\u00f6sung kostenlos zu halten, bis wir endg\u00fcltig entschieden haben, ob wir sie umfassend nutzen werden, und uns zudem Spielraum f\u00fcr Anpassungen zu lassen: wir wollen Unterst\u00fctzung f\u00fcr unsere <strong>JSON RPC 2.0<\/strong>, Generatoren f\u00fcr zwei Sprachen und sehen, was es noch gibt. <\/p>\n<p>Ironisch ist, dass wir selbst in einem so gl\u00fccklichen Fall, in dem es ein ungef\u00e4hr analoges Unternehmen wie Zalando gibt, das eine \u00e4hnliche L\u00f6sung entwickelt hat, diese nicht effektiv nutzen k\u00f6nnen. <\/p>\n<p>Architektonisch sieht das Muster beim Start so aus: Wir lesen direkt aus Kafka, schreiben aber nur \u00fcber den Events-Bus. F\u00fcr das Lesen in Kafka gibt es viel Vorhandenes: Broker, Lastverteiler und sie ist mehr oder weniger bereit f\u00fcr horizontale Skalierung, das wollten wir erhalten. Schreiben hingegen wollten wir \u00fcber ein Gateway, auch bekannt als Events-Bus, und das ist der Grund.<\/p>\n<h3>Events-Bus<\/h3>\n<p>\nOder Event-Bus. Dies ist einfach ein stateless HTTP-Gateway, das mehrere wichtige Rollen \u00fcbernimmt:<\/p>\n<ul>\n<li><strong>Validierung des Produzierens<\/strong>\u00a0\u2013 wir \u00fcberpr\u00fcfen, ob die Events unserer Spezifikation entsprechen.<\/li>\n<li><strong>Das Hauptsystem f\u00fcr Events<\/strong>, das hei\u00dft, es ist das Haupt- und einzige System im Unternehmen, das die Frage beantwortet, welche Events mit welchen Strukturen als g\u00fcltig gelten. In die Validierung flie\u00dfen einfach Datentypen und Enums f\u00fcr die strikte Spezifikation des Inhalts ein. <\/li>\n<li><strong>Hash-Funktion<\/strong> f\u00fcr Sharding \u2013 die Struktur der Kafka-Nachricht ist key-value und der Hash vom key wird berechnet, um zu bestimmen, wo diese abzulegen ist.<\/li>\n<\/ul>\n<p><\/p>\n<h3>Warum<\/h3>\n<p>\nWir arbeiten in einem gro\u00dfen Unternehmen mit einem etablierten Prozess. Warum etwas \u00e4ndern? <strong>Dies ist ein Experiment<\/strong>, und wir erwarten, mehrere Vorteile zu erzielen.<\/p>\n<h4>1:n+1 Austausche (eins zu vielen)<\/h4>\n<p>\nMit Kafka ist es sehr einfach, neue Verbraucher \u00fcber die API anzuschlie\u00dfen. <\/p>\n<p>Angenommen, Sie haben ein Verzeichnis, das in mehreren Systemen gleichzeitig (und in einigen neuen) aktuell gehalten werden muss. Fr\u00fcher haben wir ein Bundle erfunden, das das Set-API implementierte, und der Master-System hat die Adressen der Verbraucher mitgeteilt. Jetzt sendet das Master-System Updates an ein Topic, und alle, die interessiert sind, lesen mit. Ein neues System ist erschienen - es wurde an das Topic angeschlossen. Ja, auch ein Bundle, aber einfacher.<\/p>\n<p>Im Fall des Refund-Tools, das ein Teil von BOB ist, ist es uns bequem, sie \u00fcber Kafka synchronisiert zu halten. Die Zahlung sagt, dass das Geld zur\u00fcckgegeben wurde: BOB, RT haben davon erfahren, ihre Status ge\u00e4ndert, und der Fiscalization Service hat auch davon erfahren und einen Beleg ausgestellt.<\/p>\n<p><img decoding=\"async\" alt=\"Erfahrung in der Entwicklung des Refund Tool-Dienstes mit asynchroner API auf Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/b01b22a333b58e87aeef0c52d40e6960.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nWir planen einen einheitlichen Notifications Service, der den Kunden \u00fcber Neuigkeiten zu seiner Bestellung\/R\u00fcckgaben informieren w\u00fcrde. Momentan ist diese Verantwortung auf mehrere Systeme verteilt. Es reicht aus, den Notifications Service zu lehren, relevante Informationen aus Kafka zu filtern und darauf zu reagieren (und die Benachrichtigungen in den anderen Systemen zu deaktivieren). Es sind keine neuen direkten Austausche erforderlich.<\/p>\n<h4>Datengetrieben<\/h4>\n<p>\nDie Informationen zwischen den Systemen werden transparent - egal wie 'blutig' Ihr Unternehmen ist und wie umfangreich Ihr Backlog. Bei Lamoda gibt es eine Abteilung f\u00fcr Datenanalytik, die Daten \u00fcber Systeme sammelt und sie sowohl f\u00fcr das Gesch\u00e4ft als auch f\u00fcr intelligente Systeme in wiederverwendbare Form bringt. Kafka erm\u00f6glicht es, ihnen schnell viele Daten zur Verf\u00fcgung zu stellen und diesen Informationsfluss aktuell zu halten.<\/p>\n<h4>Replikationsprotokoll<\/h4>\n<p>\nNachrichten verschwinden nicht nach dem Lesen, wie bei RabbitMQ. Wenn ein Ereignis gen\u00fcgend Informationen f\u00fcr die Verarbeitung enth\u00e4lt, haben wir eine Historie der letzten \u00c4nderungen an dem Objekt, und bei Bedarf die M\u00f6glichkeit, diese \u00c4nderungen anzuwenden.<\/p>\n<p>Die Aufbewahrungsdauer des Replikationsprotokolls h\u00e4ngt von der Intensit\u00e4t der Aufzeichnungen in dieses Topic ab, Kafka erm\u00f6glicht es, die Zeitlimits und die Datenmenge flexibel einzustellen. F\u00fcr intensive Topics ist es wichtig, dass alle Verbraucher die Informationen rechtzeitig abrufen, bevor sie verschwinden, selbst im Fall einer vor\u00fcbergehenden Unf\u00e4higkeit. Normalerweise gelingt es, Daten f\u00fcr\u00a0<strong>Tage aufzubewahren<\/strong>, was f\u00fcr den Support v\u00f6llig ausreichend ist. <\/p>\n<p><img decoding=\"async\" alt=\"Erfahrung in der Entwicklung des Refund Tool-Dienstes mit asynchroner API auf Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/0e08dd384155289123ebee96430c2370.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nEin bisschen eine Zusammenfassung der Dokumentation f\u00fcr diejenigen, die nicht mit Kafka vertraut sind (das Bild stammt ebenfalls aus der Dokumentation)<\/p>\n<p>In AMQP gibt es Warteschlangen: Wir schreiben Nachrichten in die Warteschlange f\u00fcr den Verbraucher. In der Regel verarbeitet ein System mit derselben Gesch\u00e4ftslogik eine Warteschlange. Wenn mehrere Systeme benachrichtigt werden m\u00fcssen, kann die Anwendung so konfiguriert werden, dass sie in mehrere Warteschlangen schreibt, oder ein Exchange mit einem Fanout-Mechanismus eingerichtet werden, der sie selbst klont.<\/p>\n<p>In Kafka gibt es eine \u00e4hnliche Abstraktion <em>Thema<\/em>, in das Sie Nachrichten schreiben, aber sie verschwinden nicht nach dem Lesen. Bei der Verbindung mit Kafka erhalten Sie standardm\u00e4\u00dfig alle Nachrichten, und es besteht die M\u00f6glichkeit, den Ort zu speichern, an dem Sie aufgeh\u00f6rt haben. Das bedeutet, dass Sie sequenziell lesen, eine Nachricht nicht als gelesen markieren, aber die ID speichern k\u00f6nnen, von der aus Sie dann weiterlesen. Die ID, an der Sie aufgeh\u00f6rt haben, wird als Offset bezeichnet, und der Mechanismus hei\u00dft Commit-Offset. <\/p>\n<p>Dementsprechend kann unterschiedliche Logik implementiert werden. Zum Beispiel haben wir BOB in 4 Instanzen f\u00fcr verschiedene L\u00e4nder \u2013 Lamoda ist in Russland, Kasachstan, der Ukraine und Wei\u00dfrussland verf\u00fcgbar. Da sie separat bereitgestellt werden, haben sie unterschiedliche Konfigurationen und ihre eigene Gesch\u00e4ftslogik. Wir geben in der Nachricht an, auf welches Land sie sich bezieht. Jeder BOB-Verbraucher in jedem Land liest mit unterschiedlichen groupId, und wenn die Nachricht nicht relevant ist, wird sie \u00fcbersprungen, d.h. der Offset wird sofort um +1 best\u00e4tigt. Wenn dasselbe Thema von unserem Payment Service gelesen wird, geschieht dies mit einer separaten Gruppe, sodass die Offsets sich nicht \u00fcberschneiden.<\/p>\n<p><b>Anforderungen an Ereignisse:<\/b><\/p>\n<ul>\n<li><strong>Datenvollst\u00e4ndigkeit. <\/strong>Es w\u00e4re w\u00fcnschenswert, dass gen\u00fcgend Daten im Ereignis vorhanden sind, um es verarbeiten zu k\u00f6nnen. <\/li>\n<\/ul>\n<p><\/p>\n<ul>\n<li><strong>Integrit\u00e4t. <\/strong>Wir delegieren dem Events-Bus die \u00dcberpr\u00fcfung, dass das Ereignis konsistent ist und verarbeitet werden kann.<\/li>\n<li><strong>Die Reihenfolge ist wichtig. <\/strong>Im Falle einer R\u00fcckgabe sind wir gezwungen, mit der Historie zu arbeiten. Bei Benachrichtigungen ist die Reihenfolge unerheblich, wenn es sich um homogene Benachrichtigungen handelt; die E-Mail wird gleich sein, unabh\u00e4ngig davon, welcher Auftrag zuerst eingetroffen ist. Im Falle einer R\u00fcckgabe gibt es einen klaren Prozess; wenn die Reihenfolge ge\u00e4ndert wird, k\u00f6nnten Ausnahmen auftreten, R\u00fcckerstattungen werden nicht ausgel\u00f6st oder verarbeitet \u2013 und wir geraten in einen anderen Status.<\/li>\n<li><strong>Koh\u00e4renz. <\/strong>Wir haben ein Repository, und jetzt erstellen wir anstelle von APIs Events. Wir ben\u00f6tigen eine M\u00f6glichkeit, schnell und kosteng\u00fcnstig Informationen \u00fcber neue Events und \u00c4nderungen an bestehenden Events an unsere Dienste zu \u00fcbermitteln. Dies wird durch eine gemeinsame Spezifikation in einem separaten Git-Repository und Code-Generatoren erreicht. Daher sind die Clients und Server in unseren verschiedenen Diensten abgestimmt.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Kafka bei Lamoda<\/h2>\n<p>\nWir haben drei Kafka-Installationen: <\/p>\n<ol>\n<li>Logs;<\/li>\n<li>Forschung &amp; Entwicklung;<\/li>\n<li>Events-Bus.<\/li>\n<\/ol>\n<p>\nHeute sprechen wir nur \u00fcber den letzten Punkt. In unserem Events-Bus haben wir nicht sehr gro\u00dfe Installationen - 3 Broker (Server) und insgesamt 27 Themen. Ein Thema ist in der Regel ein Prozess. Aber das ist ein heikles Thema, und darauf werden wir gleich zu sprechen kommen.<\/p>\n<p><img decoding=\"async\" alt=\"Erfahrung in der Entwicklung des Refund Tool-Dienstes mit asynchroner API auf Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/f398852689b31429cc97b4cbffcabab5.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nOben ist das Diagramm der rps. Der Prozess R\u00fcckerstattungen ist durch eine t\u00fcrkisfarbene Linie markiert (ja, ja, die, die auf der X-Achse liegt), und die rosa Linie zeigt den Prozess der Inhaltsaktualisierung. <\/p>\n<p>Der Katalog von Lamoda enth\u00e4lt Millionen von Produkten, und die Daten werden st\u00e4ndig aktualisiert. Einige Kollektionen geraten aus der Mode, daf\u00fcr werden neue ver\u00f6ffentlicht, und im Katalog erscheinen st\u00e4ndig neue Modelle. Wir versuchen vorherzusagen, was morgen f\u00fcr unsere Kunden interessant sein wird, deshalb kaufen wir st\u00e4ndig neue Artikel, fotografieren sie und aktualisieren das Schaufenster. <\/p>\n<p>Die rosa Spitzen sind Produktaktualisierungen, also \u00c4nderungen bei den Artikeln. Man sieht deutlich, dass die Jungs fotografiert haben, fotografiert haben und dann pl\u00f6tzlich! \u2014 eine Reihe von Ereignissen hochgeladen haben.<\/p>\n<h2>Lamoda Events Anwendungsf\u00e4lle<\/h2>\n<p>\nDie entwickelte Architektur verwenden wir f\u00fcr folgende Operationen:<\/p>\n<ul>\n<li><strong>Statusverfolgung von R\u00fccksendungen<\/strong>: Call-to-Action und Statusverfolgung von allen beteiligten Systemen. Bezahlung, Status, Fiskalierung, Benachrichtigungen. Hier haben wir einen Ansatz ausprobiert, Werkzeuge erstellt, alle Fehler gesammelt, Dokumentation geschrieben und unseren Kollegen erkl\u00e4rt, wie sie damit umgehen k\u00f6nnen.<\/li>\n<li><strong>Aktualisierung von Produktkarten: <\/strong>Konfiguration, Metadaten, Eigenschaften. Liest ein System (das anzeigt), w\u00e4hrend mehrere schreiben.<\/li>\n<li><strong>E-Mail, Push und SMS<\/strong>: Bestellung zusammengestellt, Bestellung angekommen, R\u00fccksendung akzeptiert usw., viele davon. <\/li>\n<li><strong>Bestand, Lageraktualisierung<\/strong>\u00a0\u2014 quantitative Aktualisierung der Artikel, einfach Zahlen: Eingang ins Lager, R\u00fccksendung. Alle Systeme, die mit der Reservierung von Produkten zu tun haben, m\u00fcssen mit den aktuellsten Daten arbeiten. Momentan ist das System zur Bestandsaktualisierung ziemlich komplex, Kafka wird es vereinfachen.<\/li>\n<li><strong>Datenanalyse<\/strong> (F&amp;E-Abteilung), ML-Tools, Analytik, Statistik. Wir wollen, dass die Informationen transparent sind \u2013 daf\u00fcr eignet sich Kafka gut.<\/li>\n<\/ul>\n<p>\nJetzt kommt der interessantere Teil \u00fcber die Fehler und interessanten Entdeckungen, die in einem halben Jahr gemacht wurden.<\/p>\n<h2>Entwurfsprobleme<\/h2>\n<p>\nAngenommen, wir wollen eine neue Sache machen \u2013 zum Beispiel den gesamten Lieferprozess auf Kafka umzustellen. Derzeit wird ein Teil des Prozesses in der Auftragsbearbeitung in BOB umgesetzt. Hinter der \u00dcbermittlung des Auftrags an den Lieferdienst, der Bewegung zu einem Zwischenlager und \u00e4hnlichem steht ein Statusmodell. Es gibt ein ganzes Monolithen, sogar zwei, plus eine Menge APIs, die sich mit der Lieferung befassen. Sie wissen viel mehr \u00fcber die Lieferung. <\/p>\n<p>Es scheint, dass dies \u00e4hnliche Bereiche sind, aber f\u00fcr die Auftragsbearbeitung in BOB und f\u00fcr das Liefersystem unterscheiden sich die Status. Einige Kurierdienste senden beispielsweise keine Zwischenstatus, sondern nur die endg\u00fcltigen: 'geliefert' oder 'verloren'. Andere hingegen berichten sehr detailliert \u00fcber die Bewegung der Ware. Jeder hat seine eigenen Validierungsregeln: F\u00fcr den einen ist eine g\u00fcltige E-Mail-Adresse notwendig, um verarbeitet zu werden; f\u00fcr andere ist sie nicht g\u00fcltig, aber der Auftrag wird trotzdem bearbeitet, weil es eine Telefonnummer f\u00fcr die Kontaktaufnahme gibt, und wieder andere sagen, dass ein solcher Auftrag \u00fcberhaupt nicht bearbeitet wird.<\/p>\n<h3>Datenfluss<\/h3>\n<p>\nIm Falle von Kafka stellt sich die Frage der Organisation des Datenflusses. Diese Aufgabe h\u00e4ngt von der Wahl einer Strategie in mehreren Punkten ab; wir gehen sie alle durch.<\/p>\n<h4>In einem Topic oder in verschiedenen?<\/h4>\n<p>\nWir haben eine Spezifikation f\u00fcr das Ereignis. In BOB schreiben wir, dass ein bestimmter Auftrag geliefert werden muss, und geben an: Auftragsnummer, Inhalt, einige SKUs und Barcodes usw. Wenn die Ware im Lager ankommt, kann die Lieferung Status, Zeitstempel und alles Notwendige erhalten. Aber dann wollen wir in BOB Updates zu diesen Daten erhalten. Es entsteht ein umgekehrter Prozess der Datenerfassung aus der Lieferung. Ist das dasselbe Ereignis? Oder ist das ein separater Austausch, der ein separates Topic verdient?<\/p>\n<p>Wahrscheinlich werden sie sehr \u00e4hnlich sein, und die Versuchung, ein Topic zu erstellen, ist nicht unbegr\u00fcndet, denn ein separates Topic bedeutet separate Consumer, separate Konfigurationen, separate Generierung davon. Aber es ist nicht sicher.<\/p>\n<h4>Neues Feld oder neues Ereignis?<\/h4>\n<p>\nWenn wir jedoch dieselben Ereignisse verwenden, taucht ein anderes Problem auf. Beispielsweise k\u00f6nnen nicht alle Liefersysteme ein solches DTO generieren, das BOB generieren kann. Wir senden ihnen die ID, aber sie speichern sie nicht, weil sie nicht ben\u00f6tigt werden, und aus der Perspektive des Beginns des Prozess-Event-Bus ist dieses Feld obligatorisch. <\/p>\n<p>Wenn wir f\u00fcr den Event-Bus die Regel einf\u00fchren, dass dieses Feld obligatorisch ist, sind wir gezwungen, in BOB oder im Handler des Startereignisses zus\u00e4tzliche Validierungsregeln festzulegen. Die Validierung beginnt sich im Dienst zu verteilen \u2014 das ist nicht besonders praktisch.<\/p>\n<p>Ein weiteres Problem ist die Versuchung der inkrementellen Entwicklung. Uns wird gesagt, dass wir etwas zum Ereignis hinzuf\u00fcgen m\u00fcssen, und vielleicht, wenn man gut nachdenkt, h\u00e4tte das ein separates Ereignis sein sollen. In unserem Schema ist ein separates Ereignis jedoch ein separates Topic. Ein separates Topic ist der gesamte Prozess, den ich oben beschrieben habe. Der Entwickler hat die Versuchung, einfach ein weiteres Feld in das JSON-Schema einzuf\u00fcgen und neu zu generieren.<\/p>\n<p>Im Fall von Refunds sind wir so innerhalb von sechs Monaten zu einem Ereignis von Ereignissen gekommen. Wir hatten ein Meta-Ereignis, das Refund-Update hei\u00dft und ein Feld vom Typ enth\u00e4lt, das beschreibt, worin dieser Update besteht. Von dort hatten wir \u201ewunderbare\u201c Switches mit Validierern, die sagten, wie man dieses Ereignis mit diesem Typ validieren muss.<\/p>\n<h4>Versionierung von Ereignissen<\/h4>\n<p>\nZur Validierung von Nachrichten in Kafka kann man verwenden <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.confluent.io\/current\/schema-registry\/docs\/index.html\">Avro<\/a><\/noindex>, aber wir mussten von Anfang an darauf achten und Confluent verwenden. In unserem Fall mit der Versionierung m\u00fcssen wir vorsichtig sein. Es wird nicht immer m\u00f6glich sein, Nachrichten aus dem Replikationsprotokoll wiederzulesen, weil das Modell \u201eabgefahren\u201c ist. Im Wesentlichen versuchen wir, Versionen so zu erstellen, dass das Modell abw\u00e4rtskompatibel bleibt: zum Beispiel, indem wir ein Feld vor\u00fcbergehend optional machen. Wenn die Unterschiede zu stark sind, fangen wir an, in ein neues Topic zu schreiben, und wir \u00fcbertragen die Kunden, wenn sie das alte gelesen haben.<\/p>\n<h4>Garantie der Lesereihenfolge von Partitions<\/h4>\n<p>\nTopics innerhalb von Kafka sind in Partitions unterteilt. Das ist zun\u00e4chst nicht besonders wichtig, w\u00e4hrend wir die Entit\u00e4ten und den Austausch entwerfen, aber wichtig, wenn wir entscheiden, wie wir das konsumieren und skalieren.<\/p>\n<p>In der Regel schreiben Sie in Kafka ein Topic. Standardm\u00e4\u00dfig wird eine Partition verwendet, und alle Nachrichten dieses Topics landen darin. Der Consumer liest diese Nachrichten dann nacheinander. Angenommen, Sie m\u00fcssen das System erweitern, sodass zwei verschiedene Consumer die Nachrichten lesen. Wenn Sie zum Beispiel eine SMS senden, k\u00f6nnen Sie Kafka sagen, dass es eine zus\u00e4tzliche Partition erstellen soll, und Kafka beginnt, die Nachrichten auf zwei Teile zu verteilen \u2013 die H\u00e4lfte dort, die H\u00e4lfte hier. <\/p>\n<p>Wie trennt Kafka sie? Jedes Nachrichten hat einen K\u00f6rper (in dem wir JSON speichern) und einen Schl\u00fcssel. An diesen Schl\u00fcssel kann eine Hash-Funktion angeh\u00e4ngt werden, die bestimmt, in welche Partition die Nachricht gelangt.<\/p>\n<p>In unserem Fall mit R\u00fcckerstattungen ist das wichtig, wenn wir zwei Partitionen nehmen, besteht die Chance, dass ein paralleler Consumer das zweite Ereignis schneller verarbeitet als das erste, und das kann Probleme verursachen. Die Hash-Funktion garantiert, dass Nachrichten mit demselben Schl\u00fcssel in der gleichen Partition landen. <\/p>\n<h4>Ereignisse vs. Befehle<\/h4>\n<p>\nDies ist ein weiteres Problem, dem wir begegnet sind. Ein Ereignis ist ein bestimmtes Geschehen: Wir sagen, dass irgendwo etwas passiert ist (something_happened), zum Beispiel, dass ein Artikel storniert wurde oder eine R\u00fcckerstattung stattgefunden hat. Wenn jemand diese Ereignisse h\u00f6rt, wird beim \u201eArtikel storniert\u201c eine R\u00fcckerstattungseinheit erstellt, und \u201eR\u00fcckerstattung erfolgt\u201c wird irgendwo in den Setups festgehalten.<\/p>\n<p>Aber normalerweise, wenn Sie Ereignisse entwerfen, m\u00f6chten Sie sie nicht umsonst schreiben \u2013 Sie rechnen damit, dass sie jemand lesen wird. Es besteht die gro\u00dfe Versuchung, nicht something_happened (item_canceled, refund_refunded) zu schreiben, sondern something_should_be_done. Zum Beispiel, Artikel ist zur R\u00fcckgabe bereit.<\/p>\n<p>Einerseits gibt es Hinweise darauf, wie das Ereignis verwendet wird. Andererseits \u00e4hnelt es viel weniger einem normalen Ereignisnamen. Au\u00dferdem ist es von hier nicht weit weg zu dem Befehl do_something. Aber Sie haben keine Garantie, dass dieses Ereignis von jemandem gelesen wurde; und wenn es gelesen wurde, dann erfolgreich; und wenn es erfolgreich gelesen wurde, dann wurde etwas getan, und dieses Etwas ist erfolgreich durchgegangen. In dem Moment, in dem das Ereignis zu do_something wird, wird Feedback erforderlich, und das ist ein Problem.<\/p>\n<p><img decoding=\"async\" alt=\"Erfahrung in der Entwicklung des Refund Tool-Dienstes mit asynchroner API auf Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/b755d91208092bd9791a41ce4633fb48.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nIn der asynchronen Kommunikation in RabbitMQ, wenn Sie eine Nachricht gelesen haben, gehen Sie in das HTTP, und Sie haben eine Antwort \u2013 zumindest, dass die Nachricht angenommen wurde. Wenn Sie in Kafka geschrieben haben, gibt es eine Nachricht, dass Sie in Kafka geschrieben haben, aber wie sie verarbeitet wurde, wissen Sie nichts. <\/p>\n<p>Daher mussten wir in unserem Fall ein entsprechendes Ereignis einf\u00fchren und das Monitoring so einstellen, dass, wenn eine bestimmte Anzahl von Ereignissen auftritt, nach einer bestimmten Zeit die gleiche Anzahl an R\u00fcckmeldungen erfolgen sollte. Wenn dies nicht geschieht, scheint es, dass etwas schiefgelaufen ist. Zum Beispiel, wenn wir das Ereignis \u201eitem_ready_to_refund\u201c gesendet haben, erwarten wir, dass eine R\u00fcckerstattung erstellt wird, der Kunde sein Geld zur\u00fcckbekommt und wir das Ereignis \u201emoney_refunded\u201c erhalten. Aber das ist nicht sicher, daher ist ein Monitoring erforderlich.<\/p>\n<h3>Nuancen<\/h3>\n<p>\nEs gibt ein ziemlich offensichtliches Problem: Wenn Sie die Nachrichten der Reihe nach vom Thema lesen und eine Nachricht schlecht ist, f\u00e4llt der Consumer aus, und Sie kommen nicht weiter. Sie m\u00fcssen <strong>alle Consumer anhalten<\/strong>, den Offset weiter committen, um weiterzulesen.<\/p>\n<p>Dar\u00fcber waren wir uns bewusst, wir hatten darauf gebaut, und trotzdem ist es passiert. Das passierte, weil das Ereignis aus Sicht des Events-Bus g\u00fcltig war, das Ereignis war aus Sicht des Anwendungsvalidators g\u00fcltig, aber es war nicht aus Sicht von PostgreSQL g\u00fcltig, weil wir in einem System MySQL mit UNSIGNED INT haben und im neu geschriebenen System war es einfach INT bei PostgreSQL. Es ist ein wenig kleiner, und die ID passte nicht. Symfony starb mit einer Ausnahme. Nat\u00fcrlich haben wir die Ausnahme abgefangen, weil wir darauf gebaut hatten, und wollten diesen Offset committen, aber davor wollten wir den Fehlerz\u00e4hler erh\u00f6hen, da die Nachricht nicht erfolgreich verarbeitet wurde. Die Z\u00e4hler in diesem Projekt liegen auch in der Datenbank, und Symfony hatte bereits die Kommunikation mit der Datenbank beendet, und die zweite Ausnahme hat den gesamten Prozess ohne Chance auf Commit des Offsets get\u00f6tet.<\/p>\n<p>Eine Zeit lang lag der Service brach - zum Gl\u00fcck ist das mit Kafka nicht so schlimm, weil die Nachrichten erhalten bleiben. Wenn die Arbeit wiederhergestellt wird, k\u00f6nnen sie nachgelesen werden. Das ist praktisch.<\/p>\n<p>Kafka bietet die M\u00f6glichkeit, \u00fcber Tools einen beliebigen Offset festzulegen. Aber um dies zu tun, m\u00fcssen alle Consumer angehalten werden - in unserem Fall muss ein separates Release vorbereitet werden, in dem keine Consumer vorhanden sind, keine Neuredeployments. Dann kann der Offset \u00fcber Tools in Kafka verschoben werden, und die Nachricht geht durch.<\/p>\n<p>Ein weiterer Aspekt - <strong>Replikationsprotokoll vs rdkafka.so<\/strong>\u00a0\u2014 steht im Zusammenhang mit der Spezifik des Projekts. Wir verwenden PHP, und in PHP kommunizieren in der Regel alle Bibliotheken \u00fcber das Repository rdkafka.so mit Kafka, und dann erfolgt eine Art Wrapper. Vielleicht sind das unsere pers\u00f6nlichen Schwierigkeiten, aber es stellt sich heraus, dass es nicht so einfach ist, einen bereits gelesenen Abschnitt noch einmal zu lesen. Insgesamt gab es Softwareprobleme.<\/p>\n<p>Zur\u00fcck zu den Besonderheiten der Arbeit mit Partitions, es steht direkt in der Dokumentation geschrieben <strong>consumers &gt;= topic partitions<\/strong>. Aber ich habe davon viel sp\u00e4ter erfahren, als ich gerne wollte. Wenn Sie skalieren m\u00f6chten und zwei Konsumenten haben wollen, ben\u00f6tigen Sie mindestens zwei Partitions. Das hei\u00dft, wenn Sie eine Partition hatten, in der 20.000 Nachrichten angesammelt wurden und Sie eine frische erstellt haben, wird die Anzahl der Nachrichten nicht sofort gleichm\u00e4\u00dfig verteilt. Daher m\u00fcssen Sie sich mit Partitions befassen, um zwei parallele Konsumenten zu haben.<\/p>\n<h2>\u00dcberwachung<\/h2>\n<p>\nIch denke, dass es umso deutlicher wird, welche Probleme im bestehenden Ansatz vorhanden sind, je nachdem, wie wir \u00fcberwachen.<\/p>\n<p>Zum Beispiel z\u00e4hlen wir, wie viele Artikel in der Datenbank k\u00fcrzlich ihren Status ge\u00e4ndert haben, und demnach sollten aufgrund dieser \u00c4nderungen Ereignisse aufgetreten sein. Diese Zahl senden wir an unser \u00dcberwachungssystem. Dann erhalten wir aus Kafka eine zweite Zahl, wie viele Ereignisse tats\u00e4chlich aufgezeichnet wurden. Offensichtlich sollte die Differenz zwischen diesen beiden Zahlen immer null sein.<\/p>\n<p><img decoding=\"async\" alt=\"Erfahrung in der Entwicklung des Refund Tool-Dienstes mit asynchroner API auf Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/07d8ed08514f2fb97d9019466b96342c.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nDar\u00fcber hinaus m\u00fcssen wir \u00fcberwachen, wie es dem Producer geht, ob der Events-Bus Nachrichten angenommen hat und wie es dem Konsumenten geht. Zum Beispiel sieht bei Refund Tool alles gut aus, aber bei BOB gibt es offensichtlich einige Probleme (blaue Spitzen).<\/p>\n<p><img decoding=\"async\" alt=\"Erfahrung in der Entwicklung des Refund Tool-Dienstes mit asynchroner API auf Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/57112dbe70d2b388c53f19c34cca6f63.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nIch habe bereits den Lag der Consumer-Gruppe erw\u00e4hnt. Grob gesagt, ist das die Anzahl der ungelesenen Nachrichten. Insgesamt arbeiten unsere Konsumenten schnell, daher liegt der Lag normalerweise bei 0, aber manchmal kann es zu kurzfristigen Spitzen kommen. Kafka kann das out of the box, aber es ist notwendig, einen bestimmten Zeitraum festzulegen. <\/p>\n<p>Es gibt ein Projekt <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/linkedin\/Burrow\">Burrow<\/a><\/noindex>, das Ihnen mehr Informationen \u00fcber Kafka liefert. Es gibt einfach \u00fcber die API den Status der Consumer-Gruppe wieder, wie es dieser Gruppe geht. Neben OK und Failed gibt es dort auch Warnungen, und Sie k\u00f6nnen herausfinden, dass Ihre Konsumenten mit dem Tempo des Produzierens nicht mithalten \u2013 sie schaffen es nicht, das, was geschrieben wird, zu lesen. Das System ist ziemlich intelligent und l\u00e4sst sich gut nutzen. <\/p>\n<p><img decoding=\"async\" alt=\"Erfahrung in der Entwicklung des Refund Tool-Dienstes mit asynchroner API auf Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/cece8495801e187b155487a802e1a35a.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nSo sieht die Antwort \u00fcber die API aus. Hier ist die Gruppe bob-live-fifa, Partition refund.update.v1, Status OK, Lag 0 \u2013 letzter endg\u00fcltiger Offset so und so.<\/p>\n<p><img decoding=\"async\" alt=\"Erfahrung in der Entwicklung des Refund Tool-Dienstes mit asynchroner API auf Kafka\" src=\"\/wp-content\/uploads\/2019\/04\/1538b2ccc390e9b83075f54566f1039b.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\n\u00dcberwachung <strong>updated_at SLA (stuck)<\/strong> Ich habe bereits erw\u00e4hnt. Zum Beispiel hat das Produkt den Status erreicht, dass es zur R\u00fcckgabe bereit ist. Wir setzen Cron ein, der besagt, dass, wenn dieses Objekt innerhalb von 5 Minuten nicht in den Refund-Status \u00fcbergegangen ist (wir erstatten das Geld \u00fcber die Zahlungssysteme sehr schnell), etwas definitiv schief gelaufen ist und es ein Fall f\u00fcr den Support ist. Daher nehmen wir einfach Cron, der solche Sachen liest, und wenn sie gr\u00f6\u00dfer als 0 sind, sendet er einen Alert.<\/p>\n<p><b>Zusammenfassend l\u00e4sst sich sagen, dass es praktisch ist, Ereignisse zu verwenden, wenn<\/b>:<\/p>\n<ul>\n<li>Informationen mehreren Systemen ben\u00f6tigt werden;<\/li>\n<li>das Ergebnis der Verarbeitung nicht wichtig ist;<\/li>\n<li>es nur wenige Ereignisse gibt oder die Ereignisse klein sind. <\/li>\n<\/ul>\n<blockquote><p>Auf den ersten Blick hat der Artikel ein ganz konkretes Thema - eine asynchrone API auf Kafka, aber in diesem Zusammenhang m\u00f6chte ich gleich vieles empfehlen.<br \/>\nErstens, das n\u00e4chste <noindex><a rel=\"nofollow\" href=\"https:\/\/www.highload.ru\/\">HighLoad++<\/a><\/noindex> muss bis November warten, die Petersburger Version wird bereits im April erscheinen, und im Juni werden wir \u00fcber hohe Lasten in Nowosibirsk sprechen.<br \/>\nZweitens, der Autor des Berichts Sergej Zaika ist Mitglied des Programmkomitees unserer neuen Konferenz \u00fcber Wissensmanagement <noindex><a rel=\"nofollow\" href=\"https:\/\/knowledgeconf.ru\/2019\">KnowledgeConf<\/a><\/noindex>. Die Konferenz ist eint\u00e4gig und findet am 26. April statt, aber das Programm ist sehr umfangreich.<br \/>\nAu\u00dferdem wird es im Mai <noindex><a rel=\"nofollow\" href=\"https:\/\/phprussia.ru\/2019\">PHP Russia<\/a><\/noindex> und\u00a0<noindex><a rel=\"nofollow\" href=\"https:\/\/ritfest.ru\/2019\">RIT++<\/a><\/noindex> (mit DevOpsConf im Programm) - dort kann man noch sein Thema vorschlagen, \u00fcber seine Erfahrungen berichten und sich \u00fcber seine gemachten Fehler beschweren.<\/p><\/blockquote>\n<p>Quelle: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/oleg-bunin\/blog\/445424\/\">habr.com<\/a><\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u0427\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0442\u0430\u043a\u0443\u044e \u0431\u043e\u043b\u044c\u0448\u0443\u044e \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044e \u043a\u0430\u043a Lamoda \u0441\u00a0\u043e\u0442\u043b\u0430\u0436\u0435\u043d\u043d\u044b\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u043c \u0438\u00a0\u0434\u0435\u0441\u044f\u0442\u043a\u0430\u043c\u0438 \u0432\u0437\u0430\u0438\u043c\u043e\u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043c\u0435\u043d\u044f\u0442\u044c \u043f\u043e\u0434\u0445\u043e\u0434? \u041c\u043e\u0442\u0438\u0432\u0430\u0446\u0438\u044f \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u043e \u0440\u0430\u0437\u043d\u0430\u044f: \u043e\u0442\u00a0\u0437\u0430\u043a\u043e\u043d\u043e\u0434\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0434\u043e\u00a0\u043f\u0440\u0438\u0441\u0443\u0449\u0435\u0433\u043e \u0432\u0441\u0435\u043c \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0441\u0442\u0430\u043c \u0436\u0435\u043b\u0430\u043d\u0438\u044f \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c. \u041d\u043e\u00a0\u044d\u0442\u043e \u0432\u043e\u0432\u0441\u0435 \u043d\u0435\u00a0\u0437\u043d\u0430\u0447\u0438\u0442, \u0447\u0442\u043e \u043d\u0435\u043b\u044c\u0437\u044f \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c \u043d\u0430\u00a0\u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u0443\u044e \u0432\u044b\u0433\u043e\u0434\u0443. \u0412\u00a0\u0447\u0435\u043c \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e \u043c\u043e\u0436\u043d\u043e \u0432\u044b\u0438\u0433\u0440\u0430\u0442\u044c, \u0435\u0441\u043b\u0438 \u0432\u043d\u0435\u0434\u0440\u0438\u0442\u044c events-driven API \u043d\u0430\u00a0Kafka, \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0435\u0442 \u0421\u0435\u0440\u0433\u0435\u0439 \u0417\u0430\u0438\u043a\u0430 (fewald). \u041f\u0440\u043e \u043d\u0430\u0431\u0438\u0442\u044b\u0435 \u0448\u0438\u0448\u043a\u0438 \u0438\u00a0\u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u0435 \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u044f \u0442\u043e\u0436\u0435 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":22763,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-30778","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.1.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u0427\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0442\u0430\u043a\u0443\u044e \u0431\u043e\u043b\u044c\u0448\u0443\u044e \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044e \u043a\u0430\u043a Lamoda \u0441 \u043e\u0442\u043b\u0430\u0436\u0435\u043d\u043d\u044b\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u043c \u0438 \u0434\u0435\u0441\u044f\u0442\u043a\u0430\u043c\u0438 \u0432\u0437\u0430\u0438\u043c\u043e\u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043c\u0435\u043d\u044f\u0442\u044c \u043f\u043e\u0434\u0445\u043e\u0434?\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/de\/blog\/administrirovanie\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"de_DE\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47\u041e\u043f\u044b\u0442 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 Refund Tool \u0441 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u043c API \u043d\u0430 Kafka | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u0427\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0442\u0430\u043a\u0443\u044e \u0431\u043e\u043b\u044c\u0448\u0443\u044e \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044e \u043a\u0430\u043a Lamoda \u0441 \u043e\u0442\u043b\u0430\u0436\u0435\u043d\u043d\u044b\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u043c \u0438 \u0434\u0435\u0441\u044f\u0442\u043a\u0430\u043c\u0438 \u0432\u0437\u0430\u0438\u043c\u043e\u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043c\u0435\u043d\u044f\u0442\u044c \u043f\u043e\u0434\u0445\u043e\u0434?\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/de\/blog\/administrirovanie\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2019-10-31T18:37:23+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2019-10-31T18:37:23+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47Erfahrungen bei der Entwicklung des Dienstes Refund Tool mit einer asynchronen API auf Kafka | ProHoster","description":"Was kann ein so gro\u00dfes Unternehmen wie Lamoda, das einen verfeinerten Prozess und Dutzende von miteinander verbundenen Diensten hat, dazu bringen, seinen Ansatz erheblich zu \u00e4ndern?","canonical_url":"https:\/\/prohoster.info\/de\/blog\/administrirovanie\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"de_DE","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47\u041e\u043f\u044b\u0442 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 Refund Tool \u0441 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u043c API \u043d\u0430 Kafka | ProHoster","og:description":"\u0427\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0442\u0430\u043a\u0443\u044e \u0431\u043e\u043b\u044c\u0448\u0443\u044e \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044e \u043a\u0430\u043a Lamoda \u0441 \u043e\u0442\u043b\u0430\u0436\u0435\u043d\u043d\u044b\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u043c \u0438 \u0434\u0435\u0441\u044f\u0442\u043a\u0430\u043c\u0438 \u0432\u0437\u0430\u0438\u043c\u043e\u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043c\u0435\u043d\u044f\u0442\u044c \u043f\u043e\u0434\u0445\u043e\u0434?","og:url":"https:\/\/prohoster.info\/de\/blog\/administrirovanie\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2019-10-31T18:37:23+00:00","article:modified_time":"2019-10-31T18:37:23+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"30778","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":"2026-01-21 02:58:19","breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-03-01 03:29:56","updated":"2026-01-21 02:58:19","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/posts\/30778","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/comments?post=30778"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/posts\/30778\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/media\/22763"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/media?parent=30778"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/categories?post=30778"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/tags?post=30778"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}