German (Deutsch) translation by Nikol Angelowa (you can also view the original English article)
Webanwendungen fangen normalerweise einfach an, können aber ziemlich komplex werden, und die meisten von ihnen überschreiten schnell die Verantwortung, nur auf HTTP-Anfragen zu antworten.
Wenn das passiert, muss man unterscheiden zwischen dem, was sofort passieren muss (normalerweise im HTTP-Request-Lebenszyklus) und dem, was schließlich passieren kann. Warum das? Denn wenn Ihre Anwendung mit Datenverkehr überlastet wird, machen einfache Dinge wie diese den Unterschied.
Vorgänge in einer Webanwendung können als kritische Vorgänge oder Vorgänge zur Anforderungszeit und als Hintergrundaufgaben klassifiziert werden, die außerhalb der Anforderungszeit stattfinden. Diese ordnen sich den oben beschriebenen zu:
- muss sofort erfolgen: Vorgänge zur Anforderungszeit
- muss irgendwann passieren: Hintergrundaufgaben
Vorgänge zur Anforderungszeit können in einem einzigen Anforderungs-/Antwort-Zyklus ausgeführt werden, ohne sich Sorgen machen zu müssen, dass der Vorgang abläuft oder der Benutzer eine schlechte Erfahrung macht. Gängige Beispiele sind CRUD (Create, Read, Update, Delete)-Datenbankoperationen und die Benutzerverwaltung (Login/Logout-Routinen).
Hintergrundaufgaben sind unterschiedlich, da sie in der Regel recht zeitaufwendig und fehleranfällig sind, meist aufgrund von externen Abhängigkeiten. Einige gängige Szenarien bei komplexen Webanwendungen sind:
- Senden von Bestätigungs- oder Aktivitäts-E-Mails
- tägliches Crawlen und Scrapen von Informationen aus verschiedenen Quellen und Speichern dieser
- Datenanalyse durchführen
- Löschen nicht benötigter Ressourcen
- Exportieren von Dokumenten/Fotos in verschiedene Formate
Hintergrundaufgaben sind der Schwerpunkt dieses Tutorials. Das am häufigsten verwendete Programmiermuster für dieses Szenario ist die Producer Consumer Architecture.
Vereinfacht lässt sich diese Architektur so beschreiben:
- Produzenten erstellen Daten oder Aufgaben.
- Aufgaben werden in eine Warteschlange gestellt, die als Aufgabenwarteschlange bezeichnet wird.
- Verbraucher sind dafür verantwortlich, die Daten zu konsumieren oder die Aufgaben auszuführen.
Normalerweise rufen die Verbraucher Aufgaben aus der Warteschlange nach dem First-In-First-Out-Verfahren (FIFO) oder gemäß ihren Prioritäten ab. Die Verbraucher werden auch als Arbeiter bezeichnet, und diesen Begriff werden wir durchgehend verwenden, da er mit der Terminologie der diskutierten Technologien übereinstimmt.
Welche Aufgaben können im Hintergrund bearbeitet werden? Aufgaben, die:
- sind für die Grundfunktionalität der Webanwendung nicht zwingend erforderlich
- können nicht im Anforderungs-/Antwortzyklus ausgeführt werden, da sie langsam sind (E/A-intensiv usw.)
- hängen von externen Ressourcen ab, die möglicherweise nicht verfügbar sind oder sich nicht wie erwartet verhalten
- muss möglicherweise mindestens einmal wiederholt werden
- müssen nach einem Zeitplan ausgeführt werden
Celery ist die De-facto-Wahl für die Verarbeitung von Hintergrundaufgaben im Python/Django-Ökosystem. Es hat eine einfache und klare API und lässt sich wunderbar in Django integrieren. Es unterstützt verschiedene Technologien für die Aufgabenwarteschlange und verschiedene Paradigmen für die Arbeiter.
In diesem Tutorial erstellen wir eine Django-Spielzeug-Webanwendung (die sich mit realen Szenarien befasst), die die Verarbeitung von Hintergrundaufgaben verwendet.
Dinge einrichten
Angenommen, Sie sind bereits mit Python-Paketverwaltung und virtuellen Umgebungen vertraut, dann installieren wir Django:
1 |
$ pip install Django |
Ich habe beschlossen, noch eine weitere Blogging-Anwendung zu erstellen. Der Fokus der Anwendung wird auf Einfachheit liegen. Ein Benutzer kann einfach ein Konto erstellen und ohne großen Aufwand einen Beitrag erstellen und auf der Plattform veröffentlichen.
Richten Sie das quick_publisher Django-Projekt ein:
1 |
$ django-admin startproject quick_publisher
|
Lassen Sie uns die App starten:
1 |
$ cd quick_publisher |
2 |
$ ./manage.py startapp main
|
Wenn ich ein neues Django-Projekt starte, erstelle ich gerne eine main-Anwendung, die unter anderem ein benutzerdefiniertes Benutzermodell enthält. Meistens stoße ich auf Einschränkungen des standardmäßigen Django-User-Modells. Ein benutzerdefiniertes User-Modell bietet uns den Vorteil der Flexibilität.
1 |
# main/models.py
|
2 |
|
3 |
from django.db import models |
4 |
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager |
5 |
|
6 |
|
7 |
class UserAccountManager(BaseUserManager): |
8 |
use_in_migrations = True |
9 |
|
10 |
def _create_user(self, email, password, **extra_fields): |
11 |
if not email: |
12 |
raise ValueError('Email address must be provided') |
13 |
|
14 |
if not password: |
15 |
raise ValueError('Password must be provided') |
16 |
|
17 |
email = self.normalize_email(email) |
18 |
user = self.model(email=email, **extra_fields) |
19 |
user.set_password(password) |
20 |
user.save(using=self._db) |
21 |
return user |
22 |
|
23 |
def create_user(self, email=None, password=None, **extra_fields): |
24 |
return self._create_user(email, password, **extra_fields) |
25 |
|
26 |
def create_superuser(self, email, password, **extra_fields): |
27 |
extra_fields['is_staff'] = True |
28 |
extra_fields['is_superuser'] = True |
29 |
|
30 |
return self._create_user(email, password, **extra_fields) |
31 |
|
32 |
|
33 |
class User(AbstractBaseUser, PermissionsMixin): |
34 |
|
35 |
REQUIRED_FIELDS = [] |
36 |
USERNAME_FIELD = 'email' |
37 |
|
38 |
objects = UserAccountManager() |
39 |
|
40 |
email = models.EmailField('email', unique=True, blank=False, null=False) |
41 |
full_name = models.CharField('full name', blank=True, null=True, max_length=400) |
42 |
is_staff = models.BooleanField('staff status', default=False) |
43 |
is_active = models.BooleanField('active', default=True) |
44 |
|
45 |
def get_short_name(self): |
46 |
return self.email |
47 |
|
48 |
def get_full_name(self): |
49 |
return self.email |
50 |
|
51 |
def __unicode__(self): |
52 |
return self.email |
Lesen Sie unbedingt die Django-Dokumentation, wenn Sie nicht mit der Funktionsweise benutzerdefinierter Benutzermodelle vertraut sind.
Jetzt müssen wir Django anweisen, dieses Benutzermodell anstelle des Standardmodells zu verwenden. Fügen Sie diese Zeile zur Datei quick_publisher/settings.py hinzu:
1 |
AUTH_USER_MODEL = 'main.User' |
Wir müssen auch die main-Anwendung zur Liste INSTALLED_APPS in der Datei quick_publisher/settings.py hinzufügen. Wir können jetzt die Migrationen erstellen, anwenden und einen Superuser erstellen, um sich beim Django-Admin-Panel anmelden zu können:
1 |
$ ./manage.py makemigrations main
|
2 |
$ ./manage.py migrate
|
3 |
$ ./manage.py createsuperuser
|
Lassen Sie uns nun eine separate Django-Anwendung erstellen, die für Beiträge verantwortlich ist:
1 |
$ ./manage.py startapp publish
|
Definieren wir ein einfaches Post-Modell in publisher/models.py:
1 |
from django.db import models |
2 |
from django.utils import timezone |
3 |
from django.contrib.auth import get_user_model |
4 |
|
5 |
|
6 |
class Post(models.Model): |
7 |
author = models.ForeignKey(get_user_model()) |
8 |
created = models.DateTimeField('Created Date', default=timezone.now) |
9 |
title = models.CharField('Title', max_length=200) |
10 |
content = models.TextField('Content') |
11 |
slug = models.SlugField('Slug') |
12 |
|
13 |
def __str__(self): |
14 |
return '"%s" by %s' % (self.title, self.author) |
Das Verbinden des Post-Modells mit dem Django-Administrator erfolgt in der Datei publisher/admin.py wie folgt:
1 |
from django.contrib import admin |
2 |
from .models import Post |
3 |
|
4 |
|
5 |
@admin.register(Post) |
6 |
class PostAdmin(admin.ModelAdmin): |
7 |
pass
|
Lassen Sie uns abschließend die publisher-Anwendung mit unserem Projekt verknüpfen, indem Sie sie zur Liste INSTALLED_APPS hinzufügen.
Wir können jetzt den Server ausführen und zu http://localhost:8000/admin/ gehen und unsere ersten Beiträge erstellen, damit wir etwas zum Spielen haben:
1 |
$ ./manage.py runserver
|
Ich vertraue darauf, dass Sie Ihre Hausaufgaben gemacht und die Beiträge erstellt haben.
Lass uns weitermachen. Der nächste offensichtliche Schritt besteht darin, eine Möglichkeit zum Anzeigen der veröffentlichten Beiträge zu erstellen.
1 |
# publisher/views.py
|
2 |
|
3 |
from django.http import Http404 |
4 |
from django.shortcuts import render |
5 |
from .models import Post |
6 |
|
7 |
|
8 |
def view_post(request, slug): |
9 |
try: |
10 |
post = Post.objects.get(slug=slug) |
11 |
except Post.DoesNotExist: |
12 |
raise Http404("Poll does not exist") |
13 |
|
14 |
return render(request, 'post.html', context={'post': post}) |
Verknüpfen wir unsere neue Ansicht mit einer URL in: quick_publisher/urls.py
1 |
# quick_publisher/urls.py
|
2 |
|
3 |
from django.conf.urls import url |
4 |
from django.contrib import admin |
5 |
|
6 |
from publisher.views import view_post |
7 |
|
8 |
urlpatterns = [ |
9 |
url(r'^admin/', admin.site.urls), |
10 |
url(r'^(?P<slug>[a-zA-Z0-9\-]+)', view_post, name='view_post') |
11 |
]
|
Schließlich erstellen wir die Vorlage, die den Beitrag rendert in: publisher/templates/post.html
1 |
<!DOCTYPE html>
|
2 |
<html>
|
3 |
<head lang="en"> |
4 |
<meta charset="UTF-8"> |
5 |
<title></title>
|
6 |
</head>
|
7 |
<body>
|
8 |
<h1>{{ post.title }}</h1> |
9 |
<p>{{ post.content }}</p> |
10 |
<p>Published by {{ post.author.full_name }} on {{ post.created }}</p> |
11 |
</body>
|
12 |
</html>
|
Wir können jetzt im Browser zu http://localhost:8000/the-slug-of-the-post-you-created/ gehen. Es ist nicht gerade ein Wunder des Webdesigns, aber gut aussehende Beiträge zu erstellen, würde den Rahmen dieses Tutorials sprengen.
Senden von Bestätigungs-E-Mails
Hier ist das klassische Szenario:
- Sie erstellen ein Konto auf einer Plattform.
- Sie geben eine auf der Plattform eindeutig zu identifizierende E-Mail-Adresse an.
- Die Plattform überprüft, ob Sie tatsächlich der Inhaber der E-Mail-Adresse sind, indem sie eine E-Mail mit einem Bestätigungslink sendet.
- Bis Sie die Verifizierung durchgeführt haben, können Sie die Plattform nicht (vollständig) nutzen.
Fügen wir ein is_verified-Flag und die verification_uuid zum User-Modell hinzu:
1 |
# main/models.py
|
2 |
import uuid |
3 |
|
4 |
|
5 |
class User(AbstractBaseUser, PermissionsMixin): |
6 |
|
7 |
REQUIRED_FIELDS = [] |
8 |
USERNAME_FIELD = 'email' |
9 |
|
10 |
objects = UserAccountManager() |
11 |
|
12 |
email = models.EmailField('email', unique=True, blank=False, null=False) |
13 |
full_name = models.CharField('full name', blank=True, null=True, max_length=400) |
14 |
is_staff = models.BooleanField('staff status', default=False) |
15 |
is_active = models.BooleanField('active', default=True) |
16 |
is_verified = models.BooleanField('verified', default=False) # Add the `is_verified` flag |
17 |
verification_uuid = models.UUIDField('Unique Verification UUID', default=uuid.uuid4) |
18 |
|
19 |
def get_short_name(self): |
20 |
return self.email |
21 |
|
22 |
def get_full_name(self): |
23 |
return self.email |
24 |
|
25 |
def __unicode__(self): |
26 |
return self.email |
Lassen Sie uns diese Gelegenheit nutzen, um dem Administrator das Benutzermodell hinzuzufügen:
1 |
from django.contrib import admin |
2 |
from .models import User |
3 |
|
4 |
|
5 |
@admin.register(User) |
6 |
class UserAdmin(admin.ModelAdmin): |
7 |
pass
|
Lassen Sie uns die Änderungen in der Datenbank widerspiegeln:
1 |
$ ./manage.py makemigrations
|
2 |
$ ./manage.py migrate
|
Wir müssen jetzt einen Code schreiben, der eine E-Mail sendet, wenn eine Benutzerinstanz erstellt wird. Dafür gibt es Django-Signale, und dies ist eine perfekte Gelegenheit, dieses Thema anzusprechen.
Signale werden ausgelöst, bevor/nachdem bestimmte Ereignisse in der Anwendung auftreten. Wir können Callback-Funktionen definieren, die automatisch ausgelöst werden, wenn die Signale ausgelöst werden. Um einen Callback-Trigger zu machen, müssen wir ihn zuerst mit einem Signal verbinden.
Wir erstellen einen Rückruf, der ausgelöst wird, nachdem ein Benutzermodell erstellt wurde. Wir werden diesen Code nach der Definition des User-Modells in: main/models.py hinzufügen
1 |
from django.db.models import signals |
2 |
from django.core.mail import send_mail |
3 |
|
4 |
|
5 |
def user_post_save(sender, instance, signal, *args, **kwargs): |
6 |
if not instance.is_verified: |
7 |
# Send verification email
|
8 |
send_mail( |
9 |
'Verify your QuickPublisher account', |
10 |
'Follow this link to verify your account: '
|
11 |
'http://localhost:8000%s' % reverse('verify', kwargs={'uuid': str(instance.verification_uuid)}), |
12 |
'from@quickpublisher.dev', |
13 |
[instance.email], |
14 |
fail_silently=False, |
15 |
)
|
16 |
|
17 |
signals.post_save.connect(user_post_save, sender=User) |
Was wir hier gemacht haben, ist, dass wir eine user_post_save-Funktion definiert und mit dem post_save-Signal verbunden haben (das nach dem Speichern eines Modells ausgelöst wird), das vom User-Modell gesendet wird.
Django versendet nicht nur selbst E-Mails; es muss an einen E-Mail-Dienst gebunden sein. Der Einfachheit halber können Sie Ihre Gmail-Anmeldeinformationen in quick_publisher/settings.py hinzufügen oder Ihren bevorzugten E-Mail-Anbieter hinzufügen.
So sieht die Gmail-Konfiguration aus:
1 |
EMAIL_USE_TLS = True |
2 |
EMAIL_HOST = 'smtp.gmail.com' |
3 |
EMAIL_HOST_USER = '<YOUR_GMAIL_USERNAME>@gmail.com' |
4 |
EMAIL_HOST_PASSWORD = '<YOUR_GMAIL_PASSWORD>' |
5 |
EMAIL_PORT = 587 |
Um die Dinge zu testen, gehen Sie in das Admin-Panel und erstellen Sie einen neuen Benutzer mit einer gültigen E-Mail-Adresse, die Sie schnell überprüfen können. Wenn alles gut gegangen ist, erhalten Sie eine E-Mail mit einem Bestätigungslink. Die Überprüfungsroutine ist noch nicht fertig.
So verifizieren Sie das Konto:
1 |
# main/views.py
|
2 |
|
3 |
from django.http import Http404 |
4 |
from django.shortcuts import render, redirect |
5 |
from .models import User |
6 |
|
7 |
|
8 |
def home(request): |
9 |
return render(request, 'home.html') |
10 |
|
11 |
|
12 |
def verify(request, uuid): |
13 |
try: |
14 |
user = User.objects.get(verification_uuid=uuid, is_verified=False) |
15 |
except User.DoesNotExist: |
16 |
raise Http404("User does not exist or is already verified") |
17 |
|
18 |
user.is_verified = True |
19 |
user.save() |
20 |
|
21 |
return redirect('home') |
Schließen Sie die Ansichten an: quick_publisher/urls.py
1 |
# quick_publisher/urls.py
|
2 |
|
3 |
from django.conf.urls import url |
4 |
from django.contrib import admin |
5 |
|
6 |
from publisher.views import view_post |
7 |
from main.views import home, verify |
8 |
|
9 |
urlpatterns = [ |
10 |
url(r'^$', home, name='home'), |
11 |
url(r'^admin/', admin.site.urls), |
12 |
url(r'^verify/(?P<uuid>[a-z0-9\-]+)/', verify, name='verify'), |
13 |
url(r'^(?P<slug>[a-zA-Z0-9\-]+)', view_post, name='view_post') |
14 |
]
|
Denken Sie auch daran, eine home.html-Datei unter main/templates/home.html zu erstellen. Es wird von der home-Ansicht gerendert.
Versuchen Sie, das gesamte Szenario noch einmal auszuführen. Wenn alles in Ordnung ist, erhalten Sie eine E-Mail mit einer gültigen Bestätigungs-URL. Wenn Sie der URL folgen und dann beim Administrator nachsehen, können Sie sehen, wie das Konto verifiziert wurde.
Asynchrones Senden von E-Mails
Hier ist das Problem mit dem, was wir bisher gemacht haben. Möglicherweise haben Sie bemerkt, dass das Erstellen eines Benutzers etwas langsam ist. Das liegt daran, dass Django die Verifizierungs-E-Mail innerhalb der Anfragezeit sendet.
So funktioniert es: Wir senden die Benutzerdaten an die Django-Anwendung. Die Anwendung erstellt ein User-Modell und stellt dann eine Verbindung zu Gmail (oder einem anderen von Ihnen ausgewählten Dienst) her. Django wartet auf die Antwort und gibt erst dann eine Antwort an unseren Browser zurück.
Hier kommt Celery ins Spiel. Stellen Sie zunächst sicher, dass es installiert ist:
1 |
$ pip install Celery |
Wir müssen jetzt eine Sellerie-Anwendung in unserer Django-Anwendung erstellen:
1 |
# quick_publisher/celery.py
|
2 |
|
3 |
import os |
4 |
from celery import Celery |
5 |
|
6 |
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'quick_publisher.settings') |
7 |
|
8 |
app = Celery('quick_publisher') |
9 |
app.config_from_object('django.conf:settings') |
10 |
|
11 |
# Load task modules from all registered Django app configs.
|
12 |
app.autodiscover_tasks() |
Sellerie ist eine Aufgabenwarteschlange. Es empfängt Aufgaben von unserer Django-Anwendung und führt sie im Hintergrund aus. Sellerie muss mit anderen Diensten kombiniert werden, die als Broker fungieren.
Broker vermitteln das Senden von Nachrichten zwischen der Webanwendung und Sellerie. In diesem Tutorial verwenden wir Redis. Redis ist einfach zu installieren und wir können ohne großen Aufwand problemlos damit beginnen.
Sie können Redis installieren, indem Sie den Anweisungen auf der Redis-Schnellstartseite folgen. Sie müssen die Redis Python-Bibliothek, pip install redis und das für die Verwendung von Redis und Celery erforderliche Bundle installieren: pip install celery[redis].
Starten Sie den Redis-Server in einer separaten Konsole wie folgt: $ redis-server
Fügen wir die Celery/Redis-bezogenen Konfigurationen zu quick_publisher/settings.py hinzu:
1 |
# REDIS related settings
|
2 |
REDIS_HOST = 'localhost' |
3 |
REDIS_PORT = '6379' |
4 |
BROKER_URL = 'redis://' + REDIS_HOST + ':' + REDIS_PORT + '/0' |
5 |
BROKER_TRANSPORT_OPTIONS = {'visibility_timeout': 3600} |
6 |
CELERY_RESULT_BACKEND = 'redis://' + REDIS_HOST + ':' + REDIS_PORT + '/0' |
Bevor etwas in Sellerie ausgeführt werden kann, muss es als Aufgabe deklariert werden.
So geht's:
1 |
# main/tasks.py
|
2 |
|
3 |
import logging |
4 |
|
5 |
from django.urls import reverse |
6 |
from django.core.mail import send_mail |
7 |
from django.contrib.auth import get_user_model |
8 |
from quick_publisher.celery import app |
9 |
|
10 |
|
11 |
@app.task |
12 |
def send_verification_email(user_id): |
13 |
UserModel = get_user_model() |
14 |
try: |
15 |
user = UserModel.objects.get(pk=user_id) |
16 |
send_mail( |
17 |
'Verify your QuickPublisher account', |
18 |
'Follow this link to verify your account: '
|
19 |
'http://localhost:8000%s' % reverse('verify', kwargs={'uuid': str(user.verification_uuid)}), |
20 |
'from@quickpublisher.dev', |
21 |
[user.email], |
22 |
fail_silently=False, |
23 |
)
|
24 |
except UserModel.DoesNotExist: |
25 |
logging.warning("Tried to send verification email to non-existing user '%s'" % user_id) |
Was wir hier getan haben, ist Folgendes: Wir haben die Funktion zum Senden von Bestätigungs-E-Mails in eine andere Datei namens task.py verschoben.
Ein paar Anmerkungen:
- Der Name der Datei ist wichtig. Celery durchläuft alle Apps in
INSTALLED_APPSund registriert die Aufgaben in dentasks.py-Dateien. - Beachten Sie, wie wir die Funktion
send_verification_emailmit@app.taskausgestattet haben. Dies teilt Celery mit, dass dies eine Aufgabe ist, die in der Aufgabenwarteschlange ausgeführt wird. - Beachten Sie, wie wir als Argument
user_idund nicht alsUser-Objekt erwarten. Dies liegt daran, dass wir beim Senden der Aufgaben an Celery möglicherweise Probleme beim Serialisieren komplexer Objekte haben. Es ist am besten, sie einfach zu halten.
Zurück zu main/models.py wird der Signalcode zu:
1 |
from django.db.models import signals |
2 |
from main.tasks import send_verification_email |
3 |
|
4 |
|
5 |
def user_post_save(sender, instance, signal, *args, **kwargs): |
6 |
if not instance.is_verified: |
7 |
# Send verification email
|
8 |
send_verification_email.delay(instance.pk) |
9 |
|
10 |
signals.post_save.connect(user_post_save, sender=User) |
Beachten Sie, wie wir die .delay-Methode für das Task-Objekt aufrufen. Das heißt, wir schicken die Aufgabe an Celery und warten nicht auf das Ergebnis. Wenn wir stattdessen send_verification_email(instance.pk) verwenden, würden wir es immer noch an Celery senden, aber warten, bis die Aufgabe abgeschlossen ist, was nicht das ist, was wir wollen.
Bevor Sie einen neuen Benutzer erstellen, gibt es einen Haken. Sellerie ist ein Dienst, und wir müssen ihn starten. Öffnen Sie eine neue Konsole, stellen Sie sicher, dass Sie die entsprechende virtuelle Umgebung aktivieren, und navigieren Sie zum Projektordner.
1 |
$ celery worker -A quick_publisher --loglevel=debug --concurrency=4 |
Damit starten vier Sellerie-Prozessarbeiter. Ja, jetzt können Sie endlich einen anderen Benutzer erstellen. Beachten Sie, dass es keine Verzögerung gibt, und achten Sie auf die Protokolle in der Celery-Konsole, um zu sehen, ob die Aufgaben ordnungsgemäß ausgeführt werden. Das sollte ungefähr so aussehen:
1 |
[2017-04-28 15:00:09,190: DEBUG/MainProcess] Task accepted: main.tasks.send_verification_email[f1f41e1f-ca39-43d2-a37d-9de085dc99de] pid:62065
|
2 |
[2017-04-28 15:00:11,740: INFO/PoolWorker-2] Task main.tasks.send_verification_email[f1f41e1f-ca39-43d2-a37d-9de085dc99de] succeeded in 2.5500912349671125s: None |
Periodische Aufgaben mit Sellerie
Hier ist ein weiteres häufiges Szenario. Die meisten ausgereiften Webanwendungen senden ihren Benutzern E-Mails über den Lebenszyklus, um sie zu beschäftigen. Einige gängige Beispiele für Lebenszyklus-E-Mails:
- Monatliche Reportagen
- Aktivitätsbenachrichtigungen (Gefällt mir, Freundschaftsanfragen usw.)
- Erinnerungen an bestimmte Aktionen ("Vergiss nicht, dein Konto zu aktivieren")
Hier ist, was wir in unserer App tun werden. Wir werden zählen, wie oft jeder Beitrag angesehen wurde, und einen täglichen Bericht an den Autor senden. Einmal jeden Tag gehen wir alle Benutzer durch, rufen ihre Beiträge ab und senden eine E-Mail mit einer Tabelle mit den Beiträgen und der Anzahl der Aufrufe.
Ändern wir das Post-Modell, damit wir das Szenario mit der Anzahl der Aufrufe berücksichtigen können.
1 |
class Post(models.Model): |
2 |
author = models.ForeignKey(User) |
3 |
created = models.DateTimeField('Created Date', default=timezone.now) |
4 |
title = models.CharField('Title', max_length=200) |
5 |
content = models.TextField('Content') |
6 |
slug = models.SlugField('Slug') |
7 |
view_count = models.IntegerField("View Count", default=0) |
8 |
|
9 |
def __str__(self): |
10 |
return '"%s" by %s' % (self.title, self.author) |
Wie immer, wenn wir ein Modell ändern, müssen wir die Datenbank migrieren:
1 |
$ ./manage.py makemigrations
|
2 |
|
3 |
$ ./manage.py migrate
|
Lassen Sie uns auch die Django-Ansicht view_post ändern, um Ansichten zu zählen:
1 |
def view_post(request, slug): |
2 |
try: |
3 |
post = Post.objects.get(slug=slug) |
4 |
except Post.DoesNotExist: |
5 |
raise Http404("Poll does not exist") |
6 |
|
7 |
post.view_count += 1 |
8 |
post.save() |
9 |
|
10 |
return render(request, 'post.html', context={'post': post}) |
Es wäre nützlich, den view_count in der Vorlage anzuzeigen. Fügen Sie dies <p>Viewed {{ post.view_count }} times</p> irgendwo in der Datei publisher/templates/post.html hinzu. Machen Sie jetzt ein paar Ansichten zu einem Beitrag und sehen Sie, wie der Zähler steigt.
Lassen Sie uns eine Sellerie-Aufgabe erstellen. Da es sich um Posts handelt, platziere ich es in publisher/tasks.py:
1 |
from django.template import Template, Context |
2 |
from django.core.mail import send_mail |
3 |
from django.contrib.auth import get_user_model |
4 |
from quick_publisher.celery import app |
5 |
from publisher.models import Post |
6 |
|
7 |
|
8 |
REPORT_TEMPLATE = """ |
9 |
Here's how you did till now:
|
10 |
|
11 |
{% for post in posts %}
|
12 |
"{{ post.title }}": viewed {{ post.view_count }} times |
|
13 |
|
14 |
{% endfor %}
|
15 |
"""
|
16 |
|
17 |
|
18 |
@app.task |
19 |
def send_view_count_report(): |
20 |
for user in get_user_model().objects.all(): |
21 |
posts = Post.objects.filter(author=user) |
22 |
if not posts: |
23 |
continue
|
24 |
|
25 |
template = Template(REPORT_TEMPLATE) |
26 |
|
27 |
send_mail( |
28 |
'Your QuickPublisher Activity', |
29 |
template.render(context=Context({'posts': posts})), |
30 |
'from@quickpublisher.dev', |
31 |
[user.email], |
32 |
fail_silently=False, |
33 |
)
|
Denken Sie jedes Mal, wenn Sie Änderungen an den Celery-Aufgaben vornehmen, daran, den Celery-Prozess neu zu starten. Sellerie muss Aufgaben entdecken und neu laden. Bevor wir eine periodische Aufgabe erstellen, sollten wir dies in der Django-Shell testen, um sicherzustellen, dass alles wie beabsichtigt funktioniert:
1 |
$ ./manage.py shell |
2 |
|
3 |
In [1]: from publisher.tasks import send_view_count_report |
4 |
|
5 |
In [2]: send_view_count_report.delay() |
Hoffentlich haben Sie einen netten kleinen Bericht in Ihrer E-Mail erhalten.
Lassen Sie uns nun eine periodische Aufgabe erstellen. Öffnen Sie quick_publisher/celery.py und registrieren Sie die regelmäßigen Aufgaben:
1 |
# quick_publisher/celery.py
|
2 |
|
3 |
import os |
4 |
from celery import Celery |
5 |
from celery.schedules import crontab |
6 |
|
7 |
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'quick_publisher.settings') |
8 |
|
9 |
app = Celery('quick_publisher') |
10 |
app.config_from_object('django.conf:settings') |
11 |
|
12 |
# Load task modules from all registered Django app configs.
|
13 |
app.autodiscover_tasks() |
14 |
|
15 |
app.conf.beat_schedule = { |
16 |
'send-report-every-single-minute': { |
17 |
'task': 'publisher.tasks.send_view_count_report', |
18 |
'schedule': crontab(), # change to `crontab(minute=0, hour=0)` if you want it to run daily at midnight |
19 |
},
|
20 |
}
|
Bisher haben wir einen Zeitplan erstellt, der die Aufgabe publisher.tasks.send_view_count_report jede Minute ausführen würde, wie durch die crontab()-Notation angegeben. Sie können auch verschiedene Celery Crontab-Zeitpläne angeben.
Öffnen Sie eine andere Konsole, aktivieren Sie die entsprechende Umgebung und starten Sie den Celery Beat-Dienst.
1 |
$ celery -A quick_publisher beat |
Die Aufgabe des Beat-Dienstes besteht darin, Aufgaben in Celery gemäß dem Zeitplan zu pushen. Berücksichtigen Sie, dass der Zeitplan die Aufgabe send_view_count_report gemäß dem Setup jede Minute ausführen lässt. Es eignet sich gut zum Testen, wird jedoch nicht für eine reale Webanwendung empfohlen.
Aufgaben zuverlässiger machen
Aufgaben werden häufig verwendet, um unzuverlässige Vorgänge auszuführen, Vorgänge, die von externen Ressourcen abhängig sind oder aus verschiedenen Gründen leicht fehlschlagen können. Hier ist eine Richtlinie, um sie zuverlässiger zu machen:
- Machen Sie Aufgaben idempotent. Eine idempotente Aufgabe ist eine Aufgabe, die, wenn sie auf halbem Weg gestoppt wird, den Status des Systems in keiner Weise ändert. Die Aufgabe nimmt entweder vollständige Änderungen am System vor oder gar keine.
- Wiederholen Sie die Aufgaben. Wenn die Aufgabe fehlschlägt, sollten Sie sie immer wieder versuchen, bis sie erfolgreich ausgeführt wurde. Sie können dies in Sellerie mit Celery Retry tun. Eine weitere interessante Sache ist der Exponential Backoff-Algorithmus. Dies kann sich als nützlich erweisen, wenn Sie daran denken, die unnötige Belastung des Servers durch wiederholte Aufgaben zu begrenzen.
Schlussfolgerungen
Ich hoffe, dies war ein interessantes Tutorial für Sie und eine gute Einführung in die Verwendung von Celery mit Django.
Hier sind einige Schlussfolgerungen, die wir ziehen können:
- Es hat sich bewährt, unzuverlässige und zeitaufwendige Aufgaben außerhalb der Anforderungszeit zu halten.
- Langfristige Aufgaben sollten im Hintergrund von Worker-Prozessen (oder anderen Paradigmen) ausgeführt werden.
- Hintergrundaufgaben können für verschiedene Aufgaben verwendet werden, die für die grundlegende Funktionsweise der Anwendung nicht kritisch sind.
- Sellerie kann auch periodische Aufgaben mit dem
celery beat-Service erledigen. - Aufgaben können zuverlässiger sein, wenn sie idempotent gemacht und erneut versucht werden (möglicherweise mit exponentiellem Backoff).



