El Blog de Trespams

Blog personal sobre tecnologia, gestió de projectes i coses que se me passen pel cap

Celery: mini tutorial

Configuració del servidor rabbitmq

El servidor Rabbitmq és qui rebrà les ordre d'execució de les tasques i les enviarà als workers que han de fer la feina i rebrà el resultats de les operacions de manera que el procés que ha donat l'ordre d'execució de la tasca el pugui fer servir.

És important destacar que a l'hora de donar l'ordre d'execució podem decidir si el resultat s'ha de guardar o bé s'ha de descartar. Si el resultat no és necessari (pensem per exemple en l'enviament d'un e-mail) llavors convé marcar la tasca per a que el resultat es descarti.

sudo aptitude install rabbitmq-server Els paquets nous següents s'instal·laran: erlang-os-mon{a} erlang-snmp{a} rabbitmq-server 0 paquets a actualitzar, 3 a instal·lar, 0 a suprimir i 5 a no actualitzar. Es necessita obtenir 1398kB d'arxius. Després del desempaquetat s'utilitzaran 3187kB. Esteu segur de voler continuar? [Y/n/?] y Obté:1 http://es.archive.ubuntu.com/ubuntu/ maverick/main erlang-snmp i386 1:13.b.3-dfsg-2ubuntu3 [581kB] Obté:2 http://es.archive.ubuntu.com/ubuntu/ maverick/main erlang-os-mon i386 1:13.b.3-dfsg-2ubuntu3 [77,4kB] Obté:3 http://es.archive.ubuntu.com/ubuntu/ maverick/main rabbitmq-server all 1.8.0-1ubuntu2 [739kB] S'han obtingut 1398kB en 2s (480kB/s) S'estan preconfigurant els paquets... S'està seleccionant el paquet erlang-snmp prèviament no seleccionat. (S'està llegint la base de dades … hi ha 118931 fitxers i directoris instal·lats actualment.) S'està desempaquetant erlang-snmp (de …/erlang-snmp_1%3a13.b.3-dfsg-2ubuntu3_i386.deb) … S'està seleccionant el paquet erlang-os-mon prèviament no seleccionat. S'està desempaquetant erlang-os-mon (de …/erlang-os-mon_1%3a13.b.3-dfsg-2ubuntu3_i386.deb) … S'està seleccionant el paquet rabbitmq-server prèviament no seleccionat. S'està desempaquetant rabbitmq-server (de …/rabbitmq-server_1.8.0-1ubuntu2_all.deb) … S'estan processant els activadors per a man-db … S'estan processant els activadors per a ureadahead … S'està configurant erlang-snmp (1:13.b.3-dfsg-2ubuntu3) … S'està configurant erlang-os-mon (1:13.b.3-dfsg-2ubuntu3) … S'està configurant rabbitmq-server (1.8.0-1ubuntu2) … Adding group `rabbitmq' (GID 123) ... Fet. Adding system user `rabbitmq' (UID 115) ... Adding new user `rabbitmq' (UID 115) with group `rabbitmq' ... Not creating home directory `/var/lib/rabbitmq'. Starting rabbitmq-server: SUCCESS rabbitmq-server.

Amb això hem aconsegui instal·lar i posar en marxa el RabbitMq en un sistema Ubuntu 10.10. Ara ens queda configurar, afegirem un usuari i un vhost per a que accepti els treballs, a més hem de donar permisos a aquest usuari per a que que pugui enviar treballs al vhost creat.

Afegim l'usuari:

$ sudo su $ rabbitmqctl add_user django password Creating user "django" ... ...done.

Cream l'vhost

$ rabbitmqctl add_vhost test Creating vhost "test" ... ...done.

Assignam els permisos a l'usuari per a quest vhost

$rabbitmqctl set_permissions -p test django ".*" ".*" ".*" Setting permissions for user "django" in vhost "test" ... ...done.

Ja tenim la part més complexa llesta. El servidor RabbitMQ pot estar en la mateixa màquina o en una totalment distinta de la que llançarà la tasca o de la màquina de l'executarà.

Preparant l'entorn

La gràcia del Celery és que ens permet llançar les tasques i executarles mantinguent la cohesió de la nostra aplicació. És a dir, si estam treballant amb Django Celery ens permet manetenir el codi de cridada i d'execució de la tasca des de la mateixa aplicació o des del mateix projecte.

Per començar convé que instal·lem Celery. Ho farem des de un entorn virtual, per això convé tenir instal·lats els paquets virtualenv i virtualenvwrapper.

sudo pip install virtualenv virtualenvwrapper

i configurar el virtualenvwrapper.

Crear l'entorn, de nom per exemple celery i instal·larem el paquet celery

mkvirtualenv celery workon celery pip install celery

Que sigui un projecte Django o Python pur té molt poques diferències. En el cas de Django hem d'instal·lar el paquet django-celery i configurar el projecte. Així a les nostres aplicacions afegirem "djcelery"

INSTALLED_APPS += ("djcelery", )

I també és important afegir les següents línies al settings.py

import djcelery djcelery.setup_loader()

La resta es comú tant a un projecte Python que a un projecte Django, sols que les configuracions en el primer cas aniran a un arxiu anomenat celeryconfig.py i en el cas de django la configuració pot anar dins el settings.py

Per la nostra primera tasca crearem un project Python simple i l'arxiu de configuració celeryconfig.py contindrà

#-*- coding: UTF-8 -*- BROKER_HOST = "192.168.1.10" BROKER_PORT = 5672 BROKER_USER = "django" BROKER_PASSWORD = "password" BROKER_VHOST = "test" CELERY_RESULT_BACKEND = "amqp" CELERY_IMPORTS = ("tasks", ) CELERY_SEND_EVENTS=True

El prefixe BROKER fa referència al servidor RabbitMq que acabam d'instal·lar. Celery pot fer servir difernts tipus de Brookers pero RabbitMq és el més recomanat.

Fitxau-vos que el configuram amb els paràmetres amb els quals hem creat el vhost, i amb l'usuari i password creats a RabbitMq.

CELERY_RESULT_BACKEND serveix per a indicar a Celery on s'han de deixar els resultats. Podem tenir distints backends, des de Memcached, a una base de dades o una base de dades NoSQL.

CELERY_IMPORTS indica a l'aplicació que tasks.py conté les tasques que s'han d'executar. Bé, de fet el que fa és indicar al daemon de celery quines són les tasques a exeuctar quan es posi en marxa, però també pot fer-se servir per altres tipus d'inicialitzacions.

CELERY_SEND_EVENTS li diu a Celery que volem monitoritzar els events que es produeixin. Si no posam aquesta ordre i després volem saber què esta passant ho tendrem molt més complicat.

Creant la nostra primera tasca

Per cerar la nostra primea tasca crearem una arxiu anomenat tasks.py

# -*- coding: UTF-8 -*- 
from celery.decorators import task


@task(name='tasks.add')
def add(x, y, **kwargs):
logger = add.get_logger(**kwargs)
logger.info('Entrant a la tasca ...')
return x + y

Fitxem-nos que és codi Python del més normalet, sols que hem afegit un decorador tasks per marcar la funció com a tasca i posar-li el nom. Aquest nom és opcional, però convé posar-li ja que així evitam que celery li posi un nom per defecte que podria fer menys portable la nostra aplicació.

També podem veure como podem enviar informació a logger des de la tasca.

Executant el worker

Ara ja podem anar al nostre projecte, si tot ha anat bé tindrem dos arxius: tasks.py i celeryconfig.py

Executam: celeryd -l info

[2010-11-28 18:44:20,949: WARNING/MainProcess] celery@D820 v2.1.3 is starting. [2010-11-28 18:44:20,950: WARNING/MainProcess] Configuration -> . broker -> amqp://django@192.168.1.10:5672/test . queues -> . celery -> exchange:celery (direct) binding:celery . concurrency -> 2 . loader -> celery.loaders.default.Loader . logfile -> [stderr]@INFO . events -> ON . beat -> OFF . tasks -> . tasks.add [2010-11-28 18:44:20,960: INFO/PoolWorker-1] child process calling self.run() [2010-11-28 18:44:20,962: INFO/PoolWorker-2] child process calling self.run() [2010-11-28 18:44:20,963: WARNING/MainProcess] celery@D820 has started.

amb això hem creat el nostre primer worker preparat per a executar la tasca que li diguem (i que estarà dins tasks.py). Per aquest article hem deixat el worker dins al consola, però es pot deixar com a dimoni. Consulteu el vostre administrador de sistemes de capçalera per a una configuració acurada. El paràmetre que li passa indica que volem que generi log en mode info. Fixem-nos que també ens informa de les tasques disponibles i del nivell de concurrència que tenim, 2 en el meu cas, que és el nombre de processadors del portàtil.

I executam la tasca

Des d'una altra consola executarem l'interpret de Python

>>>from tasks import add 

>>>result = add.delay(2,3)

Si ara observam la consola del worker veurem alguna cosa semblant a això:

[2010-11-28 18:46:57,783: INFO/MainProcess] Got task from broker: tasks.add[c379421f-fa6a-4917-b45a-4833a9e30ef3] [2010-11-28 18:46:57,830: INFO/PoolWorker-2] [tasks.add(c379421f-fa6a-4917-b45a-4833a9e30ef3)] Entrant a la tasca ... [2010-11-28 18:46:57,879: INFO/MainProcess] Task tasks.add[c379421f-fa6a-4917-b45a-4833a9e30ef3] succeeded in 0.0486330986023s: 5

El brooker (RambbitMQ) ha enviat l'ordre d'execució de la tasca add que el worker té registrada i aquest es disposa a executar-la.

Des de la consola hem dit a la coa que volíem que la tasca s'executàs en modoe asíncron, d'aquí l'ús de delay.

Si ara volem obtenir el resultat farem

>>> print result.get() 5

Com es pot veure l'exemple és molt senzill, potser massa per adonar-nos de la potència de Celery. Pensem que en lloc d'una suma podem fer la generació d'un pdf, l'enviament d'un e-mail, la càrrega en batch d'un xml, etc. etc. L'important és que el procés principal ja no té que realitzar la tasca i pot ser una altra màquina l'encarregada de fer-ho. Pensem en una màquina plena de targes gràfiques dedicades al processament numèric, en un sistema encarregat del data-mining dels logs, les possibilitats són infinites.

També és important notar l'escalabilitat que aconseguim amb Celery. No estam parlant ja d'escalabilitat amb nombre de processadors, sinó que parlam d'escalabilitat amb nombre de màquines. Mentre les nostres tasques puguin ser paralelitzades podrem fer servir Celery per a distribuir la càrrega de feina entre les màquines disponibles.

I això és sols el principi: celery pot substituir al Cron, permet la monitorització, la creació de tasques molt més complexes que les que ens permet una funció i un llarg etcètera d'opcions. La documentació és força completa, però llevat de casos molt especial, el grau de dificultat que trobarem és el que hi ha a aquest post.

blog comments powered by Disqus