W jednym z poprzednich wpisów, pokazałem jak prawidłowo tworzyć wątki w tle w celu wykonania jakieś czasochłonnej operacji w ASP.NET. Pod wpisem, Michal Dymel zaproponował narzędzie o nazwie Hangfire.
Muszę przyznać, że framework jest bardzo prosty w użyciu i nie ma zbyt wiele zewnętrznych zależności. Od kilku tygodni korzystam z niego w jednym ze swoich projektów i nie miałem żadnych problemów z konfiguracją czy wdrążaniem rozwiązania w system produkcyjny.
Po kolei jednak… Hangfire służy do wykonywania czasochłonnych operacji w ASP.NET. Dostarcza zatem model “fire&forget”, czyli bardzo prostą implementację systemu kolejkowego. Jeśli zaplanowane zadanie niepowiedzie się (np. timeout), wtedy zostanie automatycznie kilkakrotnie razy powtarzane, w różnych odstępach czasu (tak jak w nServiceBus).
Wiemy, że IIS może zamknąć proces w dowolnym momencie, kończąc tym samym wszystkie zadania, które były wykonywane w tle. Dzięki Hangfire, nie musimy się martwić o wznawianie takiej operacji.
Zaplanowane zadania, przechowywane są w bazie danych, np. SQL Server czy Redis. Istnieje możliwość zaimplementowania własnego repozytorium, ale jest to dość skomplikowane.
Ponadto, Hangfire dostarcza bardzo przyjazny interfejs użytkownika. Instalacja jest bardzo prosta – wystarczy zainstalować poniższy pakiet NuGet:
Install-Package HangFire
Następnie w Owin startup, umieszczamy:
public class Startup { public void Configuration(IAppBuilder app) { GlobalConfiguration.Configuration .UseSqlServerStorage(@"Server=PIOTR\SQLEXPRESS;Database=HangfireDb;Trusted_Connection=True;"); app.UseHangfireDashboard(); app.UseHangfireServer(); } }
Powyższy przykład bazuje na SQL Server jako repozytorium zadań. Hangfire sam stworzy wymaganą strukturę tabel. Po uruchomieniu aplikacja, baza danych zostanie zainicjalizowana następującymi tabelami:
W celu zaplanowania zadania, które wykonuje się o określonym czasie wystarczy:
RecurringJob.AddOrUpdate("Nazwa zadania",() => Console.WriteLine("Zaplanowane zadanie...."),Cron.Minutely);
Zwykle powyższy kod umieszcza się w Startup. Możemy przekazać jako parametr dowolne wyrażenie CRON. Hangfire zadba, aby zadanie wykonywało się o określonych porach oraz zostało wznawiane w przypadku recycling dokonywanego przez IIS.
Możemy również dodać zadanie do kolejki, jeśli chcemy, aby zostało wykonane tylko raz:
BackgroundJob.Enqueue( () => Console.WriteLine("Test"));
Dużym udogodnieniem jest dostarczenie UI w formie dashboard. Przechodząc na stronę /hangfire (np. http://localhost:63486/hangfire), zobaczymy diagram pokazujący liczbę wykonanych zadań w ciągu ostatniego tygodnia lub dnia:
W zakładce “Recurring tasks” zobaczymy nasze zaplanowane zadania:
Mamy szybki i łatwy podgląd, kiedy ostatnio zadanie zostało wykonywane i z jaką częstotliwością będzie wykonywane.
Przechodząc do zakładki Jobs, zobaczymy listę dostępnych kolejek:
Widzimy, że aktualnie mamy 9 zadań w “Succeeded”. Hangfire zadba o tym, aby nie zapychać powyższych kolejek i każdego dnia niepotrzebne wpisy zostają usuwane. Przechodząc do jakiejkolwiek kolejki, możemy przyjrzeć się szczegółom wykonanemu zadaniowi:
Załóżmy, że nasze zadanie teraz będzie wyrzucało wyjątek:
public class Startup { public void Configuration(IAppBuilder app) { GlobalConfiguration.Configuration .UseSqlServerStorage(@"Server=PIOTR\SQLEXPRESS;Database=HangfireDb;Trusted_Connection=True;"); app.UseHangfireDashboard(); app.UseHangfireServer(); RecurringJob.AddOrUpdate("Nazwa zadania", () => SampleTask(), Cron.Minutely); } private static void SampleTask() { throw new NotImplementedException(); } }
W zakładce Retries zobaczymy wtedy wpis informujący nas, że zadanie nie zostało wykonywane i Hangfire próbuje je wykonywać powtórnie:
Oczywiście po pewnym czasie, gdy próby nie przyniosą efektu, zadanie zostanie na stałe przeniesione do “Failed” (gdzie ręcznie możemy wznowić wykonywanie).
Przechodząc do szczegółów zadania, zobaczymy informacje o wyjątku:
Ponadto HangFire integruje się z popularnymi loggerami (np. nLog, log4net) i jakiekolwiek logi wykonane przez Hangfire będą widoczne w naszych logach. Nic nie trzeba konfigurować – Hangfire automatycznie wykryje, aktualnie wykorzystywanego w systemie loggera.
Też od jakiegoś czasu używam hangfire. Świetne i proste narzędzie. I właśnie ta prostota czasem jest lekkim minusem, ponieważ np:
– brak obsługi tłumaczeń na inne niż angielski
– brak jakiegokolwiek wpływu na wygląd dashboard
– brak powiadomień (np email) o niepowodzeniach (trzeba często ślezzić dashboard)
Te punkty sprawiają że często wykorzystanie tego narzędzia może być odrzucone, a szkoda bo jest super.
I na koniec uwaga. Jeśli ktoś korzysta z IoC oraz hostuje hangfire server w innej aplikacji niż dashboard to dla dashboard także trzeba skonfigurować IoC mimo że tylko wyświetla dane z bazy. W przeciwnym wypadku nie wyświetli nam się nazwa i informacja o wykonywanej akcji ( can not find the target method )
@Pawel:
Co do alertow, mozna to rozwiazac na poziomie logger’a i wysylac po prostu maile w przypadku bledow (np. poprzez hangfire).
Używam tego narzędzia od kilku miesięcy produkcyjnie, jest super i lepszego nie znalazłem. Ciekawostka jest taka, że jeśli masz aplikację w load balancingu czyli mamy kilka instancji na tej samej bazie danych (zakładam storage np. na sql) i jest na nich HangFire serwer to aplikacje będą się prześcigać w tym która pierwsza zrobi zlecone zadanie do kolejki. 🙂 np. instancja A wrzuci task do kolejki to może go podjąć instancja B, ba wydaje mi się, że nawet różne od siebie aplikacje mogą wymieniać się zadaniami o ile są na tym samym serwerze. W dashbord w ostatniej zakładce widać wtedy więcej serwerów – trzeba tylko zadbać żeby każda instancja miała indywidualny identyfikator hangfire serwera (swego czasu to był problem i instancje się blokowały).
Ale jest jeden minus, który trzeba samemu doimplementować. To że hangfire będzie działał zawsze i nie pominie zadania zależy tylko od IIS (o ile stawiamy na IIS) i tutaj nie jest to do końca wyjaśnione jak zrobić to poprawnie, bo trzeba wykonać odpowiednie ustawienia na IIS oraz zaimplementować IHttpModule z uruchomieniem Hangfire serwera. Dokumentacja akurat w tym zakresie nie jest oczywista. Super byłoby o tym napisać jak to zrobić dobrze. 🙂