Dies ist die übliche Folge von zwei verteilten Komponenten in unserer Java-Anwendung:
1 A sends request to B
2 B starts some job J in parallel thread
3 B returns response to A
4 A accepts response
5 Job finishes after some time
6 Job sends information to A
7 A receives response from a Job and updates
Dies ist das ideale Szenario, vorausgesetzt, alles funktioniert. Natürlich ist das wirkliche Leben voller Misserfolge. Einer der schlimmsten Fälle kann beispielsweise sein, dass ein #6
Fehler nur aufgrund des Netzwerks auftritt: Der Job wurde korrekt ausgeführt, A
weiß jedoch nichts darüber.
Ich suche nach einem einfachen Ansatz zum Verwalten von Fehlern in diesem System. Beachten Sie, dass wir viele Komponenten haben. Daher ist es nicht sinnvoll, sie alle nur aufgrund der Fehlerbehandlung zu gruppieren. Als nächstes habe ich die Verwendung von verteiltem Speicher / Repo, der aus demselben Grund erneut auf jeder Komponente installiert werden würde, aufgegeben.
Meine Gedanken gehen in die Richtung, einen absoluten Zustand auf einem B zu haben und niemals einen anhaltenden Zustand auf einem A
. Dies bedeutet Folgendes:
- bevor
#1
wir darauf hinweisen,A
dass die Arbeitseinheit, dh die Änderung , gleich beginnt - Nur
B
darf dieser Status aufgehoben werden. A
kannB
jederzeit Informationen abrufen , um den Status zu aktualisieren.- Es kann keine neue Änderung an derselben Einheit aufgerufen werden
A
.
Was denken Sie? Gibt es eine einfache Möglichkeit, die Fehler in einem solchen System zu zähmen?
Antworten:
Das Anhängen an ein dauerhaftes Protokoll bei A sollte ausreichen. Dies bewältigt Neustarts und Netzwerkpartitionen, um eine eventuelle Konsistenz zu erreichen oder um einen Bruch zu signalisieren, der eine solche Konvergenz verhindert. Bei einem amortisierten Gruppen-Commit kann es weniger als einen einzigen Schreibvorgang dauern, bis ein Protokolleintrag erhalten bleibt.
Sie haben vorgeschlagen, B für das Aufheben der Markierung verantwortlich zu machen. Ich stimme dir nicht zu. Nur A wird auf neue Arbeiten aufmerksam, und nur A sollte dafür verantwortlich sein, diese zu verfolgen und Fehler wie Zeitüberschreitungen zu melden. B sendet idempotente Nachrichten an A, und A aktualisiert den Status und fragt ihn bei Bedarf in Intervallen erneut ab.
In Schritt 0 wird A auf eine neue Anforderung aufmerksam und protokolliert sie. Dies stellt eine Verpflichtung dar, die A später innerhalb einer bestimmten Frist erfüllen muss. A führt die nachfolgenden Schritte kontinuierlich durch und wiederholt sie, bis A erfährt, dass die Anforderungsverarbeitung abgeschlossen ist.
Einige Anfragen sind länger als andere. Schätzungen der Verarbeitungszeit werden auf A und B verfügbar sein, möglicherweise überarbeitet, wenn die Verarbeitung fortgesetzt wird. Solche Schätzungen können an A zurückgemeldet werden, so dass es selten zu falsch positiven Zeitüberschreitungen kommt. Stellen Sie sich das als eine Keep-Alive-Nachricht vor, die besagt: "Immer noch arbeiten, immer noch arbeiten".
quelle
Nehmen Sie eine Pull-Strategie anstelle einer Push-Strategie an. Lassen Sie jedes Teil Änderungen von den anderen übernehmen und aktualisieren Sie seine eigenen Datensätze.
(Ich verwende die Wortwarteschlange, aber Sie können das Protokoll oder das Thema ersetzen.)
Sie können die Warteschlange entweder in die Dienste einbinden oder einen separaten Nachrichtenbroker einrichten. Eine in einen Dienst eingebaute Implementierung kann so einfach sein wie
GET /jobrequests?from=<timestamp>
(wobei B den Zeitstempel der zuletzt verarbeiteten Jobanforderung verfolgt).Ein schwieriger Teil einer solchen Architektur besteht darin, sich für mindestens einmalige und höchstens einmalige Semantik zu entscheiden. Konkret: Was sollte passieren, wenn B ein Element aus der Warteschlange zieht und dann während der Ausführung abstürzt? Es gibt zwei Möglichkeiten, und welche am besten geeignet ist, hängt von Ihrem Anwendungsfall ab:
Vorteile dieses Ansatzes:
quelle