Celery jest to asynchroniczna kolejka zadań oparta na przekazywaniu rozproszonych wiadomości. Zadania mogą być umieszczane w kolejce (zlecane) poprzez np. aplikacje webowe lub inne oprogramowanie.
Klasyczne aplikacje webowe działają w cyklach żądanie-odpowiedź (request-response). Gdy użytkownik wpisze w pasku adresu przeglądarki url aplikacji, lub kliknie na odpowiednie link, to przeglądarka wysyła żądanie do aplikacji webowej. Aplikacja przetwarza to żądanie, generuje zapytania do bazy danych, parsuje szablony itp. i na końcu zwraca do przeglądarki odpowiedź. Cały ten cykl powinien trwać jak najkrócej, tak aby użytkownik nie musiał długo czekać na rezultat żądania. Dodatkowo, aplikacja może w tym samym niemal czasie otrzymywać wiele żądań od różnych użytkowników i długi czas przetwarzania żądań wpływa niekorzystnie na wydajność samej aplikacji.
Aby skrócić czas przetwarzania żądania stosuje się różne metody i zabiegi np. optymalizuje się zapytania do bazy danych, stosuje mechanizmy cache itp. Zdarzają się jednak sytuacje, że trzeba wykonać złożone czasowo lub obliczeniowo zadania (generowanie raportu, eksport dużej ilości danych, przetwarzanie obrazów/clipów video, pobranie danych z zewnętrznych serwisów/api itp). Takie zadania mogą wykonywać się w czasie znacząco dłuższym niż typowy czas przetwarzania żądania i wówczas nie powinno się ich wykonywać w cyklu request-response.
Z pomocą tutaj przychodzi Celery, które może wykonywać asynchronicznie zlecone czasochłonne zadania. Rolą Celery jest przyjąć w jak najkrótszym czasie zlecenie wykonania zadania, a samo zadanie umieścić w kolejce do wykonania w tle.
W tym wpisie przedstawię tylko podstawy współpracy z Celery, celowo pomijając bardziej złożone kwestie, takie jak rezultaty wykonanych zdań, dystrybucje zadań do wielu kolejek, priorytetowanie, sterowanie przepływem itp. Być może w przyszłości pojawi się oddzielny wpis związany z tymi kwestiami.
Instalacja Celery
Instalację Celery najprościej jest przeprowadzić w wykorzystaniem narzędzia pip:
Celery do działania potrzebuje jeszcze pośrednika (brokera) do przesyłania komunikatów. Możemy tutaj skorzystać z Redis, RabbitMQ, AmazonSQS lub Zookeeper. Dla naszych potrzeb skorzystamy z brokera Redis.
Musimy zainstalować go niezależnie w systemie:
od razu zainstalujmy niezbędne komponenty dla przykładowej aplikacji:
Konfiguracja i współpraca z Django
Od wersji 3.1 Celery potrafi już samodzielnie współpracować z projektem Django (we wcześniejszych wersjach wymagana była instalacja dodatkowej biblioteki django-celery).
Załóżmy, że mamy następujący szkielet typowej aplikacji:
Zaczniemy od zdefiniowania instancji Celery w projekcie Django. W tym celu w aplikacji projektu stworzymy plik celery.py:
zapewnijmy teraz, że celery zostanie załadowanie przy uruchamianiu aplikacji django.
i na zakończenie skonfigurujmy jeszcze odpowiedniego brokera dla Celery. Robimy to w proj/settings.py poprzez dodanie linii:
Tworzymy pierwsze zadanie
Konfiguracja przygotowana, więc możemy przystąpić do pisania pierwszego zadania dla naszej aplikacji. Niech będzie to zadanie pobrania danych o aktualnej pogodzie dla wskazanej miejscowości z serwisu .openweather.org. Do pobierania danych z API tego serwisu będziemy potrzebować założyć w nim darmowe konto i uzyskać API key. Jeśli mamy już API key możemy przystąpić do pisania kodu. Zadanie nasze będzie działało w sposób następujący:
przy wywołaniu zadania przekażemy parametr będący nazwą miejscowości, dla której będziemy pobierać dane o aktualnej pogodzie
po pobraniu dane pogodowe (wybrane) zostaną zapisane w modelu
dla uproszczenia pominiemy obsługę błędów
Model dla danych pogodowych niech wygląda następująco:
Nasze zadanie umieśćmy w pliku tasks.py w katalogu aplikacji.
Uwaga! Umieszczanie kluczy API w plikach konfiguracyjnych jest złą praktyką! Takie klucze należy umieścić dajmy na to w zmiennych środowiskowych serwera, skąd aplikacja Django mogłaby je pobierać do settings w trakcie uruchomienia (dla przykładu przy wykorzystaniu dodatku django_environ).
Teraz pozostało nam stworzenie odpowiedniego widoku, który będzie zlecał wykonanie zadania aktualizacji danych pogodowych dla poszczególnych miejscowości zapisanych w City.
i dla formalności urls.py:
Przykładowy kod mamy gotowy, pora więc lokalnie uruchomić całość. Mamy już zainstalowany i uruchomiony redis, teraz w następnej kolejności uruchamiamy celery (np. w oknie konsoli).
W drugiej konsoli uruchamiamy projekt django (jeśli tworzymy własny projekt od zera, to pamiętajmy o migracjach oraz o wpisaniu nazw miejscowości do tabeli City):
W moim przypadku miałem wpisane trzy miejscowości w tabeli City, i dla każdej z nich zostało zlecone zadanie pobrania danych pogodowych z API openweather
Z racji tego, że w niniejszym przykładzie wykorzystuję bazę danych SQLite, to mogą pojawić się błędy działania związane z równoczesnym dostępem do danych przez procesy workera (django.db.utils.OperationalError: database is locked), gdyż baza ta nie umożliwia standardowo wielodostępu do danych.
Rozwiązaniem jest zmiana bazy danych na np. PostgreSQL.
Zadania okresowe
Istnieją czasem sytuacje, w których aplikacja webowa musi wykonywać jakieś zadania okresowo (np. raz dziennie, co godzinę itp) lub o określonej porze dnia. Celery daję nam możliwość ustawiania harmonogramów wykonywania zadań w stosunkowo łatwy sposób. Do tego celu służy celery beat, który okresowo “przegląda” harmonogramy zadań i w odpowiednich momentach zleca je do wykonania workerom.
Aby to zobrazować, rozbudujemy nasz przykład o dodatkowe zadania “nadrzędne”, które będzie uruchamiane co 15 minut, i będzie zlecało pobranie danych
o pogodzie dla wszystkich zapisanych miejscowości. W tym celu zaktualizujemy kod w pliku weather/tasks.py.
Wykorzystamy dekorator periodic_task aby stworzyć harmonogram wywołań zadania. W tym przypadku zadanie będzie zlecane do wykonania co 15 minut (o pełnej godzinie, piętnaście minut po pełnej godzinie, w połowie godziny i piętnaście minut przed pełną godziną). Teraz oprócz celery worker musimy uruchomić dodatkowo proces celery beat.
O odpowiedniej porze (wynikającej z ustawionego harmonogramu) nastąpi uruchomienie zadania get_all_weather_data(). Zadanie to
zleci wykonanie kolejnych zadań pobrania danych pogodowych dla poszczególnych miejscowości. Tak to wygląda na konsolach:
Uruchomienie Celery na serwerze
Skorzystamy z demona supervisor, który umożliwia nam zarządzanie i kontrolę nad uruchamianiem
procesów celery na serwerze. Supervisor instalujemy z paczek dystrybucji:
Następnie tworzymy pliki konfiguracyjne dla celery workera i dla celery beata.
/home/maciej/weather to ścieżka do środowiska wirtualnego i aplikacji pogodowej django. Teraz wymuszamy na supervisor odczyt konfiguracji
i zastosowanie zmian:
Możemy używać następujących komend do zatrzymywania/uruchamiania/sprawdzenia stanu poszczególnych programów:
Konteneryzujemy projekt za pomocą Dockera
Konteneryzacja z wykorzystaniem Dockera staje się coraz popularniejsza dlatego przedstawię jeszcze drugi sposób, w jaki można uruchamiać cały projekt przy jego pomocy. Dla osób niezaznajomionych z Dockerem polecam ten wpis w celu zapoznania się z tematem konteneryzacji projektu Django.
Zaczniemy od stworzenia pliku z zależnościami dla projektu pogodowego:
Następnie stworzymy przepis (Dockerfile) na zbudowanie kontenera aplikacji:
I na zakończenie skorzystamy z docker-compose aby stworzyć sobie zestaw kontenerów:
Mając wszystko gotowe, uruchamiamy projekt poprzez:
W pliku konfiguracyjnym djcel/settings.py powinniśmy w przypadku dockera dokonać zmiany adresu brokera:
Przedstawione tutaj rozwiązanie doskonale nadaje się jako przykład do wykorzystania przy deweloperce projektu. Kiedyś w przyszłości mam nadzieję napisać Wam jak projekty Django “wrzucać na produkcje” na serwery VPS, Heroku, Kubernetes.
Dla osób zainteresowanych tradycyjnie udostępniam kod projektu na GitLabie, na którym bazowałem tworząc niniejszy wpis.
Jakiś czas temu na blogu Dawida Kozaka (swoję drogą bardzo polecam i równocześnie pozdrawiam Autora) natrafiłem na ciekawy wpis dotyczący upraszczania kodu J...
Dzisiaj chciałbym pokazać Wam dostępne narzędzia i metody tworzenia plików PDF z poziomu
frameworka Django. Dokumentacja samego frameworka w tym temacie jest...
W niniejszym wpisie chciałbym przedstawić Wam jak za pomocą WebSocket zbudować
web aplikację działającą w czasie rzeczywistym. Dla odróżnienia od wielu przyk...
Zostaw komentarz