Come posso recuperare un elenco di attività in una coda che devono ancora essere elaborate?
Come posso recuperare un elenco di attività in una coda che devono ancora essere elaborate?
Risposte:
EDIT: vedi altre risposte per ottenere un elenco di attività in coda.
Dovresti guardare qui: Guida al sedano - Ispezionando i lavoratori
Fondamentalmente questo:
from celery.app.control import Inspect
# Inspect all nodes.
i = Inspect()
# Show the items that have an ETA or are scheduled for later processing
i.scheduled()
# Show tasks that are currently active.
i.active()
# Show tasks that have been claimed by workers
i.reserved()
A seconda di cosa vuoi
i.reserved()
per ottenere un elenco di attività in coda.
inspect(['celery@Flatty'])
. Enorme miglioramento della velocità inspect()
.
se stai usando rabbitMQ, usa questo nel terminale:
sudo rabbitmqctl list_queues
stamperà un elenco di code con il numero di attività in sospeso. per esempio:
Listing queues ...
0b27d8c59fba4974893ec22d478a7093 0
0e0a2da9828a48bc86fe993b210d984f 0
10@torob2.celery.pidbox 0
11926b79e30a4f0a9d95df61b6f402f7 0
15c036ad25884b82839495fb29bd6395 1
celerey_mail_worker@torob2.celery.pidbox 0
celery 166
celeryev.795ec5bb-a919-46a8-80c6-5d91d2fcf2aa 0
celeryev.faa4da32-a225-4f6c-be3b-d8814856d1b6 0
il numero nella colonna di destra è il numero di attività nella coda. sopra, la coda di sedano ha 166 attività in sospeso.
grep -e "^celery\s" | cut -f2
per estrarre che 166
se si desidera elaborare quel numero in un secondo momento, dire statistiche.
Se non si utilizzano attività prioritarie, questo è piuttosto semplice se si utilizza Redis. Per ottenere il conteggio delle attività:
redis-cli -h HOST -p PORT -n DATABASE_NUMBER llen QUEUE_NAME
Tuttavia, le attività prioritarie utilizzano una chiave diversa in redis , quindi il quadro completo è leggermente più complicato. L'immagine completa è che è necessario interrogare redis per ogni priorità dell'attività. In python (e dal progetto Flower), questo assomiglia a:
PRIORITY_SEP = '\x06\x16'
DEFAULT_PRIORITY_STEPS = [0, 3, 6, 9]
def make_queue_name_for_pri(queue, pri):
"""Make a queue name for redis
Celery uses PRIORITY_SEP to separate different priorities of tasks into
different queues in Redis. Each queue-priority combination becomes a key in
redis with names like:
- batch1\x06\x163 <-- P3 queue named batch1
There's more information about this in Github, but it doesn't look like it
will change any time soon:
- https://github.com/celery/kombu/issues/422
In that ticket the code below, from the Flower project, is referenced:
- https://github.com/mher/flower/blob/master/flower/utils/broker.py#L135
:param queue: The name of the queue to make a name for.
:param pri: The priority to make a name with.
:return: A name for the queue-priority pair.
"""
if pri not in DEFAULT_PRIORITY_STEPS:
raise ValueError('Priority not in priority steps')
return '{0}{1}{2}'.format(*((queue, PRIORITY_SEP, pri) if pri else
(queue, '', '')))
def get_queue_length(queue_name='celery'):
"""Get the number of tasks in a celery queue.
:param queue_name: The name of the queue you want to inspect.
:return: the number of items in the queue.
"""
priority_names = [make_queue_name_for_pri(queue_name, pri) for pri in
DEFAULT_PRIORITY_STEPS]
r = redis.StrictRedis(
host=settings.REDIS_HOST,
port=settings.REDIS_PORT,
db=settings.REDIS_DATABASES['CELERY'],
)
return sum([r.llen(x) for x in priority_names])
Se vuoi ottenere un vero compito, puoi usare qualcosa come:
redis-cli -h HOST -p PORT -n DATABASE_NUMBER lrange QUEUE_NAME 0 -1
Da lì dovrai deserializzare l'elenco restituito. Nel mio caso sono stato in grado di ottenere questo risultato con qualcosa del tipo:
r = redis.StrictRedis(
host=settings.REDIS_HOST,
port=settings.REDIS_PORT,
db=settings.REDIS_DATABASES['CELERY'],
)
l = r.lrange('celery', 0, -1)
pickle.loads(base64.decodestring(json.loads(l[0])['body']))
Basta essere avvisati che la deserializzazione può richiedere un momento e dovrai adattare i comandi sopra per lavorare con varie priorità.
DATABASE_NUMBER
usato di default è 0
e l' QUEUE_NAME
è celery
, quindi redis-cli -n 0 llen celery
restituirà il numero di messaggi in coda.
'{{{0}}}{1}{2}'
invece di '{0}{1}{2}'
. A parte questo, funziona perfettamente!
Per recuperare attività dal back-end, utilizzare questo
from amqplib import client_0_8 as amqp
conn = amqp.Connection(host="localhost:5672 ", userid="guest",
password="guest", virtual_host="/", insist=False)
chan = conn.channel()
name, jobs, consumers = chan.queue_declare(queue="queue_name", passive=True)
Se stai usando Celery + Django il modo più semplice per ispezionare le attività usando i comandi direttamente dal tuo terminale nel tuo ambiente virtuale o usando un percorso completo per il sedano:
Doc : http://docs.celeryproject.org/en/latest/userguide/workers.html?highlight=revoke#inspecting-workers
$ celery inspect reserved
$ celery inspect active
$ celery inspect registered
$ celery inspect scheduled
Inoltre, se stai usando Celery + RabbitMQ puoi controllare l'elenco delle code usando il seguente comando:
Maggiori informazioni : https://linux.die.net/man/1/rabbitmqctl
$ sudo rabbitmqctl list_queues
celery -A my_proj inspect reserved
Una soluzione copia-incolla per Redis con serializzazione json:
def get_celery_queue_items(queue_name):
import base64
import json
# Get a configured instance of a celery app:
from yourproject.celery import app as celery_app
with celery_app.pool.acquire(block=True) as conn:
tasks = conn.default_channel.client.lrange(queue_name, 0, -1)
decoded_tasks = []
for task in tasks:
j = json.loads(task)
body = json.loads(base64.b64decode(j['body']))
decoded_tasks.append(body)
return decoded_tasks
Funziona con Django. Basta non dimenticare di cambiare yourproject.celery
.
body =
linea in body = pickle.loads(base64.b64decode(j['body']))
.
Il modulo di ispezione del sedano sembra essere consapevole solo dei compiti dal punto di vista dei lavoratori. Se si desidera visualizzare i messaggi in coda (che devono ancora essere estratti dai lavoratori), suggerisco di usare pyrabbit , che può interfacciarsi con l'api http di rabbitmq per recuperare tutti i tipi di informazioni dalla coda.
Un esempio può essere trovato qui: Recupera la lunghezza della coda con Sedano (RabbitMQ, Django)
Penso che l'unico modo per ottenere le attività in attesa sia quello di mantenere un elenco di attività avviate e lasciare che l'attività si rimuova dall'elenco quando viene avviato.
Con rabbitmqctl e list_queues è possibile ottenere una panoramica di quante attività sono in attesa, ma non delle attività stesse: http://www.rabbitmq.com/man/rabbitmqctl.1.man.html
Se ciò che desideri include l'attività in fase di elaborazione, ma non è ancora terminata, puoi tenere un elenco delle attività e verificarne gli stati:
from tasks import add
result = add.delay(4, 4)
result.ready() # True if finished
Oppure lasci che Celery memorizzi i risultati con CELERY_RESULT_BACKEND e controlli quali attività non sono presenti.
Questo ha funzionato per me nella mia applicazione:
def get_celery_queue_active_jobs(queue_name):
connection = <CELERY_APP_INSTANCE>.connection()
try:
channel = connection.channel()
name, jobs, consumers = channel.queue_declare(queue=queue_name, passive=True)
active_jobs = []
def dump_message(message):
active_jobs.append(message.properties['application_headers']['task'])
channel.basic_consume(queue=queue_name, callback=dump_message)
for job in range(jobs):
connection.drain_events()
return active_jobs
finally:
connection.close()
active_jobs
sarà un elenco di stringhe che corrispondono alle attività in coda.
Non dimenticare di scambiare CELERY_APP_INSTANCE con il tuo.
Grazie a @ashish per avermi indicato nella giusta direzione con la sua risposta qui: https://stackoverflow.com/a/19465670/9843399
jobs
è sempre zero ... qualche idea?
Per quanto ne so, il sedano non fornisce l'API per l'esame delle attività in attesa in coda. Questo è specifico del broker. Se usi Redis come broker per un esempio, esaminare le attività in attesa nella celery
coda (predefinita) è semplice come:
celery
nell'elenco (comando LRANGE per un esempio)Tieni presente che si tratta di attività IN ATTESA di essere raccolte dai lavoratori disponibili. Il cluster potrebbe avere alcune attività in esecuzione: quelle non saranno in questo elenco poiché sono già state selezionate.
Sono giunto alla conclusione che il modo migliore per ottenere il numero di lavori su una coda è utilizzare rabbitmqctl
come è stato suggerito più volte qui. Per consentire a qualsiasi utente scelto di eseguire il comando con sudo
ho seguito le istruzioni qui (ho saltato la modifica della parte del profilo poiché non mi dispiace digitare in sudo prima del comando.)
Ho anche preso jamesc grep
e cut
snippet e lo ho avvolto in chiamate di sottoprocesso.
from subprocess import Popen, PIPE
p1 = Popen(["sudo", "rabbitmqctl", "list_queues", "-p", "[name of your virtula host"], stdout=PIPE)
p2 = Popen(["grep", "-e", "^celery\s"], stdin=p1.stdout, stdout=PIPE)
p3 = Popen(["cut", "-f2"], stdin=p2.stdout, stdout=PIPE)
p1.stdout.close()
p2.stdout.close()
print("number of jobs on queue: %i" % int(p3.communicate()[0]))
Se controlli il codice delle attività, puoi aggirare il problema lasciando che un'attività attivi un nuovo tentativo banale la prima volta che viene eseguita, quindi controllando inspect().reserved()
. Il nuovo tentativo registra l'attività con il risultato backend e il sedano può vederlo. L'attività deve accettare self
o context
come primo parametro in modo da poter accedere al conteggio dei tentativi.
@task(bind=True)
def mytask(self):
if self.request.retries == 0:
raise self.retry(exc=MyTrivialError(), countdown=1)
...
Questa soluzione è agnostica del broker, vale a dire. non devi preoccuparti se stai usando RabbitMQ o Redis per archiviare le attività.
EDIT: dopo i test ho scoperto che questa è solo una soluzione parziale. La dimensione di riservato è limitata all'impostazione di prefetch per il lavoratore.
Con subprocess.run
:
import subprocess
import re
active_process_txt = subprocess.run(['celery', '-A', 'my_proj', 'inspect', 'active'],
stdout=subprocess.PIPE).stdout.decode('utf-8')
return len(re.findall(r'worker_pid', active_process_txt))
Fai attenzione a cambiare my_proj
conyour_proj