Enterprise Library: TransientFaultHandling Application Block

Gerade bei vernetzten oder cloudbasierten Anwendungen ist es wichtig eine Strategie für flüchtige/vorübergehende (engl. transient) Fehler zu haben. Aber was genau versteht man denn jetzt unter dem Begriff „flüchtiger Fehler“? Unter flüchtigen Fehlern versteht man zum Beispiel:

  • Netzwerkverbindung unterbrochen
  • Service nicht erreichbar (z.B. Service welcher Wetterdaten oder Aktienkurse liefert)
  • Datenbank nicht verfügbar

Damit die Anwendung jetzt etwas nachgiebiger auf solche Fehler reagiert bedarf es einer geeigneten Strategie. Typischerweise führt man die Operation, die den entsprechenden Fehler verursacht hat, nochmals oder solange durch bis der Fehler nicht mehr auftritt. Gerade bei Netzwerkproblemen ist dies eine gängige Vorgehensweise. Der TransientFaultHandling Application Block der Enterprise Library bietet hier nun einige Funktionen und Strategien, die man leicht in die eigene Anwendung integrieren kann. Zusätzlich können diese Funktionen/Strategien einfach erweitert und konfiguriert werden.

Verschiedene Dienste können nun unterschiedliche Fehler produzieren. Aus diesem Grund stellt der TransientFaultHandling Application Block auch standardmäßig schon einige Strategien (DetectionStrategy) für gängige cloudbasierte Services bereit. Aktuell sind das die Folgenden:

  • SQL-Datenbank
  • Windows Azure Service Bus
  • Windows Azure Storage Service
  • Windows Azure Caching Service

Zum anderen ist es natürlich auch möglich eigene Strategien für die Fehlerbehandlung zu implementieren, um einen konsistenten Ansatz für die Behandlung solcher Fehler innerhalb der Anwendung zu haben. Dabei kann die eigene Strategie von diversen Faktoren abhängen, beispielsweise wie energisch die Wiederholungsversuche (Retry Strategy) durchgeführt werden sollen. Dies könnte nämlich den dahinterliegenden Service dazu veranlassen, die Verbindungsgeschwindigkeit zu drosseln oder den Client komplett zu sperren. Der TransientFaultHandling Application Block stellt standardmäßig schon die folgenden Wiederholungsstrategien zur Verfügung:

WiederholungsstrategieBeispiel (Intervall zwischen den einzelnen Wiederholungen in Sekunden)
Fixed interval2,2,2,2,2,2
Incremental intervals2,4,6,8,10,12
Random exponential back-off intervals2, 3.755, 9.176, 14.306, 31.895

Ein weiterer Punkt ist zu entscheiden, ab welchem Zeitpunkt es sich nicht mehr um einen flüchtigen sondern um einen dauerhaften Fehler handelt (z.B. nach 3 weiteren Verbindungsversuchen oder nach 60 Sekunden). Deshalb ist es möglich für alle gezeigten Strategien die maximale Anzahl von Wiederholungsversuchen festzulegen! Nachfolgend wird jetzt erläutert wie man die beschriebenen Funktionen in der eigenen Anwendung verwenden kann. Zunächst einmal ein Schaubild zur Verdeutlichung der Zusammenhänge:

transient_fault_handling
Zusammenhänge der einzelnen Objekte (Quelle [1], Seite 85)

Wie aus dem Schaubild ersichtlich ist die RetryPolicy eine Kombintation aus Detection- und RetryStrategy. Die RetryPolicy wird dann genutzt um die die eigentliche Aktion auszuführen. Dazu später mehr.

Transient Fault Application Block zur Anwendung hinzufügen

Wie alle Application Blocks der Enterprise Library lässt sich auch der Transient Fault Handling Application Block einfach via NuGet zur eigenen Anwendung hinzufügen:

EntLibTransientFaultHandlingNuGet

Instanziierung der Objekte

Nachdem der Transient Fault Handling Application Block zum Projekt hinzugefügt wurde dann direkt mit der Implementierung begonnen werden. Für die Erzeugung von Objekten aus den verschiedenen Enterprise Library Application Blocks (Logging, Database, …) gibt es zwei Möglichkeiten:

  • direkte Erzeugung im Code oder
  • via Konfiguration

Hier mal Beispiele für die Objekterzeugung in Verbindung mit dem Transient Fault Application Block:

Code

Gerade in kleineren Applikationen ist es meist sinnvoller die Objekte direkt im Code zu erzeugen. Dies sieht wie folgt aus:

Hier wird zunächst eine passende RetryStrategy erstellt, die dann für die Erzeugung der RetryPolicy verwendet wird. Die RetryStrategy wird hier mit folgenden Parametern erstellt: Der erste Neuversuch startet 1 Sekunde nachdem der flüchtige (transiente) Fehler festgestellt wurde. Danach wird das Wiederholungsintervall (bei jedem Neuversuch) um jeweils 2 Sekunden verlängert, so dass alle 2,4,6,8,10 Sekunden ein neuer Versuch gestartet wird bis die maximale Anzahl von 5 Versuchen erreicht ist. Wie eine entsprechende Aktion ausgeführt werden kann wird nachfolgend genauer erläutert.

Konfiguration

In größeren Applikation mit vielen Methoden, die eine entsprechende Wiederholungsstrategie (RetryStrategy) benötigen, ist es auch möglich diese Informationen in der Konfigurationsdatei abzulegen. Damit ist dann alles konsistent in einer Datei abgelegt und hat den Vorteil, dass man bei Änderungen den Code nicht neu kompilieren muss. Nachfolgend mal eine beispielhafte Konfiguration:

Das folgende Codebeispiel lädt jetzt die Konfiguration und erzeugt ein Objekt mit den hinterlegten Konfigurationseinstellungen namens „Incremental Retry Strategy„:

Dieses Vorgehen hat natürlich den großen Vorteil, dass kein Quellcode angepasst werden muss. Hier genügt es die Konfiguration zu ändern bzw. anzupassen und das jeweilige Objekt wird mit den neuen Einstellungen erzeugt.

Hinweis zu Konfiguration

Zur Konfiguration kann ich jedem nur das mitgelieferte Konfigurationstool der Enterprise-Library empfehlen. Mit diesem Tool ist es ganz einfach die einzelnen Application-Blocks in die Konfiguration einzufügen. Darüber hinaus können die einzelnen Application-Blocks dann im Detail konfiguriert werden und die Konfigurationsdatei hat immer einen konsistenten Stand (denn diese wird beim Speichern automatisch validiert). Beim manuellen Editieren der Konfigurationsdatei schleichen sich schnell Fehler ein, die manchmal nur sehr schwer zu finden sind.

Eigene ErrorDetectionStrategy implementieren

Um eine eigene ErrorDetectionStrategy zu erstellen muss das Interface ITransientErrorDetectionStrategy implementiert werden.

Dieses Interface ist recht einfach aufgebaut und besitzt lediglich die Methode IsTransient. Diese Methode erwartet als Parameter die aufgetretene Exception und gibt dann true oder false zurück. Damit wird dann festgelegt, ob es sich bei dem aufgetrenen Fehler um einen flüchtigen Fehler handelt oder nicht. Nachfolgend mal ein Beispiel dazu:

Im oben gezeigten Beispiel wird eine neue Klasse erstellt, die das Interface ITransientErrorDetectionStrategy implementiert. Die IsTransient-Methode erhält als Parameter die aufgetretene Exception. Innerhalb der Methode wird dann der Typ der Exception geprüft und im Falle einer WebException wird true zurückgeliefert. Das wäre auch schon alles. Kommen wir nun zu einem kleinen Beispiel.

Beispiel

In folgendem Beispiel soll einfach eine Webressource abgerufen werden. Um die Webressource abzurufen wird die Klasse WebClient verwendet. Diese Klasse stellt eine entsprechende Download-Methode bereit. Bei der Ausführung dieser Operation können nun flüchtige Fehler, wie z.B. Netzwerkverbindung nicht verfügbar oder Hostname kann nicht aufgelöst werden, in Form einer WebException auftreten. Das Beispielprogramm dazu sieht wie folgt aus:

In Zeile 15 wird zunächst die RetryStrategy erzeugt, die dann in Zeile 19 für die Instanziierung der RetryPolicy verwendet wird. Als DetectionStrategy kommt die im Artikel gezeigte CustomTransientErrorDetectionStrategy zum Einsatz. Das Retrying-Event wird verwendet um den aktuellen Status auszugeben (Zeile 20). In Zeile 23 wird dann die eigentliche Aktion ausgeführt. Dafür stellt die Klasse RetryPolicy die Methoden ExecuteAction und ExecuteAsync bereit.

Synchrone Aufrufe

Synchrone Aufrufe können mit der ExecuteAction-Methode durchgeführt werden. Dabei bieten die diversen Überladungen dieser Methode die folgenden Möglichkeiten:

  • Synchroner Aufruf, der void zurückliefert
  • Synchroner Aufruf, der einen Wert zurückliefert

Asynchrone Aufrufe

Asynchrone Aufrufe können mit der ExecuteAsync-Methode realisiert werden. Dabei bieten die diversen Überladungen dieser Methode die folgenden Möglichkeiten:

  • Asynchroner Aufruf, der einen Task zurückliefert
  • Asynchroner Aufruf, der einen Task<T> zurückliefert

In Zeile 41 des Beispielprogramms wurde absichtlich eine nicht existente Adresse (http://csharp-blogg.de) hinterlegt. Dies führt zu einer WebException (Hostname kann nicht aufgelöst werden) und daraus resultiert dann die folgende Ausgabe:

EntLibTransientFaultHandlingSample

Die RetryStrategy ist ja so konfiguriert, dass 1 Sekunde gewartet bis der erste Neuversuch gestartet wird. Danach wird das Wiederholungsintervall (bei jedem Neuversuch) um jeweils 2 Sekunden verlängert, so dass alle 2,4,6,8,10 Sekunden ein neuer Versuch gestartet wird bis die maximale Anzahl von 5 Versuchen erreicht ist. Wie im Beispielprogramm zu sehen ist es mit dem TransientFaultHandling Application Block recht einfach flüchtige Fehler zu handeln bzw. ein mehrfaches Ausführen einer Operation im Fehlerfall zu realisieren. Dabei hält sich der Implementierungsaufwand in Grenzen und das Error-Handling lässt sich darüber hinaus flexibel konfigurieren. Das Beispielprogramm gibt es am Ende des Artikels als Download.

Alternativen

Es gibt auch einige Alternativen, die hier der Vollständigkeit halber erwähnt werden. Ich werden diese in einem nächsten Artikel etwas genauer vorstellen:

Literaturverzeichnis und Weblinks

Abk.Quelle
[1]Developer's Guide to Microsoft Enterprise Library, 2nd Edition, ISBN 978-1-62114-034-4

Download:
http://www.microsoft.com/en-us/download/details.aspx?id=41145
[2]Transient Fault Handling Application Block
https://msdn.microsoft.com/en-us/library/hh680934(v=pandp.50).aspx
[3]Implementing a Custom Detection Strategy
https://msdn.microsoft.com/en-us/library/hh680940(v=pandp.50).aspx
[4]Implementing a Custom Retry Strategy
https://msdn.microsoft.com/en-us/library/hh680943(v=pandp.50).aspx
[5]Palmer
Palmer is a .NET library that presents a fluent-API which makes it easier to write retry logic. Palmer is developed and maintained by Mitch Denny and is released under the MIT license.
https://github.com/mitchdenny/palmer/
[6]Polly
Polly is a .NET 3.5 / 4.0 / 4.5 / PCL (Profile 259) library that allows developers to express transient exception handling policies such as Retry, Retry Forever, Wait and Retry or Circuit Breaker in a fluent manner.
https://github.com/michael-wolfenden/Polly

Downloads

[wpdm_package id=’1692′]

Fork me on GitHub