Fehlerbehandlung - AWS Flow Framework für Java

Die vorliegende Übersetzung wurde maschinell erstellt. Im Falle eines Konflikts oder eines Widerspruchs zwischen dieser übersetzten Fassung und der englischen Fassung (einschließlich infolge von Verzögerungen bei der Übersetzung) ist die englische Fassung maßgeblich.

Fehlerbehandlung

Das Konstrukt try/catch/finally in Java vereinfacht die Fehlerbehandlung und wird sehr häufig eingesetzt. Es ermöglicht die Verknüpfung von Fehler-Handlern mit einem Codeblock. Dies geschieht intern durch die Anhäufung von Metadaten zu den Fehler-Handlern auf dem Aufruf-Stack. Wird eine Ausnahme ausgelöst, sucht die Laufzeit beim Aufruf-Stack nach einem zugehörigen Fehler-Handler und ruft diesen auf. Wird kein passender gefunden, wird die Ausnahme an die Aufruf-Kette weitergegeben.

Dies funktioniert gut bei synchronem Code. Die Fehlerbehandlung in asynchronen und verteilten Programmen stellt jedoch einige Herausforderungen dar. Da ein asynchroner Aufruf sofort zurückgegeben wird, befindet sich der Aufrufer nicht auf dem Aufruf-Stack, wenn der asynchrone Code ausgeführt wird. Das bedeutet, dass nicht behandelte Ausnahmen in einem asynchronen Code vom Aufrufer nicht in der üblichen Weise behandelt werden können. In der Regel werden Ausnahmen, die in einem asynchronen Code auftreten, behandelt, indem der Fehlerstatus an ein Callback übergeben wird, das an die asynchrone Methode übermittelt wird. Alternativ erfolgt bei Verwendung von Future<?> die Meldung eines Fehlers, wenn Sie versuchen, darauf zuzugreifen. Dies ist keineswegs ideal, da dem Code, der die Ausnahme empfängt (das Callback oder den Code, das bzw. der Future<?> verwendet), der Kontext des ursprünglichen Aufrufs fehlt und er die Ausnahme möglicherweise nicht adäquat behandeln kann. Darüber hinaus kann es bei einem verteilten asynchronen System, bei dem mehrere Komponenten parallel ausgeführt werden, gleichzeitig zu mehreren Fehlern kommen. Dabei kann es sich um unterschiedliche Fehlertypen von unterschiedlichem Schweregrad handeln, die alle entsprechend behandelt werden müssen.

Das Bereinigen einer Ressource nach einem asynchronen Aufruf ist ebenfalls schwierig. Im Gegensatz zum synchronen Code können Sie für das Bereinigen von Ressourcen nicht das Konstrukt "try/catch/finally" im aufrufenden Code verwenden, da die Vorgänge, die im Try-Block initiiert wurden, möglicherweise noch weiter fortgeführt werden, wenn der Finally-Block ausgeführt wird.

Das Framework stellt einen Mechanismus bereit, durch den die Fehlerbehandlung bei einem verteilten asynchronen Code ähnlich einfach wird wie die Java-Fehlerbehandlung in Form von "try/catch/finally".

ImageProcessingActivitiesClient activitiesClient = new ImageProcessingActivitiesClientImpl(); public void createThumbnail(final String webPageUrl) { new TryCatchFinally() { @Override protected void doTry() throws Throwable { List<String> images = getImageUrls(webPageUrl); for (String image: images) { Promise<String> localImage = activitiesClient.downloadImage(image); Promise<String> thumbnailFile = activitiesClient.createThumbnail(localImage); activitiesClient.uploadImage(thumbnailFile); } } @Override protected void doCatch(Throwable e) throws Throwable { // Handle exception and rethrow failures LoggingActivitiesClient logClient = new LoggingActivitiesClientImpl(); logClient.reportError(e); throw new RuntimeException("Failed to process images", e); } @Override protected void doFinally() throws Throwable { activitiesClient.cleanUp(); } }; }

Die TryCatchFinally-Klasse und deren Varianten TryFinally und TryCatch funktionieren ähnlich wie Javas try/catch/finally. Mit dieser Lösung können Sie Ausnahme-Handler mit Blöcken von Workflow-Code verknüpfen, die als asynchrone und Remote-Aufgaben ausgeführt werden können. Die doTry()-Methode entspricht logisch dem try-Block. Das Framework führt den Code automatisch in doTry() aus. Eine Liste von Promise-Objekten kann an den Konstruktor von TryCatchFinally übergeben werden. Die doTry-Methode wird ausgeführt, wenn alle Promise -Objekte, die an den Konstruktor übergeben wurden, bereit sind. Wird eine Ausnahme von einem Code ausgelöst, der asynchron innerhalb von doTry() aufgerufen wurde, werden alle Vorgänge in doTry() abgebrochen und doCatch() aufgerufen, um die Ausnahme zu behandeln. Wenn beispielsweise in der obigen Auflistung downloadImage eine Ausnahme auslöst, dann werden createThumbnail und uploadImage abgebrochen. Wenn alle asynchronen Vorgänge beendet wurden (abgeschlossen, fehlgeschlagen oder abgebrochen), wird abschließend doFinally() aufgerufen. Es kann zum Bereinigen von Ressourcen verwendet werden. Sie können diese Klassen auch gemäß Ihren Anforderungen verschachteln.

Wenn eine Ausnahme in doCatch() gemeldet wird, stellt das Framework einen vollständigen logischen Aufruf-Stack mit asynchronen und Remote-Aufrufen bereit. Dies kann beim Debuggen nützlich sein, insbesondere bei asynchronen Methoden, die andere asynchrone Methoden aufrufen. Eine Ausnahme von downloadImage führt beispielsweise zu einer Ausnahme wie der folgenden:

RuntimeException: error downloading image at downloadImage(Main.java:35) at ---continuation---.(repeated:1) at errorHandlingAsync$1.doTry(Main.java:24) at ---continuation---.(repeated:1) …

TryCatchFinally-Semantik

Die Hinrichtung einesAWS Flow Frameworkfür Java-Programm kann als Baumstruktur gleichzeitig ausgeführter Verzweigungen visualisiert werden. Durch den Aufruf einer asynchronen Methode, einer Aktivität oder TryCatchFinally wird eine neue Verzweigung in dieser Baumstruktur der Ausführung angelegt. Der Bildverarbeitungs-Workflow beispielsweise ist in Form einer Baumstruktur auf folgender Abbildung zu sehen.

Baumstruktur der asynchronen Ausführung

Ein Fehler in einer Verzweigung der Ausführung führt zu einer Entladung der Verzweigung, genau wie eine Ausnahme die Entladung eines Aufruf-Stacks in einem Java-Programm verursacht. Dieser Vorgang setzt sich fort, bis entweder der Fehler behandelt oder der Stamm erreicht ist. In diesem Fall wird die Workflow-Ausführung beendet.

Das Framework meldet Fehler, die bei der Verarbeitung von Aufgaben auftreten, als Ausnahmen. Es verknüpft die Ausnahme-Handler (doCatch()-Methoden), die in TryCatchFinally definiert sind, mit allen Aufgaben, die vom Code im entsprechenden doTry() erstellt wurden. Schlägt eine Aufgabe fehl - beispielsweise aufgrund einer Zeitüberschreitung oder einer nicht behandelten Ausnahme -, wird die entsprechende Ausnahme ausgelöst und die zugehörigedoCatch()wird aufgerufen, um damit umzugehen. Damit dies erreicht wird, arbeitet das Framework mit Amazon SWF zusammen, um Remote-Fehler weitergeben und diese als Ausnahmen im Kontext des Aufrufers wieder aufleben zu lassen.

Abbruch

Tritt eine Ausnahme im asynchronen Code auf, springt das Steuerelement direkt zum catch-Block und überspringt den verbleibenden Code im try-Block. z. B.:

try { a(); b(); c(); } catch (Exception e) { e.printStackTrace(); }

Bei diesem Code wird, wenn b() eine Ausnahme auslöst, c() niemals aufgerufen. Vergleichen Sie dies mit einem Workflow:

new TryCatch() { @Override protected void doTry() throws Throwable { activityA(); activityB(); activityC(); } @Override protected void doCatch(Throwable e) throws Throwable { e.printStackTrace(); } };

Hier werden Aufrufe von activityA, activityB und activityC erfolgreich zurückgegeben und führen zur Erstellung dreier Aufgaben, die asynchron ausgeführt werden. Angenommen, die Aufgabe für activityB verursacht zu einem späteren Zeitpunkt einen Fehler. Der Fehler wird von Amazon SWF im Verlauf protokolliert. Aus diesem Grund versucht das Framework zunächst alle anderen Aufgaben abzubrechen, die aus dem Bereich desselben doTry() stammen. In diesem Fall sind das activityAund activityC. Nach Beendigung aller Aufgaben (durch Abbrechen, Fehlschlagen oder erfolgreichem Abschließen), wird die entsprechende doCatch()-Methode aufgerufen, um den Fehler zu behandeln.

Im Gegensatz zum synchronen Beispiel, bei dem c() niemals ausgeführt wurde, wurde activityC hier aufgerufen. Zudem wurde eine Aufgabe für die Ausführung eingeplant. Deshalb versucht das Framework einen Abbruch, für dessen Erfolg es aber keine Garantie gibt. Der Abbruch kann nicht garantiert werden, da die Aktivität möglicherweise bereits abgeschlossen ist, die Abbruchanforderung ignoriert oder fehlschlägt. Das Framework garantiert jedoch, dass doCatch() nur aufgerufen wird, wenn alle Aufgaben, die über das entsprechende doTry() gestartet wurden, abgeschlossen sind. Es garantiert zudem, dass doFinally() nur aufgerufen wird, wenn alle Aufgaben, die vom doTry()- und doCatch()-Block gestartet wurden, abgeschlossen sind. Wenn beispielsweise die Aktivitäten im obigen Beispiel voneinander abhängen, sagen SieactivityBhängt vonactivityAundactivityCaufactivityB, dann die Stornierung vonactivityCwird sofort sein, da es erst in Amazon SWF geplant istactivityBSchließt ab:

new TryCatch() { @Override protected void doTry() throws Throwable { Promise<Void> a = activityA(); Promise<Void> b = activityB(a); activityC(b); } @Override protected void doCatch(Throwable e) throws Throwable { e.printStackTrace(); } };

Aktivitäts-Heartbeat

DieAWS Flow FrameworkDer kooperative Abbruchmechanismus von Java ermöglicht den ordnungsgemäßen Abbruch übertragener Aktivitätsaufgaben. Wird der Abbruch ausgelöst, werden blockierte Aufgaben oder Aufgaben, die darauf warten, zu einem Worker zugewiesen zu werden, automatisch abgebrochen. Wenn eine Aufgabe aber bereits einem Worker zugewiesen wurde, fordert das Framework den Abbruch der Aktivität an. Ihre Aktivitätsimplementierung muss diese Abbruchanforderungen explizit behandeln können. Dies geschieht durch das Übermitteln von Heartbeats Ihrer Aktivität.

Durch das Senden von Heartbeats ist die Aktivitätsimplementierung in der Lage, den Fortschritt einer andauernden Aufgabe zu melden. Dies unterstützt die Überwachung und ermöglicht der Aktivität zu prüfen, ob Abbruchanforderungen vorliegen. Die recordActivityHeartbeat-Methode löst bei Anforderung eines Abbruchs eine CancellationException aus. Die Aktivitätsimplementierung kann diese Ausnahme abfangen und auf die Abbruchanforderung reagieren oder die Anforderung durch "Verschlucken" der Ausnahme ignorieren. Um der Abbruchanforderung Rechnung zu tragen, sollte die Aktivität die gewünschte Bereinigung vornehmen, sofern erforderlich, und dann CancellationException erneut auslösen. Wird diese Ausnahme von einer Aktivitätsimplementierung ausgelöst, erfasst das Framework, dass die Aktivitätsaufgabe im abgebrochenen Status beendet wurde.

Das folgende Beispiel zeigt eine Aufgabe, bei der Bilder heruntergeladen und verarbeitet werden. Es kommt nach jeder Verarbeitung eines Bilds zu einem Heartbeat. Wird ein Abbruch gefordert, wird bereinigt und die Ausnahme zur Bestätigung des Abbruchs erneut ausgelöst.

@Override public void processImages(List<String> urls) { int imageCounter = 0; for (String url: urls) { imageCounter++; Image image = download(url); process(image); try { ActivityExecutionContext context = contextProvider.getActivityExecutionContext(); context.recordActivityHeartbeat(Integer.toString(imageCounter)); } catch(CancellationException ex) { cleanDownloadFolder(); throw ex; } } }

Das Senden von Aktivitäts-Heartbeats ist nicht erforderlich, wird aber empfohlen, wenn die Ausführung der Aktivität lange dauert oder dabei kostenintensive Operationen ausgeführt werden, die im Falle eines Fehlers abgebrochen werden sollten. Sie sollten heartbeatActivityTask periodisch von der Aktivitätsimplementierung aus aufrufen.

Kommt es bei der Ausführung der Aktivität zu einer Zeitüberschreitung, wird die ActivityTaskTimedOutException ausgelöst und getDetails auf dem Ausnahmeobjekt gibt für die entsprechende Aktivitätsaufgabe die Daten zurück, die an den letzten erfolgreichen Aufruf von heartbeatActivityTask übergeben wurden. Die Workflow-Implementierung kann anhand dieser Informationen feststellen, wie weit die Ausführung fortgeschritten war, ehe es zu einer Zeitüberschreitung bei der Aktivitätsaufgabe kam.

Anmerkung

Zu viele Heartbeats werden nicht angezeigt, da Amazon SWF möglicherweise Heartbeat-Anforderungen drosselt. Sehen Sie dasAmazon Simple Workflow Service Entwicklerhandbuchfür Limits von Amazon SWF.

Explizites Abbrechen einer Aufgabe

Abgesehen von Fehlerbedingungen gibt es noch andere Fälle, in denen eine Aufgabe explizit abzubrechen ist. So muss beispielsweise eine Aktivität zur Verarbeitung von Zahlungen mit der Kreditkarte abgebrochen werden, wenn der Benutzer den Auftrag storniert. Das Framework ermöglicht das explizite Abbrechen von Aufgaben, die mit TryCatchFinally erstellt wurden. Im folgenden Beispiel wird die Zahlungsaufgabe abgebrochen, wenn während der Verarbeitung der Zahlung ein Signal empfangen wird.

public class OrderProcessorImpl implements OrderProcessor { private PaymentProcessorClientFactory factory = new PaymentProcessorClientFactoryImpl(); boolean processingPayment = false; private TryCatchFinally paymentTask = null; @Override public void processOrder(int orderId, final float amount) { paymentTask = new TryCatchFinally() { @Override protected void doTry() throws Throwable { processingPayment = true; PaymentProcessorClient paymentClient = factory.getClient(); paymentClient.processPayment(amount); } @Override protected void doCatch(Throwable e) throws Throwable { if (e instanceof CancellationException) { paymentClient.log("Payment canceled."); } else { throw e; } } @Override protected void doFinally() throws Throwable { processingPayment = false; } }; } @Override public void cancelPayment() { if (processingPayment) { paymentTask.cancel(null); } } }

Empfangen von Benachrichtigungen über abgebrochene Aufgaben

Wird eine Aufgabe im abgebrochenen Status beendet, informiert das Framework die Workflow-Logik durch Auslösen einer CancellationException. Wenn eine Aktivität im abgebrochenen Status beendet wird, wird ein Datensatz zum Verlauf hinzugefügt und das Framework ruft das erforderliche doCatch() mit einer CancellationException auf. Wie im vorherigen Beispiel gezeigt, empfängt der Workflow eine CancellationException, wenn die Aufgabe der Zahlungsverarbeitung abgebrochen wird.

Eine unbehandelte CancellationException wird wie jede andere Ausnahme in der Ausnahmeverzweigung weiter nach oben gereicht. Die doCatch()-Methode empfängt die CancellationException aber nur, wenn es im Scope keine weitere Ausnahme gibt. Andere Ausnahmen werden höher priorisiert als der Abbruch.

Verschachteltes TryCatchFinally

Sie können TryCatchFinally gemäß Ihren Anforderungen verschachteln. Da jedes TryCatchFinally eine neue Verzweigung in der Baumstruktur der Ausführung erstellt, können Sie verschachtelte Bereiche anlegen. Ausnahmen im übergeordneten Scope führen zu Abbruchversuchen bei allen Aufgaben, die durch ein verschachteltes TryCatchFinally' in ihnen initiiert wurden. Allerdings werden Ausnahmen in einem verschachtelten TryCatchFinally nicht automatisch an das übergeordnete Element weitergegeben. Wenn Sie eine Ausnahme aus einem verschachtelten TryCatchFinally an das enthaltene TryCatchFinally weitergeben möchten, sollten Sie die Ausnahme in doCatch() erneut auslösen. Anderes ausgedrückt: Nur unbehandelte Ausnahmen steigen wie Javas try/catch-Konstrukt auf. Wenn Sie ein verschachteltes TryCatchFinally durch Aufruf der Abbruchmethode abbrechen, wird das verschachtelte TryCatchFinally abgebrochen, aber nicht automatisch auch das enthaltene TryCatchFinally.

Verschachteltes TryCatchFinally
new TryCatch() { @Override protected void doTry() throws Throwable { activityA(); new TryCatch() { @Override protected void doTry() throws Throwable { activityB(); } @Override protected void doCatch(Throwable e) throws Throwable { reportError(e); } }; activityC(); } @Override protected void doCatch(Throwable e) throws Throwable { reportError(e); } };