sql >> Databasteknik >  >> RDS >> Database

Asynkrona uppgifter med Django och selleri

När jag var ny på Django var en av de mest frustrerande sakerna jag upplevde behovet av att köra lite kod med jämna mellanrum. Jag skrev en trevlig funktion som utförde en åtgärd som behövde köras dagligen klockan 12. Lätt, eller hur? Fel. Detta visade sig vara ett stort problem för mig eftersom jag vid den tidpunkten var van vid webbhotell av "Cpanel-typ" där det fanns ett trevligt praktiskt gränssnitt för att ställa in cron-jobb för just detta ändamål.

Efter mycket efterforskning hittade jag en bra lösning – Selleri, en kraftfull asynkron jobbkö som används för att köra uppgifter i bakgrunden. Men detta ledde till ytterligare problem, eftersom jag inte kunde hitta en enkel uppsättning instruktioner för att integrera Selleri i ett Django-projekt.

Naturligtvis lyckades jag så småningom komma på det – vilket är vad den här artikeln kommer att täcka:Hur man integrerar selleri i ett Django-projekt och skapar periodiska uppgifter.

Gratis bonus: Klicka här för att få tillgång till en gratis guide för Django Learning Resources (PDF) som visar dig tips och tricks samt vanliga fallgropar att undvika när du bygger Python + Django webbapplikationer.

Detta projekt använder Python 3.4, Django 1.8.2, Celery 3.1.18 och Redis 3.0.2.


Översikt

För din bekvämlighet, eftersom detta är ett så stort inlägg, vänligen gå tillbaka till den här tabellen för kort information om varje steg och för att ta tag i den tillhörande koden.

Steg Översikt Git Tag
Boilerplate Ladda ner boilerplate v1
Inställningar Integrera selleri med Django v2
Selleriuppgifter Lägg till grundläggande selleriuppgift v3
Periodiska uppgifter Lägg till periodisk uppgift v4
Körs lokalt Kör vår app lokalt v5
Körs på distans Kör vår app på distans v6


Vad är selleri?

"Selleri är en asynkron uppgiftskö/jobbkö baserad på distribuerad meddelandeöverföring. Det är fokuserat på drift i realtid, men stöder också schemaläggning." För det här inlägget kommer vi att fokusera på schemaläggningsfunktionen för att regelbundet köra ett jobb/uppgift.

Varför är detta användbart?

  • Tänk på alla gånger du har varit tvungen att köra en viss uppgift i framtiden. Du kanske behövde komma åt ett API varje timme. Eller så kanske du behövde skicka ett parti e-postmeddelanden i slutet av dagen. Stor eller liten, Celery gör det enkelt att schemalägga sådana periodiska uppgifter.
  • Du vill aldrig att slutanvändare ska behöva vänta i onödan på att sidor ska laddas eller att åtgärder ska slutföras. Om en lång process är en del av din applikations arbetsflöde kan du använda Celery för att köra den processen i bakgrunden, när resurser blir tillgängliga, så att din applikation kan fortsätta att svara på klientförfrågningar. Detta håller uppgiften borta från applikationens sammanhang.


Inställningar

Innan du dyker in i Selleri, ta tag i startprojektet från Github-repo. Se till att aktivera en virtualenv, installera kraven och köra migreringarna. Starta sedan servern och navigera till http://localhost:8000/ i din webbläsare. Du bör se den välbekanta texten "Grattis till din första Django-drivna sida". När du är klar, döda servern.

Låt oss sedan installera selleri med pip:

$ pip install celery==3.1.18
$ pip freeze > requirements.txt

Nu kan vi integrera Selleri i vårt Django-projekt i bara tre enkla steg.


Steg 1:Lägg till celery.py

Skapa en ny fil som heter celery.py i katalogen "picha". :

from __future__ import absolute_import
import os
from celery import Celery
from django.conf import settings

# set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'picha.settings')
app = Celery('picha')

# Using a string here means the worker will not have to
# pickle the object when using Windows.
app.config_from_object('django.conf:settings')
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)


@app.task(bind=True)
def debug_task(self):
    print('Request: {0!r}'.format(self.request))

Notera kommentarerna i koden.



Steg 2:Importera din nya Celery-app

För att säkerställa att Celery-appen laddas när Django startar, lägg till följande kod i __init__.py fil som finns bredvid din settings.py fil:

from __future__ import absolute_import

# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app

Efter att ha gjort det bör din projektlayout nu se ut så här:

├── manage.py
├── picha
│   ├── __init__.py
│   ├── celery.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── requirements.txt


Steg 3:Installera Redis som en "mäklare" på selleri

Selleri använder "mäklare" för att skicka meddelanden mellan ett Django-projekt och Selleri-arbetarna. I den här handledningen kommer vi att använda Redis som meddelandeförmedlare.

Installera först Redis från den officiella nedladdningssidan eller via brew (brew install redis ) och vänd dig sedan till din terminal, i ett nytt terminalfönster, starta servern:

$ redis-server

Du kan testa att Redis fungerar korrekt genom att skriva in detta i din terminal:

$ redis-cli ping

Redis bör svara med PONG - prova!

När Redis är uppe lägger du till följande kod i filen settings.py:

# CELERY STUFF
BROKER_URL = 'redis://localhost:6379'
CELERY_RESULT_BACKEND = 'redis://localhost:6379'
CELERY_ACCEPT_CONTENT = ['application/json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = 'Africa/Nairobi'

Du måste också lägga till Redis som ett beroende i Django-projektet:

$ pip install redis==2.10.3
$ pip freeze > requirements.txt

Det är allt! Du bör nu kunna använda Selleri med Django. För mer information om hur du ställer in Selleri med Django, vänligen kolla in den officiella Selleri-dokumentationen.

Innan vi går vidare, låt oss göra några hälsokontroller för att säkerställa att allt är bra...

Testa att Selleri-arbetaren är redo att ta emot uppgifter:

$ celery -A picha worker -l info
...
[2015-07-07 14:07:07,398: INFO/MainProcess] Connected to redis://localhost:6379//
[2015-07-07 14:07:07,410: INFO/MainProcess] mingle: searching for neighbors
[2015-07-07 14:07:08,419: INFO/MainProcess] mingle: all alone

Döda processen med CTRL-C. Testa nu att aktivitetsschemaläggaren för Celery är redo för åtgärd:

$ celery -A picha beat -l info
...
[2015-07-07 14:08:23,054: INFO/MainProcess] beat: Starting...

Bom!

Återigen, döda processen när du är klar.




Selleriuppgifter

Selleri använder uppgifter, som kan ses som vanliga Python-funktioner som anropas med Selleri.

Låt oss till exempel förvandla den här grundläggande funktionen till en selleriuppgift:

def add(x, y):
    return x + y

Lägg först till en dekoratör:

from celery.decorators import task

@task(name="sum_two_numbers")
def add(x, y):
    return x + y

Sedan kan du köra den här uppgiften asynkront med Celery så här:

add.delay(7, 8)

Enkelt, eller hur?

Så dessa typer av uppgifter är perfekta när du vill ladda en webbsida utan att få användaren att vänta på att en bakgrundsprocess ska slutföras.

Låt oss titta på ett exempel...

Gå tillbaka till Django-projektet, ta version tre, som inkluderar en app som accepterar feedback från användare, lämpligen kallad feedback :

├── feedback
│   ├── __init__.py
│   ├── admin.py
│   ├── emails.py
│   ├── forms.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── manage.py
├── picha
│   ├── __init__.py
│   ├── celery.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── requirements.txt
└── templates
    ├── base.html
    └── feedback
        ├── contact.html
        └── email
            ├── feedback_email_body.txt
            └── feedback_email_subject.txt

Installera de nya kraven, starta appen och navigera till http://localhost:8000/feedback/. Du bör se:

Låt oss koppla upp selleriuppgiften.


Lägg till uppgiften

I grund och botten, efter att användaren har skickat in feedbackformuläret, vill vi omedelbart låta honom fortsätta på sin glada väg medan vi behandlar feedbacken, skickar ett e-postmeddelande, etc., allt i bakgrunden.

För att åstadkomma detta, lägg först till en fil som heter tasks.py till "feedback"-katalogen:

from celery.decorators import task
from celery.utils.log import get_task_logger

from feedback.emails import send_feedback_email

logger = get_task_logger(__name__)


@task(name="send_feedback_email_task")
def send_feedback_email_task(email, message):
    """sends an email when feedback form is filled successfully"""
    logger.info("Sent feedback email")
    return send_feedback_email(email, message)

Uppdatera sedan forms.py som så:

from django import forms
from feedback.tasks import send_feedback_email_task


class FeedbackForm(forms.Form):
    email = forms.EmailField(label="Email Address")
    message = forms.CharField(
        label="Message", widget=forms.Textarea(attrs={'rows': 5}))
    honeypot = forms.CharField(widget=forms.HiddenInput(), required=False)

    def send_email(self):
        # try to trick spammers by checking whether the honeypot field is
        # filled in; not super complicated/effective but it works
        if self.cleaned_data['honeypot']:
            return False
        send_feedback_email_task.delay(
            self.cleaned_data['email'], self.cleaned_data['message'])

I huvudsak är send_feedback_email_task.delay(email, message) funktion behandlar och skickar feedback-e-postmeddelandet i bakgrunden när användaren fortsätter att använda webbplatsen.

OBS :success_url i views.py är inställd på att omdirigera användaren till / , som inte finns ännu. Vi kommer att ställa in denna slutpunkt i nästa avsnitt.




Periodiska uppgifter

Ofta måste du schemalägga en uppgift som ska köras vid en specifik tidpunkt då och då - till exempel kan en webbskrapa behöva köras dagligen. Sådana uppgifter, kallade periodiska uppgifter, är lätta att ställa in med Selleri.

Selleri använder "selleribeat" för att schemalägga periodiska uppgifter. Celery beat kör uppgifter med jämna mellanrum, som sedan utförs av selleriarbetare.

Till exempel är följande uppgift schemalagd att köras var femtonde minut:

from celery.task.schedules import crontab
from celery.decorators import periodic_task


@periodic_task(run_every=(crontab(minute='*/15')), name="some_task", ignore_result=True)
def some_task():
    # do something

Låt oss titta på ett mer robust exempel genom att lägga till denna funktionalitet i Django-projektet...

Tillbaka till Django-projektet, hämta version fyra, som innehåller en annan ny app, kallad photos , som använder Flickr API för att få nya foton för visning på webbplatsen:

├── feedback
│   ├── __init__.py
│   ├── admin.py
│   ├── emails.py
│   ├── forms.py
│   ├── models.py
│   ├── tasks.py
│   ├── tests.py
│   └── views.py
├── manage.py
├── photos
│   ├── __init__.py
│   ├── admin.py
│   ├── models.py
│   ├── settings.py
│   ├── tests.py
│   ├── utils.py
│   └── views.py
├── picha
│   ├── __init__.py
│   ├── celery.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── requirements.txt
└── templates
    ├── base.html
    ├── feedback
    │   ├── contact.html
    │   └── email
    │       ├── feedback_email_body.txt
    │       └── feedback_email_subject.txt
    └── photos
        └── photo_list.html

Installera de nya kraven, kör migreringarna och starta sedan upp servern för att se till att allt är bra. Försök att testa feedbackformuläret igen. Den här gången borde den omdirigera helt okej.

Vad händer härnäst?

Tja, eftersom vi skulle behöva anropa Flickr API med jämna mellanrum för att lägga till fler foton på vår webbplats, kan vi lägga till en Selleri-uppgift.


Lägg till uppgiften

Lägg till en tasks.py till photos app:

from celery.task.schedules import crontab
from celery.decorators import periodic_task
from celery.utils.log import get_task_logger

from photos.utils import save_latest_flickr_image

logger = get_task_logger(__name__)


@periodic_task(
    run_every=(crontab(minute='*/15')),
    name="task_save_latest_flickr_image",
    ignore_result=True
)
def task_save_latest_flickr_image():
    """
    Saves latest image from Flickr
    """
    save_latest_flickr_image()
    logger.info("Saved image from Flickr")

Här kör vi save_latest_flickr_image() funktion var femtonde minut genom att slå in funktionsanropet i en task . @periodic_task decorator abstraherar ut koden för att köra aktiviteten Selleri och lämnar tasks.py fil ren och lätt att läsa!




Körs lokalt

Är du redo att köra den här saken?

Öppna två nya terminalfönster/flikar med din Django-app och Redis igång. I varje nytt fönster, navigera till din projektkatalog, aktivera din virtualenv och kör sedan följande kommandon (ett i varje fönster):

$ celery -A picha worker -l info
$ celery -A picha beat -l info

När du besöker webbplatsen på http://127.0.0.1:8000/ bör du nu se en bild. Vår app får en bild från Flickr var 15:e minut:

Ta en titt på photos/tasks.py för att se koden. Genom att klicka på knappen "Feedback" kan du... skicka lite feedback:

Detta fungerar via en selleriuppgift. Ta en titt på feedback/tasks.py för mer.

Det är allt, du har Picha-projektet igång!

Detta är bra för att testa medan du utvecklar ditt Django-projekt lokalt, men fungerar inte så bra när du behöver distribuera till produktion - som på DigitalOcean, kanske. För det rekommenderas det att du kör Celery-arbetaren och schemaläggaren i bakgrunden som en demon med Supervisor.



Kör på distans

Installationen är enkel. Ta version fem från repet (om du inte redan har det). SSH sedan till din fjärrserver och kör:

$ sudo apt-get install supervisor

Vi måste sedan berätta för Supervisor om våra Celery-arbetare genom att lägga till konfigurationsfiler i katalogen “/etc/supervisor/conf.d/” på fjärrservern. I vårt fall behöver vi två sådana konfigurationsfiler - en för Celery-arbetaren och en för Celery-schemaläggaren.

Skapa en mapp som heter "supervisor" lokalt i projektroten. Lägg sedan till följande filer...

Selleriarbetare:picha_celery.conf

; ==================================
;  celery worker supervisor example
; ==================================

; the name of your supervisord program
[program:pichacelery]

; Set full path to celery program if using virtualenv
command=/home/mosh/.virtualenvs/picha/bin/celery worker -A picha --loglevel=INFO

; The directory to your Django project
directory=/home/mosh/sites/picha

; If supervisord is run as the root user, switch users to this UNIX user account
; before doing any processing.
user=mosh

; Supervisor will start as many instances of this program as named by numprocs
numprocs=1

; Put process stdout output in this file
stdout_logfile=/var/log/celery/picha_worker.log

; Put process stderr output in this file
stderr_logfile=/var/log/celery/picha_worker.log

; If true, this program will start automatically when supervisord is started
autostart=true

; May be one of false, unexpected, or true. If false, the process will never
; be autorestarted. If unexpected, the process will be restart when the program
; exits with an exit code that is not one of the exit codes associated with this
; process’ configuration (see exitcodes). If true, the process will be
; unconditionally restarted when it exits, without regard to its exit code.
autorestart=true

; The total number of seconds which the program needs to stay running after
; a startup to consider the start successful.
startsecs=10

; Need to wait for currently executing tasks to finish at shutdown.
; Increase this if you have very long running tasks.
stopwaitsecs = 600

; When resorting to send SIGKILL to the program to terminate it
; send SIGKILL to its whole process group instead,
; taking care of its children as well.
killasgroup=true

; if your broker is supervised, set its priority higher
; so it starts first
priority=998

Sellerischemaläggare:picha_celerybeat.conf

; ================================
;  celery beat supervisor example
; ================================

; the name of your supervisord program
[program:pichacelerybeat]

; Set full path to celery program if using virtualenv
command=/home/mosh/.virtualenvs/picha/bin/celerybeat -A picha --loglevel=INFO

; The directory to your Django project
directory=/home/mosh/sites/picha

; If supervisord is run as the root user, switch users to this UNIX user account
; before doing any processing.
user=mosh

; Supervisor will start as many instances of this program as named by numprocs
numprocs=1

; Put process stdout output in this file
stdout_logfile=/var/log/celery/picha_beat.log

; Put process stderr output in this file
stderr_logfile=/var/log/celery/picha_beat.log

; If true, this program will start automatically when supervisord is started
autostart=true

; May be one of false, unexpected, or true. If false, the process will never
; be autorestarted. If unexpected, the process will be restart when the program
; exits with an exit code that is not one of the exit codes associated with this
; process’ configuration (see exitcodes). If true, the process will be
; unconditionally restarted when it exits, without regard to its exit code.
autorestart=true

; The total number of seconds which the program needs to stay running after
; a startup to consider the start successful.
startsecs=10

; if your broker is supervised, set its priority higher
; so it starts first
priority=999

Se till att uppdatera sökvägarna i dessa filer så att de matchar fjärrserverns filsystem.

I grund och botten berättar dessa supervisorkonfigurationsfiler för supervisor hur man kör och hanterar våra "program" (som de kallas av supervisor).

I exemplen ovan har vi skapat två handledningsprogram som heter "pichacelery" och "pichacelerybeat".

Kopiera nu bara dessa filer till fjärrservern i katalogen "/etc/supervisor/conf.d/".

Vi måste också skapa loggfilerna som nämns i ovanstående skript på fjärrservern:

$ touch /var/log/celery/picha_worker.log
$ touch /var/log/celery/picha_beat.log

Slutligen, kör följande kommandon för att göra Supervisor medveten om programmen - t.ex. pichacelery och pichacelerybeat :

$ sudo supervisorctl reread
$ sudo supervisorctl update

Kör följande kommandon för att stoppa, starta och/eller kontrollera statusen för pichacelery program:

$ sudo supervisorctl stop pichacelery
$ sudo supervisorctl start pichacelery
$ sudo supervisorctl status pichacelery

Du kan läsa mer om Supervisor från den officiella dokumentationen.



Sluta tips

  1. Ge inte Django-modellobjekt till Selleri-uppgifter. För att undvika fall där modellobjektet redan har ändrats innan det skickas till en Selleri-uppgift, skicka objektets primärnyckel till Selleri. Du måste då naturligtvis använda primärnyckeln för att hämta objektet från databasen innan du arbetar med det.
  2. Standardschemaläggaren för Celery skapar några filer för att lagra dess schema lokalt. Dessa filer skulle vara "celerybeat-schedule.db" och "celerybeat.pid". Om du använder ett versionskontrollsystem som Git (vilket du borde!), är det en bra idé att ignorera dessa filer och inte lägga till dem i ditt arkiv eftersom de är till för att köra processer lokalt.


Nästa steg

Tja, det är det för den grundläggande introduktionen till att integrera selleri i ett Django-projekt.

Vill du ha mer?

  1. Dyk in i den officiella användarhandboken för Celery för att lära dig mer.
  2. Skapa en Fab-fil för att ställa in Supervisor och konfigurationsfilerna. Se till att lägga till kommandona för att reread och update Handledare.
  3. Fördela projektet från arkivet och öppna en Pull Request för att lägga till en ny Selleri-uppgift.

Gratis bonus: Klicka här för att få tillgång till en gratis guide för Django Learning Resources (PDF) som visar dig tips och tricks samt vanliga fallgropar att undvika när du bygger Python + Django webbapplikationer.

Lycka till med kodningen!



  1. Ansluter Delphi till Salesforce.com

  2. Ansluter Snowflake DB &IRI Workbench

  3. Oracle-schemaanvändare kan inte skapa tabell i proceduren

  4. Hur väljer och optimerar man orakelindex?