El Blog de Trespams

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

Celery i Redis

En anteriors apunts he parlat ja de Celery i de Django Celery, un sistema per a la gestió de cues i tasques per Python i Django.

Celery a la seva documentació recomana RabbitMQ com a gestor de missatgeria, és a dir, com a aplicació que reb i distribueix les tasques que li envia l'aplicació entre els diferents worker que tenguem al nostre sistema.

Una vegada el worker ha realitzat la tasca deixa el resultat (si així ho hem indicat) al contenidor de resultats, que normalment serà el mateix que el broker de missatgeria, és a dir RabbitMQ.

L'arquitectura de Celery és molt potent en tant que ens permet escalar cap a baix i substituir peces segons necessitem. Així per aplicacions que necessitin d'un sistema de distribució de tasques però no de la complexitat de RabbitMQ podem utilitzar altres sistemes de notificacions, fins i tot fer servir una base de dades. On es guarden els resultats o com es serialitzen els missatges també es pot canviar. En definitiva, encara que hi hagi una configuració recomanada per a entorns que necessitin de molta potència en el sistema de distribució de tasques i coes, podem personalitzar Celery al nostre gust i a les nostres necessitats.

En aquest article el que presentaré és una configuració a mig camí entre el que seria la configuració més complexa de Celery i una configuració simple basada sols en base de dades.

Plantejant el problema

Tenim doncs una aplicació que no és molt gran, però a la que aniria molt bé tenir un sistema de distribució de tasques.

  • Volem que la instal·lació i configuració sigui senzilla.
  • El que instal·lem si pot ser hauria de poder aprofitar-se per quelcom més que no el sistema de tasques.
  • Volem poder monitoritzar el que està passant.
  • Volem poder gestionar fàcilment l'entorn
  • Hem de poder depurar fàcilment el que està passant i assegurar-nos que l'aplicació funciona sense tenir que muntar tot l'entorn.
  • El gestor de tasques ha de consumir pocs recursos i ser bo de controlar.
  • El gestor de tasques ha de permetre l'execució de tasques periòdiques.
  • Volem poder tenir accés als resultats.

Podem imaginar molts escenaris en que això s'ha de complir, per exemple un agregador de notícies, en un sistema de e-comerce l'enviament de factures, en un petit gestor documental la conversió de formats, ... És a dir, sistemes en els que volem que la resposta cap a l'usuari final sigui el més ràpida possible i que la feina feixuga no necessàriament es tengui que fer de manera síncrona, però on tampoc passa res si el sistema de missatgeria cau i perd alguna tasca, simplement es pot tornar a executar.

Amb aquestes condicions la configuració estàndard de Celery i de Django Celery és massa complexa, així que el que farem és aprimar-la un poc.

El broker

Volem que el sistema de distribució de tasques sigui prou potent i flexible, però sense la complexitat de RabbitMQ. Per això el que farem serà instal·lar Redis una base de dades NoSQL molt semblant al que seria un memcached.

Redis té l'avantatge de que és molt ràpid, ocupa molt pocs recursos de màquina, permet la persistència periòdica de les dades i és una aplicació prou genèrica com per poder ser utilitzada en les nostres aplicacions a més de per fer la gestió de les tasques. Una presentació de Simon Wilson crec que resumeix molt bé les possibilitats d'aquesta base de dades

Redis té un emperò important que hem de conèixer, és una base de dades que en la seva configuració estàndard requereix que totes les dades hi càpiguen en memòria i que aquesta es sincronitzi de manera periòdica al disc. Així doncs hem de procurar monitoritzar la nostra aplicació i l'ús que en fa de Redis per a no consumir més memòria de la que ens vulguem permetre.

La instal·lació de Redis

Redis està com a paquet en les principals distribucions, així que per distribucions basades en Debian bastarà fer:

sudo apt-get install redis-server

i com que també l'utilitzarem des de la nostra aplicació convé instal·lar també el l'API de connexió per Python, amb pip mateix

pip install redis

dins el nostre entorn virtual (perquè està clar que feis servir virtualenv a les vostres aplicacions, no?).

En una instal·lació neta dins una màquina virtual Ubuntu redis està ocupant 3271B de memòria virtual i 1516B de memòria resident a un únic procés.

En producció segurament ens interessarà tocar alguns paràmetres de Redis per fer la configuració més segura:

  • bind ens permet lligar redis a una IP concreta.
  • loglevel per defecte està a verbose, a producció amb notice o warning serà suficient.

l'arxiu de configuració de redis es troba a /etc/redis/redis.conf i està molt ben documentat per a poder adaptar-lo a les nostres necessitats.

On guardam els resultats?

Com hem dit Celery ens permet definir també on guardar els nostres resultats. Donat que Redis és una base de dades de propòsit general, a més de fer les tasques de missatgeria l'aprofitarem per a que guardi els resultats de les tasques.

Aquí hem de tenir en comptes el que dèiem de Redis, si guardam moltes dades i no pensam en anar fent neteja la memòria de Redis anirà creixent fins a ocupar tot el que tinguem.

Normalment a un sistema de tasques i cues ens interessarà guardar els resultats un temps per veure que tot està anant bé i després ja no els necessitam. Es a dir, els resultats no tenen perquè quedar forçosament dins la base de dades Redis, això ja dependrà de la nostra aplicació.

M'explicaré. Una de les aplicacions on feim servir Celery ens permet actualitzar la informació que tenim d'hotels. Es van llançant les peticions d'actualització i el worker se n'encarrega de baixar-se la informació llançant a la seva vegada una altra tasca i de processar-la una vegada l'ha rebuda. La informació sols necessita estar al magatzem el temps just i necessari per poder monitoritzar que tot va bé i que el worker ha rebut i processat la informació.

En el cas de la generació d'una factura no hem de guardar necessàriament el pdf de la factura dins Redis, basta poder dir que la factura s'ha realitzat correctament o directament suposar que ha anat tot bé i periòdicament tornar a llançar les tasques d'enviament per a tots aquells clients que han sol·licitat la factura i no se'ls ha enviada.

Així doncs utilitzarem també Redis per guardar els resultats i configurarem la nostra aplicació per a que no els guardi per sempre.

Amb això ja hem aconseguit que Redis faci una doble funció, però és que a més podem utilitzar Redis per a guardar les sessions de Django o fer-la servir com a Cache. Així doncs estam aprofitant els recursos que és un dels nostres principals objectius.

La configuració del settings.py

A INSTALLED_APPS hem afegit djcelery i per configurar Redis com a broker i com a magatzem de resultats feim:

BROKER_HOST = "192.168.1.33" BROKER_BACKEND="redis" REDIS_PORT=6379 REDIS_HOST = "192.168.1.33" BROKER_USER = "" BROKER_PASSWORD ="" BROKER_VHOST = "0" REDIS_DB = 0 REDIS_CONNECT_RETRY = True CELERY_SEND_EVENTS=True CELERY_RESULT_BACKEND='redis' CELERY_TASK_RESULT_EXPIRES = 10 CELERYBEAT_SCHEDULER="djcelery.schedulers.DatabaseScheduler" import djcelery djcelery.setup_loader()

El 192.168.1.33 és la màquina virtual on tenc instal·lat Redis, per tal d'emular un entorn de producció amb dues màquines. El port és el port per defecte i no he protegit redis amb usuari i password.

Important, el BROKER_VHOST és per redis el nom de la base de dades que es farà servir. Per defecte Redis fa servir la base de dades zero (0) però res ens impedeix tenir una base de dades diferent per les tasques i una altra pels resultats.

A CELERY_TASK_RESULT_EXPIRES podem veure com hem posat que els resultat expirin als 10 segons, més que suficient per la configuració de l'aplicació.

CELERYBEAT_SCHEDULER ens permet utilitzar la nostra aplicació d'Django per a gestionar les tasques periòdiques. Necessitarem fer un syncdb per a crear les estructures de dades, però a canvi podrem definir períodes i tasques.

Desenvolupant

En desenvolupament ens interessarà tenir molt present que tenim un sistema de coes i tasques, però realment el que més ens interessa és provar que tot funciona

CELERY_ALWAYS_EAGER=True

Aquesta configuració fa que l'aplicació s'executi com si no tingués coneixement del gestor de tasques, la qual cosa ens permetrà crear i depurar la nostra aplicació com sempre hem fet.

Posant en marxa els workers

Per tal de monitoritzar millor el que feim podem utilitzar una aplicació com terminator, una consola que ens permet agrupar consoles i veure d'una ullada el que està passant. A una d'aquestes consoles farem:

python manage.py celeryd -E -B --loglevel=INFO -n w1.d820

Això posa en marxa un worker, configurat amb un nombre de processos per defecte (2 en el meu cas) i li deim que envii els senyals de monitorització (això és el paràmetre S) i que a més engegui un procés addicional per a que gestioni les tasques periòdiques.

Hem de tenir en compte que sols hi pot haver un procés de gestió de tasques periòdiques, així que o bé es llança des d'un sol worker o bé podem fer servir l'aplicació celerybeat

python manage.py celerybeat --loglevel=INFO

Recordau! O una manera o l'altra però no les dues i mai per duplicat a dos workers.

Si l'executam per separat celerybeat ens informa de que està engegat i de las configuració que farà servir:

[2011-04-03 11:16:46,808: WARNING/MainProcess] celerybeat v2.2.5 is starting. [2011-04-03 11:16:46,863: WARNING/MainProcess] __ - ... __ - _ Configuration -> . broker -> redis://@192.168.1.33:6379/0 . loader -> djcelery.loaders.DjangoLoader . scheduler -> djcelery.schedulers.DatabaseScheduler

Com que volem controlar i monitoritzar bé el que passa i potser tenir més d'un worker és convenient posar-los nom, això es fa amb el paràmetre -n, a mi m'agrada donarlos un nom junt amb el nom de la màquina.

Arrancaré un segon worker:

python manage.py celeryd -E --loglevel=INFO -n w2.d820 -------------- celery@w2.d820 v2.2.5 ---- **** ----- --- * *** * -- [Configuration] -- * - **** --- . broker: redis://@192.168.1.33:6379/0 - ** ---------- . loader: djcelery.loaders.DjangoLoader - ** ---------- . logfile: [stderr]@WARNING - ** ---------- . concurrency: 2 - ** ---------- . events: ON - *** --- * --- . beat: OFF -- ******* ---- --- ***** ----- [Queues] -------------- . celery: exchange:celery (direct) binding:celery

Fitxem-nos que en aquest cas ens diu que els events estan activat però que no hi ha el beat (el responsable de les tasques periòdiques actiu per aquest worker).

Podem ampliar o limitar el nombre de processos que arranca cada worker amb el paràmetre --concurrency, la configuració per defecte que depèn del nombre de CPUs disponibles sol anar bé, però sempre dependrà de la nostra aplicació i de les limitacions de recursos que li vulguem donar. Per exemple:

python manage.py celeryd -E --concurrency=10 -n w3.d820
Monitoritzant el que passa

Si a la nostra aplicació hem posat logs a cada worker podem veure el que està passant, però potser no sigui així, o tinguem els workers distribuïts a vàries màquines. Per això Celery ens dóna vàries eines de monitorització.

El primer que hem de fer és engegar el servei de monitorització:

python manage.py celerymon celerymon 2.2.5 is starting. Configuration -> . broker -> redis://@192.168.1.33:6379/0 . webserver -> http://localhost:8989 celerymon has started.

Si anam al servidor web que ha engegat Celery, podem veure un resum de les tasques i dels workers. Per exemple, en el nostre cas:

[{"heartbeats": [1301824037.784225],"hostname": "w1.d820"}, {"heartbeats": [1301824018.90294], "hostname": "w2.d820"}]

Podem veure que hi ha dos workers actius.

Si hem fet servir el DatabaseScheduler veurem les nostres tasques dins la pròpia base de dades de Django, però a més hi ha una eina de consola d'allò més interessant celeryev

Amb python manage.py celeryev posarem en marxa una consola en la que ens informarà del que està passat, el nombre de tasques que hi ha executant-se i podrem veure informació damunt la tasca o eliminar (revoke) una tasca de la cua.

Si volem saber més coses o fer les nostres pròpies eines de monitorització Celery ens dóna tot una API per fer-ho, així que no estam limitats a les eines de sèrie.

El que ens queda també per monitoritzar és què està passant amb Redis

sudo tail -n 100 -f /var/log/redis/redis-server.log

ens informarà del que està passant i de les connexions que es realitzen.

==> /var/log/redis/redis-server.log <== [677] 03 Apr 09:56:10 - Accepted 192.168.1.32:38923 [677] 03 Apr 09:56:10 - Client closed connection [677] 03 Apr 09:56:10 - Client closed connection [677] 03 Apr 09:56:10 - Accepted 192.168.1.32:38924 [677] 03 Apr 09:56:10 - Client closed connection [677] 03 Apr 09:56:10 - Client closed connection [677] 03 Apr 09:56:10 - Accepted 192.168.1.32:38925

Si volem anar més enllà podem fer servir la consola de Redis, redis-cli

Algunes comandes molt útils:

  • KEYS * - ens monstra les claus que tenim actives.
  • DBSIZE - ens diu com està el tamany de la nostra base de dades
  • INFO - ens dóna tot un conjunt d'informació de com està la nostra bd, és especialment interessant veure i monitoritzar el tamany de la memòria.

process_id:677 uptime_in_seconds:5349 uptime_in_days:0 connected_clients:14 connected_slaves:0 blocked_clients:4 used_memory:1551228 used_memory_human:1.48M changes_since_last_save:1111 bgsave_in_progress:0 last_save_time:1301824662 bgrewriteaof_in_progress:0 total_connections_received:509 total_commands_processed:7060 expired_keys:0 hash_max_zipmap_entries:64 hash_max_zipmap_value:512 pubsub_channels:1 pubsub_patterns:0 vm_enabled:0 role:master db0:keys=8,expires=0

  • FLUSHDB - per netejar tota la base de dades activa de Redis.
  • MONITOR - ens mostra què està passant, les claus que es generen, els resultats i les sentències que va executant Redis.

Conclusions

Amb Django, Celery i Redis hem aconseguit tenir un sistema de distribució de tasques simple, escalable i amb molts pocs requisits de màquina.

Redis es pot aprofitar tant pel sistema de cues com per la nostra aplicació, obrint-nos tot un món de possibilitats i mantenint baixa la complexitat de tota l'arquitectura.

Posar en marxa tot el sistema implica:

  • Desenvolupar l'aplicació pensant en les tasques
  • Configurar Redis
  • Executar els workers
  • Executar celerybeat per les tasques periòdiques si en tenim
  • Executar el monitor

I òbviament anar monitoritzant-ho tot. Que ho disfruteu!

blog comments powered by Disqus