Jak ponawiać operacje w .NET z wykorzystaniem Polly?

Wprowadzenie

W aplikacjach bardzo często używamy zewnętrznych zasobów. A to trzeba wykonać zapytanie na bazie danych, pobrać dane z usługi lub wysłać wiadomość e-mail. Zasoby te charakteryzują się tym, że mogą być przez jakiś czas niedostępne, bo na przykład wystąpił jakiś problem z siecią lub zasób jest zbyt mocno obciążony. W takich sytuacjach zastanawiamy się, czy od razu pokazać użytkownikowi informacje o błędzie, czy może spróbować ponowić operację po jakimś czasie i dopiero po którejś próbie pokazać błąd. Ten drugi sposób jest na ogół pożądanym podejściem. W dzisiejszym wpisie pokażę Ci, jak łatwo i przyjemnie ponawiać operacje w momencie wystąpienia błędu (np. wyjątku) z wykorzystaniem biblioteki Polly (https://github.com/App-vNext/Polly).

Pobranie danych z Internetu

Do przetestowania biblioteki wykorzystamy klasę WebClient, która umożliwia pobieranie danych z internetu. Przykład tym razem nie będzie za bardzo rozbudowany. Będzie to prosta aplikacja konsolowa, która pobierze html z głównej strony tego bloga:

class Program
{
private static NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();
static void Main(string[] args)
{
_logger.Info("Start");
try
{
var content = Download("https://plawgo.pl");
_logger.Info("Success");
}
catch (Exception ex)
{
_logger.Fatal(ex, "Error");
}
_logger.Info("End");
}
static string Download(string url)
{
var client = new WebClient();
return client.DownloadString(url);
}
}
view raw Program.cs hosted with ❤ by GitHub

Nie ma tutaj nic skomplikowanego. Aplikacja za pomocą nloga wypisuje komunikaty na konsoli (jest ona ustawiona jako target w Nlog.config). Metoda Download pobiera string z przekazanego adresu i jest wywołana w bloku try-catch, który wyświetla informacja o tym, czy udało się pobrać htmla, czy nastąpił błąd.

W normalnej sytuacji bez błędu aplikacja wyświetli coś takiego:

polly download success

Symulowanie błędów sieci

Aby przetestować w praktyce działanie Polly, będziemy musieli kontrolować połączenie sieciowe. Podczas testów możemy się fizycznie odłączać od sieci, ale takie podejście na dłuższą metę jest problematyczne. Dlatego skorzystamy z gotowej aplikacji, która umożliwia symulowanie różnych problemów z działaniem sieci. Aplikacja nazywa się clumsy (https://jagt.github.io/clumsy/). Wystarczy ją pobrać, wypakować i uruchomić z uprawnieniami administratora. Co fajne, nie trzeba nic konfigurować, dodawać proxy itp. Uruchamiamy i działa.

Do przetestowania Polly skorzystamy z funkcji Drop, która oznacza zbyt długi czas odpowiedzi serwera. W testach ustawiłem, aby timeout występował z szansą 75%. Dodatkowo warto jeszcze ustawić filtr, aby aplikacja blokowa pokazywała tylko żądania wysyłane do testowego serwera, a nie wszystkie połączenia w systemie. Można zrobić to za pomocą filtru na górze. Podczas pisania tego wpisu korzystałem z filtru:

outbound and ip.DstAddr >= 185.255.40.24 and ip.DstAddr <= 185.255.40.24
view raw filtr.txt hosted with ❤ by GitHub

Z czasem adres IP serwera, na którym jest blog, może się zmienić, więc warto wcześniej to sprawdzić.

Dokładna konfiguracja clumsy wygląda tak:

polly clumsy

Po uruchomieniu i skonfigurowaniu clumsy testowa aplikacja wykona się tak (o ile oczywiście trafimy w te 75% problematycznych żądań :)):

polly download error

Darmowy kurs Visual Studio

Pracując z setkami programistów, zauważyłem, że większość osób nie pracuje efektywnie w Visual Studio. W skrajnych przypadkach korzystali z kopiowania z wykorzystaniem menu Edit. Wiem, że to dziwne, ale naprawdę niektórzy tak pracują. Dlatego postanowiłem stworzyć kurs Visual Studio – aby pomóc koleżankom i kolegom w efektywniejszej pracy.

Przygotowałem 30 lekcji e-mail, w których pokażę Ci, w jaki sposób pracować efektywniej i szybciej w Visual Studio. Poznasz dodatki, bez których nie wyobrażam sobie pracy w tym IDE.

Po więcej informacji zapraszam na dedykowaną stronę kursu: Darmowy Kurs Visual Studio.

Quiz C#

Ostatnio przygotowałem również quiz C#, w którym możesz sprawdzić swoją wiedzę. Podejmiesz wyzwanie?

Ponawianie operacji z Polly

Biblioteka Polly (https://github.com/App-vNext/Polly) jest jedną z ciekawszych opcji do ponawiania operacji. Możemy ją wykorzystać w standardowym .NET, jak i w wersji Core, w różnych frameworkach. Sama praca z biblioteką jest prosta i składa się generalnie z trzech kroków:

  • Określenia błędu lub sytuacji, którą chcemy obsłużyć – np. wyjątku lub wyniku metody
  • Zdefiniowania, co ma się stać, gdy nastąpiła sytuacja z punktu pierwszego – np. trzykrotne ponowienie operacji
  • Samego wywołania operacji, której ma dotyczyć zdefiniowana reguła.

Najlepiej działanie biblioteki zobaczyć w praktyce. Poniżej znajduje się fragment kodu, który do wcześniejszej metody pobierania htmla z internetu dodaje trzykrotne ponowienie operacji w momencie, gdy zostanie wyrzucony WebException:

static string DownloadWithRetry(string url)
{
var client = new WebClient();
return Policy
.Handle<WebException>()
.Retry(3, (ex, retryCount) =>
{
_logger.Error(ex, $"Error - try retry (count: {retryCount})");
})
.Execute(() => client.DownloadString(url));
}
view raw Program2.cs hosted with ❤ by GitHub

Powyżej w kodzie widać wspomniane trzy kroki:

  • Handle – rodzaj wyjątku, dla którego tworzymy regułę
  • Retry – określa, co ma się stać, gdy będzie wyjątek – w tym przypadku trzykrotna próba ponowienia operacji – dodatkowo dodanie do logu stosownej informacji
  • Execute – uruchamia właściwą metodę, która pobiera html i do której zostanie zastosowana reguła

Poniżej zrzut ekranu, na którym widać, jak zadziałała powyższa reguła. Wystąpił jeden błąd i operacja została ponowiona:

polly download retry

Ponawianie z czekaniem

Metoda Retry z wcześniejszego przykładu, w momencie gdy pojawi się błąd, od razu ponawia operację. Czasami takie zachowanie jest tym, czego potrzebujemy, natomiast bardzo często takie zachowanie może powodować dodatkowe problemy. Gdy serwer, do którego wysyłamy żądania, jest obciążony, taki sposób ponawiania może dodatkowo obciążyć go jeszcze bardziej. Dlatego definiując reguły w Polly, warto zastanowić się, czy powinniśmy od razu ponowić operację, czy może chwilkę poczekać.

Czekanie daje serwerowi czas, aby obsłużył wszystkie aktualne operacje bez dodawania mu kolejnych rzeczy do wykonania. Warto to rozważyć, szczególnie że w Polly definiuje się to bardzo łatwo:

static string DownloadWithRetryAndDelay(string url)
{
var client = new WebClient();
return Policy
.Handle<WebException>()
.WaitAndRetry(new[]
{
TimeSpan.FromSeconds(10),
TimeSpan.FromSeconds(20),
TimeSpan.FromSeconds(50)
}, (ex, timeSpan, retryCount, context) =>
{
_logger.Error(ex, $"Error - try retry (count: {retryCount}, timeSpan: {timeSpan})");
})
.Execute(() => client.DownloadString(url));
}
view raw Program3.cs hosted with ❤ by GitHub

Zamiast z metody Retry korzystamy z metody WaitAndRetry. Do metody tej przekazujemy informacje o czasie, jaki powinien minąć między poszczególnymi próbami. Czas ten możemy przekazać w postaci tablicy jak powyżej lub delegatu, który będzie obliczał kolejne okresy czekania. Poniżej wynik działania testowej aplikacji. Zwróć uwagę, że czas między poszczególnymi próbami się zwiększa:

polly download wait and retry

Ponawianie operacji w nieskończoność

Innym sposobem ponawiania operacji jest próba wykonywania jej w nieskończoność. Taki sposób ponawiania operacji również może być przydatny w aplikacji. Czasami mamy specjalną akcję w systemie, która służy do monitorowania, czy usługa lub inny zasób, z którego korzystamy, jest dostępny (np. metoda Ping, która pinguje serwer co 1 minutę). W momencie gdy serwer przestaje odpowiadać, możemy zablokować wszystkie wywołania poza tą specjalną metodą. Wywołanie pinga ponawiamy w nieskończoność do momentu odpowiedzi. Gdy serwer zacznie odpowiadać, możemy wznowić wszystkie operacje.

Aby dodać ponawianie w nieskończoność, wystarczy skorzystać z innej metody w regule Polly. RetryForever może próbować wykonywać operację ponownie od razu po błędzie (tak jak w przykładzie poniżej); możemy dodać też opóźnienia między poszczególnymi próbami, tak jak w WaitAndRetry.

static string DownloadWithRetryForever(string url)
{
var client = new WebClient();
return Policy
.Handle<WebException>()
.RetryForever(ex =>
{
_logger.Error(ex, $"Error - try retry forever");
})
.Execute(() => client.DownloadString(url));
}
view raw Program4.cs hosted with ❤ by GitHub

Kilka kolejnych prób w testowej aplikacji:

polly download retry forever

Hangfire vs Polly

Kilka tygodni temu opisywałem użycie biblioteki Hangfire (na przykładzie wysyłki e-mail w tle). Obie biblioteki służą w pewnym sensie do ponawiania operacji. Hangfire w momencie błędu również po jakimś czasie wykonuje zadanie jeszcze raz. Kiedy więc używam Hangfire, a kiedy Polly?

Hangfire służy mi do wykonywania większych (takich bardziej biznesowych) zadań. Dobrym przykładem jest właśnie wysyłka wiadomości e-mail. Jest to takie większe zadanie, które na ogół składa się z kilku różnych kroków. Na przykład wysłanie wiadomości potwierdzającej założenie konta (w Hangfire mamy tylko zapisane ID nowego użytkownika) będzie składać się z następujących kroków: pobranie danych z bazy o użytkowniku, wygenerowanie treści wiadomości oraz jej wysyłka.

Polly natomiast wykorzystuję do operacji niskopoziomowych, które na ogół wchodzą w skład czegoś większego. W przykładzie z wysyłką ee-mail Polly użyłbym w dwóch miejscach: do ponawiania pobraniach danych użytkownika z bazy oraz samej wysyłki wiadomości.

Jak widać, można w fajny sposób połączyć działanie tych dwóch bibliotek w jeden niezawodny system.

Przykład

Kod przykładu znajduje się w repozytorium na githubie: https://github.com/danielplawgo/PollyExample. Po pobraniu przykładu w metodzie Main należy zmienić wywołanie testowych metod w zależności od tego, co chcesz testować. Pamiętaj, żeby w jakiś sposób (np. za pomocą clumsy) zablokować dostęp do serwera, aby wystąpił wyjątek. Wtedy będziesz mógł sprawdzić działanie biblioteki w praktyce.

Podsumowanie

Niezawodność działania jest jednym z istotnych elementów podczas tworzenia aplikacji. Jest to istotne szczególnie gdy coraz popularniejsze staje się rozbijanie dużych systemów na mniejsze mikro serwisy, które komunikują się z sobą. W takim środowisku ponawianie operacji jest bardzo ważne.

Dlatego warto zainteresować się biblioteką Polly, który umożliwia dodanie ponawiania w prosty i przyjemny sposób. Sama biblioteka posiada dużo większe możliwości niż to, co pokazałem w tym wpisie. Dlatego gorąco zachęcam do przejrzenia jej dokumentacji na stronie https://github.com/App-vNext/Polly.

W jednym w kolejnych wpisów pokażę, jak można dodać takie ponawianie transparentnie z wykorzystaniem interceptorów w Autofac.

 

 

8 thoughts on “Jak ponawiać operacje w .NET z wykorzystaniem Polly?

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.