Fehlerbehandlung im verteilten System

8

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 #6Fehler nur aufgrund des Netzwerks auftritt: Der Job wurde korrekt ausgeführt, Aweiß 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 #1wir darauf hinweisen, Adass die Arbeitseinheit, dh die Änderung , gleich beginnt
  • Nur Bdarf dieser Status aufgehoben werden.
  • Akann Bjederzeit 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?

igor
quelle
Das ist eine alte Frage. Haben Sie eine gute Lösung gefunden? ... Wenn ja, können Sie es teilen?
Svidgen

Antworten:

2

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".

J_H
quelle
1

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.

  • A protokolliert, was B mit einer Warteschlange tun soll
  • B zieht aus der Warteschlange von A und erledigt die Arbeit
  • B protokolliert die Aktionen in einer Warteschlange
  • A zieht aus der Warteschlange von B, um zu erfahren, was das Jobergebnis war

(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:

  • Mindestens einmal: B schreibt nur fest, an welchem ​​Punkt in der Warteschlange es nach Abschluss einer Aktion angekommen ist. Es besteht die Gefahr, dass Aktionen zweimal ausgeführt werden. Wenn Sie Aktionen so entwerfen, dass sie idempotent sind, können Sie mit diesem Ansatz ein genau einmaliges Verhalten erzielen. (Ich benutze Kafka für dieses Szenario.)
  • Höchstens einmal: B verbraucht jedes Warteschlangenelement nur einmal. Wenn es während der Ausführung abstürzt, wird das Element niemals ausgeführt.

Vorteile dieses Ansatzes:

  • Warteschlangenintensive Dienste müssen nicht aktiv sein, damit ein Warteschlangen-Push ausgeführt werden kann. Dies bedeutet, dass Sie B neu starten können, während A arbeitet, oder A neu starten können, während B arbeitet. Das redundante Hosting von Hintergrunddiensten ist nur erforderlich, um die Gesamtantwortzeit und nicht den zuverlässigen Betrieb sicherzustellen.
  • Das Tempo beim Ziehen von Warteschlangenelementen kann vom Verbraucher gesteuert werden, wodurch Sie Lastspitzen in der Warteschlange vorübergehend puffern können.
Joeri Sebrechts
quelle