Le applicazioni Web di solito iniziano in modo semplice ma possono diventare piuttosto complesse e la maggior parte di esse supera rapidamente la responsabilità di rispondere solo alle richieste HTTP.
Quando ciò accade, bisogna fare una distinzione tra ciò che deve accadere all'istante (di solito nel ciclo di vita della richiesta HTTP) e cosa può accadere alla fine. Perché? Bene, perché quando la tua applicazione si sovraccarica di traffico, cose semplici come questa fanno la differenza.
Le operazioni in un'applicazione Web possono essere classificate come operazioni critiche o richieste in tempo reale e attività in background, quelle che si verificano al di fuori del tempo richiesto. Queste mappe sono quelle sopra descritte:
Le operazioni in fase di richiesta possono essere eseguite su un singolo ciclo di richiesta / risposta senza preoccuparsi del fatto che l'operazione scada o che l'utente possa avere un'esperienza negativa. Esempi comuni includono operazioni di database CRUD (creazione, lettura, aggiornamento, eliminazione) e gestione utenti (routine di login / disconnessione).
Le attività in background sono diverse in quanto di solito richiedono molto tempo e sono soggette a errori, principalmente a causa di dipendenze esterne. Alcuni scenari comuni tra le applicazioni Web complesse includono:
Le attività in background sono l'obiettivo principale di questo tutorial. Il modello di programmazione più comune utilizzato per questo scenario è Producer Consumer Architecture.
In termini semplici, questa architettura può essere descritta in questo modo:
Di solito, i consumatori recuperano le attività dalla coda in modalità FIFO (First-in-first-out) o in base alle loro priorità. Anche i consumatori sono indicati come lavoratori, e questo è il termine che useremo per tutto, poiché è coerente con la terminologia utilizzata dalle tecnologie discusse.
Che tipo di attività possono essere elaborate in background? Compiti che:
Celery è la scelta di fatto per l'elaborazione di attività in background nell'ecosistema Python / Django. Ha un'API semplice e chiara e si integra magnificamente con Django. Supporta varie tecnologie per la coda di attività e vari paradigmi per i lavoratori.
In questo tutorial creeremo un'applicazione web di giocattoli Django (che si occupa di scenari del mondo reale) che utilizza l'elaborazione di attività in background.
Supponendo che tu abbia già familiarità con la gestione dei pacchetti Python e gli ambienti virtuali, installiamo Django:
$ pip installa Django
Ho deciso di creare un'altra applicazione di blogging. Il focus dell'applicazione sarà sulla semplicità. Un utente può semplicemente creare un account e senza troppa confusione può creare un post e pubblicarlo sulla piattaforma.
Configura il quick_publisher
Progetto Django:
$ django-admin startproject quick_publisher
Iniziamo l'app:
$ cd quick_publisher $ ./manage.py startapp main
Quando si avvia un nuovo progetto Django, mi piace creare un principale
applicazione che contiene, tra le altre cose, un modello utente personalizzato. Più spesso, ho riscontrato limitazioni del Django predefinito Utente
modello. Avere una consuetudine Utente
il modello ci dà il vantaggio della flessibilità.
# main / models.py dai modelli di importazione django.db da django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, class BaseUserManager UserAccountManager (BaseUserManager): use_in_migrations = True def _create_user (self, email, password, ** extra_fields): if non email: raise ValueError ('Indirizzo email deve essere fornito') se non password: genera ValueError ('Password deve essere fornita') email = self.normalize_email (email) user = self.model (email = email, ** extra_fields) user.set_password (password) user.save (using = self._db) return user def create_user (self, email = None, password = None, ** extra_fields): return self._create_user (email, password, ** extra_fields) def create_superuser (self, email, password, ** extra_fields): extra_fields ['is_staff'] = True extra_fields ['is_superuser'] = True return self._create_user (email, password, ** extra_fields) class Utente (AbstractBaseUser, PermissionsMixin): REQUIRED_FIELDS = [] USERNAME_FIELD = oggetti 'email' = UserAccountManager () email = models.EmailField ('email', unique = True, blank = False, null = False) full_name = models.CharField ('nome completo', blank = True, null = True, max_length = 400) is_staff = models.BooleanField ('stato personale') , default = False) is_active = models.BooleanField ('active', default = True) def get_short_name (self): return self.email def get_full_name (self): return self.email def __unicode __ (self): return self.email
Assicurati di controllare la documentazione di Django se non hai familiarità con il funzionamento dei modelli utente personalizzati.
Ora dobbiamo dire a Django di usare questo modello utente invece di quello predefinito. Aggiungi questa linea al quick_publisher / settings.py
file:
AUTH_USER_MODEL = 'main.User'
Abbiamo anche bisogno di aggiungere il principale
applicazione al INSTALLED_APPS
lista in quick_publisher / settings.py
file. Ora possiamo creare le migrazioni, applicarle e creare un superutente per poter accedere al pannello di amministrazione di Django:
$ ./manage.py makemigrations main $ ./manage.py migrare $ ./manage.py creauperuser
Creiamo ora un'applicazione Django separata che è responsabile per i post:
$ ./manage.py startapp publish
Definiamo un semplice modello Post in Editore / models.py
:
da django.db importare modelli dal fuso orario di importazione di django.utils da django.contrib.auth import classe get_user_model Post (models.Model): author = models.ForeignKey (get_user_model ()) created = models.DateTimeField ('Data di creazione', default = timezone.now) title = models.CharField ('Title', max_length = 200) content = models.TextField ('Content') slug = models.SlugField ('Slug') def __str __ (self): return '"% s "di% s '% (self.title, self.author)
Agganciare il Inviare
il modello con l'admin di Django è fatto nel Editore / admin.py
file come questo:
da django.contrib import admin da .models import Post @ admin.register (Post) classe PostAdmin (admin.ModelAdmin): pass
Finalmente, agganciamo il editore
applicazione con il nostro progetto aggiungendolo al INSTALLED_APPS
elenco.
Ora possiamo eseguire il server e dirigerci verso http: // localhost: 8000 / admin /
e crea i nostri primi post in modo da avere qualcosa con cui giocare:
$ ./manage.py runserver
Credo che tu abbia fatto i compiti e tu abbia creato i post.
Andiamo avanti. Il prossimo passo ovvio è creare un modo per visualizzare i post pubblicati.
# publisher / views.py da django.http importa Http404 da django.shortcuts import rendering da .models import Post def view_post (request, slug): prova: post = Post.objects.get (slug = slug) tranne Post.DoesNotExist: solleva Http404 ("Sondaggio inesistente") return render (request, 'post.html', context = 'post': post)
Associamo la nostra nuova vista con un URL in: quick_publisher / urls.py
# quick_publisher / urls.py dall'URL di importazione django.conf.urls da django.contrib import admin da publish.views import view_post urlpatterns = [url (r '^ admin /', admin.site.urls), url (r '^ (? P[a-zA-Z0-9 \ -] +) ', view_post, name = "view_post")]
Infine, creiamo il modello che esegue il rendering del post in: Editore / templates / post.html
titolo del post
post.content
Pubblicato da post.author.full_name su post.created
Ora possiamo andare a http: // localhost: 8000 / the-slug-of-the-post-creato / nel browser. Non è esattamente un miracolo del web design, ma creare post di bell'aspetto va oltre lo scopo di questo tutorial.
Ecco lo scenario classico:
Aggiungiamo un is_verified
bandiera e il verification_uuid
sul Utente
modello:
# main / models.py import Uuid class Utente (AbstractBaseUser, PermissionsMixin): REQUIRED_FIELDS = [] USERNAME_FIELD = 'email' objects = UserAccountManager () email = models.EmailField ('email', unique = True, blank = False, null = False) full_name = models.CharField ('nome completo', blank = True, null = True, max_length = 400) is_staff = models.BooleanField ('staff status', default = False) is_active = models.BooleanField ('active', default = True) is_verified = models.BooleanField ('verified', default = False) # Aggiungi il flag 'is_verified' verify_uuid = models.UUIDField ('UUID verifica unica', default = uuid.uuid4) def get_short_name (self): return self.email def get_full_name (self): return self.email def __unicode __ (self): return self.email
Usiamo questa occasione per aggiungere il modello User all'admin:
da django.contrib import admin da .models import User @ admin.register (User) classe UserAdmin (admin.ModelAdmin): pass
Facciamo riflettere le modifiche nel database:
$ ./manage.py makemigrations $ ./manage.py migrare
Ora dobbiamo scrivere una parte di codice che invia un'email quando viene creata un'istanza utente. Questo è ciò che i segnali di Django servono, e questa è un'occasione perfetta per toccare questo argomento.
I segnali vengono attivati prima / dopo determinati eventi si verificano nell'applicazione. Possiamo definire le funzioni di callback che vengono attivate automaticamente quando vengono emessi i segnali. Per attivare un callback, dobbiamo prima collegarlo a un segnale.
Creeremo un callback che verrà attivato dopo la creazione di un modello utente. Aggiungeremo questo codice dopo il Utente
definizione del modello in: principale / models.py
da django.db.models importare segnali da django.core.mail import send_mail def user_post_save (mittente, istanza, segnale, * args, ** kwargs): se non instance.is_verified: # Invia email di verifica send_mail ('Verifica il tuo account QuickPublisher ',' Segui questo link per verificare il tuo account: "http: // localhost: 8000% s '% reverse (' verify ', kwargs = ' uuid ': str (instance.verification_uuid)),' da @ quickpublisher. dev ', [instance.email], fail_silently = False,) signals.post_save.connect (user_post_save, sender = Utente)
Quello che abbiamo fatto qui è che abbiamo definito a user_post_save
funzione e collegato al post_save
segnale (uno che viene attivato dopo che un modello è stato salvato) inviato dal Utente
modello.
Django non invia solo e-mail da solo; ha bisogno di essere legato a un servizio di posta elettronica. Per semplicità, puoi aggiungere le tue credenziali di Gmail in quick_publisher / settings.py
, oppure puoi aggiungere il tuo fornitore di posta elettronica preferito.
Ecco come si presenta la configurazione di Gmail:
EMAIL_USE_TLS = True EMAIL_HOST = 'smtp.gmail.com' EMAIL_HOST_USER = '@ gmail.com 'EMAIL_HOST_PASSWORD =' 'EMAIL_PORT = 587
Per testare le cose, vai nel pannello di amministrazione e crea un nuovo utente con un indirizzo email valido che puoi controllare rapidamente. Se tutto è andato bene, riceverai un'email con un link di verifica. La routine di verifica non è ancora pronta.
Ecco come verificare l'account:
# main / views.py da django.http importa Http404 da django.shortcuts importazione render, reindirizzamento da .models import User def home (richiesta): return render (request, 'home.html') def verify (request, uuid): provare: utente = User.objects.get (verify_uuid = uuid, is_verified = False) tranne User.DoesNotExist: genera Http404 ("L'utente non esiste o è già verificato") user.is_verified = True user.save () return redirect ( 'casa')
Agganciare le viste in: quick_publisher / urls.py
# quick_publisher / urls.py dall'URL di importazione django.conf.urls da django.contrib import admin da editore.viste import view_post da main.views import home, verifica urlpatterns = [url (r '^ $', home, name = "" home "), url (r '^ admin /', admin.site.urls), url (r '^ verify / (? P[a-z0-9 \ -] +) / ', verify, name = "verify"), url (r' ^ (? P [a-zA-Z0-9 \ -] +) ', view_post, name = "view_post")]
Inoltre, ricorda di creare un home.html
file sotto principale / templates / home.html
. Sarà reso dal casa
vista.
Prova a eseguire di nuovo l'intero scenario. Se tutto va bene, riceverai un'email con un URL di verifica valido. Se segui l'URL e poi accedi all'amministratore, puoi vedere come è stato verificato l'account.
Ecco il problema con ciò che abbiamo fatto finora. Avrai notato che creare un utente è un po 'lento. Questo perché Django invia l'email di verifica entro il tempo richiesto.
Ecco come funziona: inviamo i dati dell'utente all'applicazione Django. L'applicazione crea a Utente
modella e quindi crea una connessione a Gmail (o un altro servizio selezionato). Django attende la risposta e solo allora restituisce una risposta al nostro browser.
Qui è dove entra in gioco Celery. Innanzitutto, assicurati che sia installato:
$ pip installa Celery
Ora dobbiamo creare un'applicazione Celery nella nostra applicazione Django:
# quick_publisher / celery.py import os da Celery import Celery os.environ.setdefault ('DJANGO_SETTINGS_MODULE', 'quick_publisher.settings') app = Celery ('quick_publisher') app.config_from_object ('django.conf: settings') # Load moduli di attività da tutte le configurazioni di app Django registrate. app.autodiscover_tasks ()
Celery è una coda di attività. Riceve attività dalla nostra applicazione Django e le eseguirà in background. Il sedano deve essere associato ad altri servizi che fungono da intermediari.
I broker intermediano l'invio di messaggi tra l'applicazione Web e Celery. In questo tutorial, useremo Redis. Redis è facile da installare e possiamo facilmente iniziare senza troppa confusione.
È possibile installare Redis seguendo le istruzioni sulla pagina Avvio rapido di Redis. Avrai bisogno di installare la libreria Redis Python, pip install redis
, e il pacchetto necessario per l'utilizzo di Redis e Celery: pip installa sedano [redis]
.
Avvia il server Redis in una console separata come questa: $ redis-server
Aggiungiamo le configurazioni relative a Celery / Redis in quick_publisher / settings.py
:
# REDIS related settings REDIS_HOST = 'localhost' REDIS_PORT = '6379' BROKER_URL = 'redis: //' + REDIS_HOST + ':' + REDIS_PORT + '/ 0' BROKER_TRANSPORT_OPTIONS = 'visibility_timeout': 3600 CELERY_RESULT_BACKEND = 'redis: / / '+ REDIS_HOST +': '+ REDIS_PORT +' / 0 '
Prima che tutto possa essere eseguito in Celery, deve essere dichiarato come attività.
Ecco come fare:
# main / tasks.py import logging da django.urls import reverse da django.core.mail import send_mail from django.contrib.auth import get_user_model dall'app di importazione quick_publisher.celery @ app.task def send_verification_email (user_id): UserModel = get_user_model ( ) try: user = UserModel.objects.get (pk = user_id) send_mail ('Verifica il tuo account QuickPublisher', 'Segui questo link per verificare il tuo account: "http: // localhost: 8000% s'% reverse ('verify') , kwargs = 'uuid': str (user.verification_uuid)), '[email protected]', [user.email], fail_silently = False,) tranne UserModel.DoesNotExist: logging.warning ("Tentativo di inviare la verifica email a utente non esistente '% s' "% user_id)
Quello che abbiamo fatto qui è questo: abbiamo spostato la funzionalità di email di verifica dell'invio in un altro file chiamato tasks.py
.
Alcune note:
INSTALLED_APPS
e registra i compiti in tasks.py
File.Invia email di verifica
funzione con @ app.task
. Questo dice a Celery che questa è un'attività che verrà eseguita nella coda dei task.ID utente
piuttosto che a Utente
oggetto. Questo perché potremmo avere problemi nel serializzare oggetti complessi quando si inviano le attività a Celery. È meglio mantenerli semplici.Tornando a principale / models.py
, il codice del segnale si trasforma in:
da django.db.models import segnali da main.tasks import send_verification_email def user_post_save (mittente, istanza, segnale, * args, ** kwargs): se non instance.is_verified: # Invia email di verifica send_verification_email.delay (instance.pk) segnali .post_save.connect (user_post_save, mittente = utente)
Nota come chiamiamo il .ritardo
metodo sull'oggetto compito. Ciò significa che stiamo inviando l'incarico a Celery e non aspettiamo il risultato. Se abbiamo usato send_verification_email (instance.pk)
invece, lo manderemmo comunque a Celery, ma aspetteremmo che il compito finisse, che non è quello che vogliamo.
Prima di iniziare a creare un nuovo utente, c'è un problema. Il sedano è un servizio e dobbiamo avviarlo. Apri una nuova console, assicurati di attivare la virtualenv appropriata e accedi alla cartella del progetto.
$ sedano lavoratore -A quick_publisher --loglevel = debug --concurrency = 4
Questo avvia quattro processisti di Celery. Sì, ora puoi finalmente andare e creare un altro utente. Nota come non c'è ritardo e assicurati di guardare i log nella console di Celery e vedere se le attività sono state eseguite correttamente. Questo dovrebbe assomigliare a questo:
[2017-04-28 15: 00: 09,190: DEBUG / MainProcess] Attività accettata: main.tasks.send_verification_email [f1f41e1f-ca39-43d2-a37d-9de085dc99de] pid: 62065 [2017-04-28 15: 00: 11,740: INFO / PoolWorker-2] Task main.tasks.send_verification_email [f1f41e1f-ca39-43d2-a37d-9de085dc99de] riuscito in 2.5500912349671125s: Nessuno
Ecco un altro scenario comune. Le applicazioni web più mature inviano le loro email al ciclo di vita degli utenti per mantenerle coinvolte. Alcuni esempi comuni di e-mail del ciclo di vita:
Ecco cosa faremo nella nostra app. Conteremo quante volte ogni post è stato visualizzato e inviamo un rapporto giornaliero all'autore. Una volta ogni giorno, passeremo attraverso tutti gli utenti, recupereremo i loro post e inviamo un'email con una tabella contenente i post e i conteggi delle visualizzazioni.
Cambiamo il Inviare
modello in modo da poter accogliere lo scenario dei conteggi di vista.
class Post (models.Model): author = models.ForeignKey (User) created = models.DateTimeField ('Created Date', default = timezone.now) title = models.CharField ('Title', max_length = 200) content = models .TextField ('Content') slug = models.SlugField ('Slug') view_count = models.IntegerField ("Visualizza numero", default = 0) def __str __ (self): restituisce '"% s" di% s'% ( self.title, self.author)
Come sempre, quando cambiamo un modello, dobbiamo migrare il database:
$ ./manage.py makemigrations $ ./manage.py migrare
Modifichiamo anche il view_post
Vista Django per contare le visualizzazioni:
def view_post (request, slug): prova: post = Post.objects.get (slug = slug) tranne Post.DoesNotExist: genera Http404 ("Sondaggio inesistente") post.view_count + = 1 post.save () restituisce il rendering (richiesta, 'post.html', context = 'post': post)
Sarebbe utile visualizzare il Visto post.view_count volteview_count
nel modello. Aggiungi questo
da qualche parte dentro il Editore / templates / post.html
file. Esegui ora alcune visualizzazioni su un post e osserva come aumenta il contatore.
Creiamo un compito di Celery. Dato che si tratta di post, ho intenzione di inserirlo Editore / tasks.py
:
da django.template import Template, Context from django.core.mail import send_mail from django.contrib.auth import get_user_model dall'app di importazione quick_publisher.celery da publisher.models import Post REPORT_TEMPLATE = "" "Ecco come hai fatto fino ad ora: % per post nei post% "post.title": visualizzato post.view_count volte | % endfor% "" "@ app.task def send_view_count_report (): per utente in get_user_model (). objects.all (): posts = Post.objects.filter (autore = utente) se non post: continua template = Template (REPORT_TEMPLATE) send_mail ('Your QuickPublisher Activity', template.render (context = Context ('posts': post)), '[email protected]', [user.email], fail_silently = False,)
Ogni volta che apporti modifiche alle attività di Celery, ricorda di riavviare il processo di Celery. Il sedano ha bisogno di scoprire e ricaricare i compiti. Prima di creare un'attività periodica, dovremmo verificarlo nella shell di Django per assicurarci che tutto funzioni come previsto:
$ ./manage.py shell In [1]: da editore.tasks import send_view_count_report In [2]: send_view_count_report.delay ()
Spero che tu abbia ricevuto una piccola relazione nella tua email.
Creiamo ora un'attività periodica. Aprire quick_publisher / celery.py
e registrare i compiti periodici:
# quick_publisher / celery.py import os da Celery import Celery da celery.schedules import crontab os.environ.setdefault ('DJANGO_SETTINGS_MODULE', 'quick_publisher.settings') app = Celery ('quick_publisher') app.config_from_object ('django.conf : settings ') # Carica i moduli di attività da tutte le configurazioni di app Django registrate. app.autodiscover_tasks () app.conf.beat_schedule = 'send-report-every-single-minute': 'task': 'publisher.tasks.send_view_count_report', 'schedule': crontab (), # cambia in 'crontab (minuto = 0, ora = 0) 'se vuoi che funzioni giornalmente a mezzanotte,
Finora, abbiamo creato un programma che avrebbe eseguito il compito publisher.tasks.send_view_count_report
ogni minuto come indicato dal crontab ()
notazione. Puoi anche specificare vari programmi di Celery Crontab.
Apri un'altra console, attiva l'ambiente appropriato e avvia il servizio Celery Beat.
$ sedano-Un battitore quick_publisher
Il lavoro del servizio Beat è quello di spingere le attività in Celery secondo il programma. Tieni conto che il programma rende il send_view_count_report
l'attività viene eseguita ogni minuto in base all'impostazione. È utile per i test, ma non è consigliato per un'applicazione web reale.
Le attività vengono spesso utilizzate per eseguire operazioni inaffidabili, operazioni che dipendono da risorse esterne o che possono facilmente fallire a causa di vari motivi. Ecco una linea guida per renderli più affidabili:
Spero che questo sia stato un tutorial interessante per te e una buona introduzione all'uso di Celery con Django.
Ecco alcune conclusioni che possiamo trarre:
battito di sedano
servizio.