Die Artikel dieser Reihe
Einleitung
Im zweiten Teil der Artikelserie ging es um Events, dieser Teil der Artikelserie beschreibt den Teil der Diskussion in dem kontrovers und hitzig diskutiert wurde: Transaktionen, ACID-Properties, CAP-Theorem usw.
Der Leser möge sich einfach folgendes vor Augen halten:
Die Diskussion fand mit verschiedenen Teilnehmern aus verschiedenen Bereichen des Unternehmens statt, da waren:
der Betrieb, also diejenigen, die unter dem Fehler bisher am meisten zu leiden hatten; deren DNA primär Aspekte wie transaktionale Absicherung, Datenkonsistenz und ähnliches beinhaltete.
Weniges bringt DB-Admins so schnell und so hoch auf die Palme wenn man kontrovers über Transaktionen und Konsistenz diskutiert.Die Vertreter des Empfängersystems, deren feste Überzeugung war: Bekommen wir doppelte Nachrichten, dann seht eben zu, dass ihr das abstellt.
Die Vertreter des sendenden Systems die irgenwie den schwarzen Peter aber keine Lösung hatten
Der Vorstand, den die Technik wenig interessierte und dessen Kerninteresse die schnelle Behebung des Problems war.
Der folgende Teil spiegelt die diskutierten Ideen wieder …
Transaktionales Senden von Nachrichten
Eine Transaktion ist das Überführen eines oder mehrere Systeme von einem konsistenten (widerspruchfreien) Zustand in einen folgenden konsistenten Zustand.
—- unbekannter Autor
Sie erinnern sich, das doppelte Versenden der Nachrichten?
Doppelte Nachrichten, für die Seite des Senders nicht wirklich ein Problem; andere - bspw. die Empfänger sehen das anders.
Natürlich war der erste Gedanke: das müssen wir verhindern - aber wie? Klare Sache: Transaktionen! Leicht gesagt …
Your Coffee Shop Doesn’t Use Two-Phase Commit
… so die Überschrift eines hervorragenden Artikels von Gregor Hohpe (ja, der mit den EAI-Pattern).
Die essentielle Frage ist doch in diesem Kontext: Wie genau bringe ich den bspw. einen/mehrere REST-Call(s), das Versenden einer / mehrerer Event-Nachrichten und einen/mehrere Datenbank-Operation(en) gemeinsam in einer Transaktionalen Klammer unter?
Hierbei ist es schon bewusst, dass es sich um unterschiedliche Ebenen handelt (technische Aufrufe und am Geschäftsprozess orientierte Koordination von Einzelaktionen), aber es gibt IMHO doch eine ganze Reihe von Gemeinsamkeiten.
Hophe unterteilt in seinem Artikel verschiedene Möglichkeiten mit Fehlern in diesem Kontext umzugehen:
Do Nothing
Aus leicht nachvollziehbaren Gründen hier nicht die geeignete Strategie in diesem Kontext. Hätte man die Fehler ignorieren könne, wäre es nie zu einer Beauftragung meiner Person gekommen.Retry
Bei temporären Ausfällen sicherlich eine geeignete Strategie und urprünglich war auch das genau der Ansatz. Behalten wir das mal im Auge.Compensating Action
Analog zum Workflow-Pattern “RecoveryAction” von van der Aalst innerhalb dessen Kompensationen für die der fehlgeschlagenen Transaktion vorangegangenen Operationen implementiert werden. Passt das in diesem Kontext wirklich?Transaction coordinator
Sowas ist doch irgendwie bekannt aus dem Kontext XA und JTA …
JTA, XA und Two-Phase-Commit — transaction coordinator?
The Java™ Transaction API (JTA) allows applications to perform distributed transactions, that is, transactions that access and update data on two or more networked computer resources. The JTA specifies standard Java interfaces between a transaction manager and the parties involved in a distributed transaction system: the application, the application server, and the resource manager that controls access to the shared resources affected by the transactions.
Das Ziel des Two-Phase-Commit-Protokolls ist es, eine verteilte Transaktion zu koordinieren und dafür zu sorgen, dass entweder alle beteiligten Systeme die Transaktion festschreiben oder alle in den Zustand vor Beginn der Transaktion zurückkehren. Hört sich doch gut an, oder?
Da war doch was, @Transactional an der Methode und schon funktioniert alles - sofern XA von allen Beteiligten unterstützt wird, doch wird es das?
Gibt es da nicht Frameworks wie Atomicos oder Nayarana die mir dabei helfen?
Wie sieht das eigentlich mit der Performance aus?
XA kenne ich noch aus den Zeiten von CORBA (stimmt in meinem Fall wirklich, das war die Zeit, in der es noch keine Mobiltelefone gab, das Internet sowieso nicht - aber schon Farbfernsehen), XA war schon immer cool (stimmt nicht).
Auch wenn man mit den oben genannten Frameworks einer Lösung nahekam, wer schon einmal mit RessourceAdaptern im JavaEE Umfeld (konkret JavaConnectorArchitecture) arbeiten musste weiß, es ist tricky. Am Ende des Tages bleiben viele Fragen und Ungewissheiten. Man sollte nicht vergessen, dass die Services bereits implementiert waren und jede Änderung hier quasi eine Operation am offenen Herzen ist.
Schlussendlich wurde diese Idee aus naheliegenden Gründen verworfen, niemand wollte ein entsprechendes Framework einbauen und XA ist zweifelsohne eine robuste Technologie aber irgendwie musste das Ganze doch einfacher zu lösen sein.
Folgen wir Gregor Hophe:
Using such a two-phase-commit approach would certainly kill a coffee shop’s business because it would dramatically decrease the number of customers the shop could serve in a certain time interval. Although a two-phase commit can make life a lot simpler, it can also hurt the free flow of messages (and therefore scalability) because it requires the allocation of a stateful transaction resource across the flow of multiple, asynchronous actions.
Sagas — compensating action?
“Long lived transactions (LLTs) hold on to database resources for relatively long periods of time, significantly delaying the termination of shorter and more common transactions. To alleviate these problems we propose the notion of a saga. A Long Lived Transaction is a saga if it can be written as a sequence of transactions that can be interleaved with other transactions. The database management system guarantees that either all the transactions in a saga are successfully completed or compensating transactions are run to amend a partial execution”
--- Garcia-Molina und Salem (“Erfinder” der Sagas)
Wahrscheinlich hat jeder der sich mit Software-Architektur und Microservices beschäftigt hat schon einmal das Buch von Chris Richardson “Microservices Pattern“ in den Händen gehalten. Und da kann man lesen:
“Wenn jeder Microservice seine eigene Datenhaltung hat und 2PC keine Option ist, dann ziehe das Saga-Pattern in Betracht.”
Weiterhin:
“Dieses Pattern hat die folgenden Vorteile: Es ermöglicht einer Applikation die Datenkonsistenz über eine Vielzahl von Services ohne verteilte Transaktionen zu benutzen”
Also wenn sich das nicht vielversprechend anhört … Heureka, die Lösung des Problems ist nahe …
Was für ein schöner Gedanke: weil die Datenbanktransaktion fehlschlug muss eine entsprechende Nachricht an die Umsysteme geschickt werden - “sorry, Kommando zurück“. Dann die Hasenpfote reiben und ein Stoßgebet ausstoßen in der Hoffnung, dass keines der Systeme seinerseits bereits Folgeaktionen initiiert hat.
Also das ist dann wirklich fehlerträchtig, never ever.
Und siehe da, man sollte das Pattern schon vollständig lesen:
Es gilt aber die folgenden Aspekte zu addressieren: Um als zuverlässig zu gelten muss ein Service Änderungen der Datenbank und das Versenden von Nachrichten gemeinsam durchführen. Traditionelle Mechanismen einer verteilten Transaktion welche Datenbank und Messaging umfasst können nicht genutzt werden.
Das beschreibt doch das zugrundeliegende Problem, wir hatten uns in der Diskussion quasi im Kreis gedreht: Die angedachte Lösung des Problems hatte als Vorbedingung genau die Lösung ebendieses Problems.
Idempotente Empfänger — retry (2 success)
Am Ende des Tages stellte sich doch nur noch die folgende Frage:
Kann man mit doppelten Nachrichten leben?
Die Antwort darauf war und ist: ja!
"Working with distributed architectures makes it difficult to ensure exactly once delivery of messages and you should expect to process a small number of duplicate events. To handle that, event-driven architectures should be designed to handle duplicates without side effects. An application is defined "idempotent" if processing duplicate inputs doesn’t change the final result of the application. You can achieve idempotency in different ways, the most common being comparing timestamps or storing an index of processed events." —- Amazon
Wäre es nicht viel einfacher, wenn der Empfänger einer Sendung unmittelbar feststellen könnte, dass er genau diese Nachricht bereits erhalten hat und diese dann in der Folge ignoriert?
Es liegt auf der Hand, dass diese Lösung die einfachste Lösung ist. Alle Beteiligten nickten und auch die Empfängerseite musste letztendlich zustimmen. Um diesen Teil geht es in dem folgenden Artikel.
Bleiben Sie mir gewogen.