Open Source Highlight: Polly

In meinem Berufsalltag begegnen mir immer wieder ausgezeichnete Open Source Bibliotheken, die ich mir auf GitHub markiere. Einige davon möchte ich gerne vorstellen.

Als erste Bibliothek in dieser Serie möchte ich Polly vorstellen. Polly kommt dann zum Einsatz, wenn Funktionsaufrufe getätigt werden, von denen man ausgehen muss, dass sie fehlschlagen können. Beispielsweise aufgrund von Netzwerkproblemen oder weil der entfernte Server regelmässig neu gestartet wird. Polly reagiert dann entsprechend darauf.

Zuerst muss man Polly sagen, was überhaupt fehlschlagen kann. Dazu erstellt man eine sogenannte Policy, die auf bestimmte Exceptions reagiert.

var policy = Policy.Handle<SqlException>();

Eine Policy kann auch auf mehrere verschiedene Exceptions reagieren. Zudem können Bedingungen erfasst werden, die von der Exception erfüllt werden müssen.

var policy = Policy
    .Handle<SqlException>()
    .Or<ArgumentException>(ex => ex.ParamName == "example");

Danach muss der Policy noch mitgeteilt werden, wie sie auf die entsprechenden Fehlschläge reagieren soll. Es gibt die Möglichkeiten Retry, RetryForever, WaitAndRetry und CircuitBreaker.

Retry

Mit der Retry Methode kann man einen Funktionsaufruf wiederholen. Entweder nur einmal oder auch mehrmals, bis zu einem definierten Maximum. Zusätzlich kann für jede Wiederholung ein Action-Delegate übergeben werden, welches für jeden Fehlschlag aufgerufen wird.

var policy = Policy
    .Handle<SqlException>
    .Retry();
var policy = Policy
    .Handle<SqlException>
    .Retry(3);
var policy = Policy
    .Handle<SqlException>()
    .Retry(3, (exception, retryCount) =>
    {
        // hier auf Fehlschläge reagieren
    });

RetryForever

Wie der Name schon sagt, wird der Funktionsaufruf bei der RetryForever Methode einfach so lange wiederholt, bis kein Fehler mehr auftritt. Wie bei Retry kann auch hier ein Action-Delegate übergeben werden, welches im Fehlerfall aufgerufen wird.

var policy = Policy
    .Handle<SqlException>()
    .RetryForever();
var policy = Policy
    .Handle<SqlException>()
    .RetryForever(exception =>
    {
        // Hier auf Fehlschläge reagieren
    });

WaitAndRetry

Wenn eine Aktion fehlschlägt, weil der entfernte Rechner gerade neu gestartet wird, nützt es nichts, wenn man den Server mit Anfragen bombardiert. In solchen Fällen ist es ratsam, zwischen den Versuchen ein Weilchen zu warten. Dafür gibt es die WaitAndRetry Methode.

var policy = Policy
    .Handle<SqlException>()
    .WaitAndRetry(new []
    {
        TimeSpan.FromSeconds(1),
        TimeSpan.FromSeconds(5),
        TimeSpan.FromSeconds(30),
    });

In diesem Beispiel wird nach dem ersten Fehlschlag eine Sekunde gewartet, beim zweiten fünf Sekunden und beim dritten 30 Sekunden. Es gibt aber auch die Möglichkeit, die Wartezeit jedes Mal neu zu berechnen.

var policy = Policy
    .Handle<SqlException>()
    .WaitAndRetry(5, retryAttempt => 
        TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))
    );

Mit diesem Beispiel von der Polly-Website wird die Wartezeit als Zweierpotenz berechnet. Somit wird beim ersten Mal 2 Sekunden gewartet, beim zweiten Mal 4 Sekunden, dann 8 Sekunden, dann 16 und schlussendlich 32 Sekunden.

CircuitBreaker

Eine längere Abhandlung des Circuit Breaker Patterns findet man auf dem Blog von Davy Brion. Daher hier nur eine kurze Einführung.

Ist ein entfernter Webservice temporär nicht verfügbar, kann es sein, dass anstatt einer schnellen Fehlermeldung erst nach einer gewissen Zeit ein Timeout Fehler kommt. Wird dieser Service von vielen Threads oft aufgerufen, kann das das gesamte System drastisch ausbremsen. Das Circuit Breaker Pattern implementiert eine State Machine, die den aktuellen Zustand zwischenspeichert. Wenn der Funktionsaufruf ein paar Mal fehlgeschlagen ist, geht die State Machine in den Fehlerzustand. Dieser Zustand wird für eine bestimmte Zeitdauer gespeichert. Jeder Funktionsaufruf schlägt nun sofort fehl, anstatt auf das Timeout zu warten. Damit werden nicht unnötige Systemressourcen verschwendet.

Polly implementiert das Circuit Breaker Pattern mit der CircuitBreaker Methode. Zuerst wird angegeben, wie viele Fehlschläge es benötigt, bis das System in den Fehlerzustand geht, der zweite Parameter ist die Zeitdauer, wie lange der Fehlerzustand gespeichert wird.

var policy = Policy
    .Handle<SqlException>()
    .CircuitBreaker(2, TimeSpan.FromMinutes(1));

Execute

Die Policy allein nützt noch nichts, man muss sie auch nutzen können. Dazu gibt es die Execute Methode.

var policy = Policy
    .Handle<SqlException>()
    .Retry();

var result = policy.Execute(() => DoSomething());

Selbstverständlich kann man das auch zusammen schreiben.

var result = Policy
    .Handle<SqlException>()
    .Retry()
    .Execute(() => DoSomething());

Zusammenfassend lässt sich sagen, dass Polly ein sehr mächtiges Werkzeug ist. Nur wenige Zeilen Quellcode reichen, um eine Retry-Policy zu erstellen und zu nutzen. Gerade für verteilte Anwendungen kann ich Polly sehr empfehlen.

Polly wurde von Michael Wolfenden auf GitHub veröffentlicht und steht auch als NuGet Packet zur Verfügung.