EventBus und Microservices - a Case Study: Part V
Wenn man meint, ein Konzept für Event-Nachrichten zu haben, sollte man auch irgendwie die Technologie eruieren, die diese Nachrichten zuverlässig zustellt.
Die Artikel dieser Reihe
Einleitung
Fassen wir diesen Artikel einleitend zusammen, was bisher diskutiert wurde:
Transactional Outbox und das Erkennen des Dual Write Problems waren Gegenstand des ersten Artikels.
Die Nachrichtenstruktur von Event-Nachrichten war das zentrale Thema des zweiten Artikels
Transaktionen waren der Gegenstand des dritten Artikels
Im vierten Artikel drehte es sich primär um die Empfängerseite
Was wurde bislang ausgeblendet: wie sollen die Nachrichten eigentlich versendet werden?
Der Enterprise Service Bus
… ein Wiedergänger
Integrationssoftware labeled als Enterprise Service Bus waren einer der Hypes vergangener Dekaden, versprachen Sie doch Lösungen zur Integration verschiedenster Systeme mittels zentralen Integrationslösungen. Diesem Versprechen war auch der Kunde gefolgt - die Erfahrungen jedoch waren ernüchternd.
Einer der gravierendsten Nachteile ist, dass die Nutzung von ESBs aufgrund aufbauorganisatorischer Probleme oftmals zum Scheitern verurteilt war. ESBs und die Entwicklung von Routen innerhalb der ESBs waren kundenseitig oftmals in einer organisatorischen Einheit verankert, diese wird mit jeglicher Art von Integrationsproblem betraut und kann innerhalb kürzester Zeit ihren Aufgaben nicht termingerecht nachkommen; der ESB wird zum Bottleneck. Auch tendieren ESBs dazu, Geschäftslogik anzuziehen, Probleme der Integrationsentwickler mit den Applikationsentwicklern sind vorprogrammiert.
Im DevOps-Zeitalter stellen die Eigenschaften, die einst für die Beliebtheit des ESB sorgten oftmals ein Hindernis dar. Ein ESB stellt alle Integrationen monolithisch bereit. Das mag in der Vergangenheit Vorteile gebracht haben, ist aber der Grund, warum ESB mit DevOps oftmals inkompatibel ist. Das war auch die Erfahrung des Kunden, man war froh, den ESB in nahezu allen Projekten ausgebaut zu haben.
Messaging mittels REST
Messaging und ein asynchrones Kommunikationsmuster werden oft gleichgesetzt, dem ist aber nicht so. Event-Nachrichten können jederzeit auch mittels REST versendet werden.
Ein Problem tritt dann auf, wenn die empfängerseitige Verarbeitung so lange dauert, dass es bspw. zu einem TimeOut kommt oder der Aufrufer schlicht und einfach zu lange blockiert und inperformant wird.
Eine mögliche Lösung wäre das Asynchrone Anforderung-Antwort-Muster:
Auch wenn das auf den ersten Blick recht charmant aussieht, so eignet es sich doch primär für Punkt-zu-Punkt Verbindungen und ist ohne erheblichen Mehraufwand nicht für den Anwendungsfall Publish/Subscribe Messaging geeignet => Vorschlag einstimmig von allen Beteiligten verworfen.
Messaging mittels JMS
… das war beim Kunden bereits im Einsatz doch hatte es sich aus Kundensicht nicht wirklich bewährt. Zum einen setzte der Kunde einer der letzten Versionen von ActiveMQ ein und zögerte mit einem Upgrade zu Artemis, war sich aber durchaus bewusst, dass dieses wohl in der Zukunft unausweichlich würde - sofern man sich nicht für eine andere Technologie entscheiden würde.
Queues, Queues, Queues
Wie ausgeführt hatte der Kunde eine wahre Flut von Queues, kein wirkliches Konzept für die Message-Stores - insbesondere nicht bei Fail-Over Konfigurationen . Dieses führte dann zu bspw. dazu, dass die Nachrichten im willkürlicher Folge beim Empfänger eintrafen, je nachdem, welcher ActiveMQ-Server gerade ausgefallen war.
Es löste wahre Begeisterungsstürme bei den Admins aus, wenn wieder ein Ticket hereinkam mit einem Inhalt wie: “Wir haben bereits Nachricht C erhalten, wo sind eigenlich Nachricht A und Nachricht B?“. Logischweise waren oftmals die Nachrichten A und B notwendige Vorbedingungen für die korrekte Abarbeitung von Nachricht C.
Und dann ging das fröhliche Rätselraten los:
Wo genau sind die Nachrichten verlustig gegangen? Das Spaghetti-Konzept der Queue-Flut unterstützte natürlich die Suche optimal und
Wie finde ich die Nachricht eigentlich? Der Kunde nutzte die nur leicht angepasste KahaDB Konfiguration ohne wirkliche Master/Slave-Konfiguration für horizontales ScaleOut und/oder FailOver. Die Suche geriet zu einem fröhlichen Try/Error in den diversen Queues.
Diese Notwendigkeit und die generelle Unzufriedenheit führte dazu, dass einige der Projektbeteiligten Messaging insgesamt in Frage stellten (aber auch kein alternatives, wirklich tragfähiges Konzept offerieren konnten).
Alles genannte war jedoch kein originäres Problem der eingesetzten Middleware, es war vielmehr das Ergebnis eines ziemlich verkorksten Ansatzes.
Bei Betrachtung des Vorschlages aus dem vorherigen Kapitel wurde ersichtlich, dass:
Für synchrone Command-Nachrichten REST der deutlich bessere Ansatz als eine Request/Reply-Queue Kombination zwischen den betroffenen Systemen ist.
Im Falle von Event-Nachrichten das Nutzen eines (Fail-Over konfigurierten) Topics die Komplexität und den administrativen Aufwand wesentlich reduzieren konnte.
Es durchaus Sinn machen könnte, die Message Stores in Richtung eines Shared SQL-Stores zu konfigurieren (hier ein Beispiel mit C3PO-Cache und Postgres). Hierbei ist natürlich die Performance zu beachten.
Topics? Die Shared Durable Subscriptions
Ein kleines Detail sollte allerdings noch die Aufmerksamkeit auf sich ziehen: Shared Durable Subscriptions.
Since JMS 1.1 doesn’t explicitly allow shared durable subscriptions, you can’t horizontally scale applications using createDurableSubscriber
on the same topic with the same client identifier and subscription name
Es war unmittelbar einsichtig, dass Event-Messages versendet über ein Topic eigentlich nur mit Durable Subscriber funktionieren; es liegt in der Natur der Sache, dass temporär nicht erreichbare Empfänger die Event-Nachricht auf jeden Fall erhalten müssen.
Durable Subscriptions ermöglichen genau diesen Use Case, ein Empfänger registriert sich mittels einer ID an der Messaging-Middleware und erhalten die Nachrichten dann einfach zugestellt, wenn sie (wieder) aktiv sind. Soweit, so gut. Die Empfänger ID wird allerdings dann zu einem Problem, wenn die Empfänger horizontal skalieren. Dann nämlich wird die EmpfängerID zu einem Problem, ein Empfänger kann sich nämlich nur einmal mit einer ID registrieren. Jetzt kann man natürlich argumentieren, dann nehmen wir bei der horizontalen Skalierung einfach neue IDs (UUIDs bspw.) Schöne Lösungen sehen anders aus, zumal dann das Problem auftaucht, dass alle Abonnenten die Nachricht bekommen - auch wenn sie eigentlich dasselbe tun. was man braucht ist dann eine Idempotenz die über alle Gruppen von “identischen“ Empfängern erkennt das die Nachricht innerhalb der Gruppe bereits zugestellt wurde. Auch nicht schön1.
Genau aus diesem Grund existieren Shareable Durable Subscriptions - ab JMS 2.0. Aufgrund der eingesetzten ActiveMQ Version (6.x) konnte nur JMS 1.1 genutzt werden.
Kundenseitig wurde eingewendet, dass man am eigentlichen Hot Spot “Billing“ aktuell nicht horizontal skaliert (und des Kunden Wunsch ist sein Himmelreich - aus dem Erfahrungschatz langjähriger Consultingtätigkeit), also sei es drum.
Auch die Anmerkung, dass Apache Kafka dieses recht elegant über Consumer Groups erreicht (und eine Reihe weiterer Vorteile bietet) fand keinen Widerhall.
Messaging mittels Apache Kafka
Consumer groups give Kafka the flexibility to have the advantages of both message queuing and publish-subscribe models. Kafka consumers belonging to the same consumer group share a group id. The consumers in a group then divides the topic partitions as fairly amongst themselves as possible by establishing that each partition is only consumed by a single consumer from the group.
— cloudera
… so fancy, new and sexy … and a new world (for the customer).
Natürlich wurde über Apache Kafka diskutiert und die Vorteile einer Lösung auf Basis dessen durchaus gesehen; insbesondere wurde Kafka als zukunftssicher angesehen; zukunftssicherer inbesondere in Hinblick auf die Echtzeitanalysen über die Streams. Die Ausfallsicherheit und die Performance von Kafka haben ebenfall beeindruckt.
Aber:
beim Kunden lag wenig bis kein Know-How über Kafka vor, das gilt sowohl betriebs- als auch entwicklungsseitig.
das dargestellte Lösungsszenario erschien komplex genug, aber problemadäquat und in mittelfristiger Reichweite
der Feasability-Prototyp mit Kafka funktionierte hervorragend, aber niemand wollte mit Blut unterschreiben, dass der Ansatz in allen Teams und über alle Zulieferer ohne unnötige Zeitverluste in der Form ausrollbar sein wird.
Damit war Kafka nicht vom Tisch, eine Entscheidung über den Wechsel der Messaging-Plattform wurde allerdings in die Zukunft verschoben. Schade eigentlich, aber nachvollziehbar.
Zusammenfassung
Man sagt, dass Politik die Kunst des Möglichen ist (zugeschrieben Bismarck), machmal ist das Consulting es auch. Machbar ist im Kontext des konkreten Projektes die Ertüchtigung von ActiveMQ und eventuell der Umstieg auf Artemis in der näheren Zukunft. Nicht machbar hingegen ist im Projektkontext der umgehende Wechsel auf Apache Kafka. So sind die Dinge eben.
Nehmen wir dieses als gegeben hin und wenden und weiteren Aspekten zu. Nachdem das ursprüngliche Problem nunmehr als gelöst erschien sollte der Fokus weiter aufgespannt werden, hin zu einem generelleren Ansatz mit mehr Beteiligten als lediglich dem Produktionsservice und dem Abrechnungsservice.
Diesem Aspekt widmet sich der Folgeartikel.
Bleiben Sie mir gewogen.
Für einen Lösungshinweis:
https://stackoverflow.com/questions/58676577/loading-balancing-consumers-on-topics-on-activemq-artemis/58683482#58683482