特殊任务后的芹菜关闭工人

时间:2018-03-18 14:15:02

标签: python celery

我正在使用芹菜(并发池的并发数为1),我希望能够在特定任务运行后关闭工作人员。需要注意的是,我希望避免工人在那之后接受任何进一步任务的可能性。

这是我在大纲中的尝试:

from __future__ import absolute_import, unicode_literals
from celery import Celery
from celery.exceptions import WorkerShutdown
from celery.signals import task_postrun

app = Celery()
app.config_from_object('celeryconfig')

@app.task
def add(x, y):
    return x + y

@task_postrun.connect(sender=add)
def shutdown(*args, **kwargs):
    raise WorkerShutdown()

但是,当我运行工作人员时

celery -A celeryapp  worker --concurrency=1 --pool=solo

并运行任务

add.delay(1,4)

我得到以下内容:

 -------------- celery@sam-APOLLO-2000 v4.0.2 (latentcall)
---- **** ----- 
--- * ***  * -- Linux-4.4.0-116-generic-x86_64-with-Ubuntu-16.04-xenial 2018-03-18 14:08:37
-- * - **** --- 
- ** ---------- [config]
- ** ---------- .> app:         __main__:0x7f596896ce90
- ** ---------- .> transport:   redis://localhost:6379/0
- ** ---------- .> results:     redis://localhost/
- *** --- * --- .> concurrency: 4 (solo)
-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)
--- ***** ----- 
 -------------- [queues]
                .> celery           exchange=celery(direct) key=celery


[2018-03-18 14:08:39,892: WARNING/MainProcess] Restoring 1 unacknowledged message(s)

任务重新排队,将在另一个工作人员上再次运行,从而导致循环。

当我在任务本身内移动WorkerShutdown异常时,也会发生这种情况。

@app.task
def add(x, y):
    print(x + y)
    raise WorkerShutdown()

有没有办法在特定任务后关闭工人,同时避免这种不幸的副作用?

3 个答案:

答案 0 :(得分:5)

关闭工作人员的建议流程是发送TERM信号。这将导致芹菜工人在完成任何当前正在运行的任务后关闭。如果向工作人员的主进程发送QUIT信号,则工作人员将立即关闭。

然而,芹菜文档通常在从命令行或通过systemd / initd管理芹菜方面讨论这个问题,但芹菜还通过celery.app.control提供远程工作人员控制API。 您可以revoke任务来阻止工作人员执行任务。这应该可以防止您遇到的循环。此外,控制也以这种方式支持工人的shutdown

所以我想以下内容会让你得到你想要的行为。

@app.task(bind=True)
def shutdown(self):
    app.control.revoke(self.id) # prevent this task from being executed again
    app.control.shutdown() # send shutdown signal to all workers

由于目前无法从任务中执行任务,因此继续执行所述任务,这种使用revoke的方法可以避免此问题,即使任务再次排队,新员工会忽略它。

或者,以下内容也会阻止重新传送的任务再次执行......

@app.task(bind=True)
def some_task(self):
    if self.request.delivery_info['redelivered']:
        raise Ignore() # ignore if this task was redelivered
    print('This should only execute on first receipt of task')

另外值得注意的是AsyncResult还有revoke方法可以为您调用self.app.control.revoke

答案 1 :(得分:2)

如果关闭工作人员,任务完成后,它将不会再次重新排队。

@task_postrun.connect(sender=add)
def shutdown(*args, **kwargs):
    app.control.broadcast('shutdown')

这将在任务完成后正常关闭工作人员。

[2018-04-01 18:44:14,627: INFO/MainProcess] Connected to redis://localhost:6379/0
[2018-04-01 18:44:14,656: INFO/MainProcess] mingle: searching for neighbors
[2018-04-01 18:44:15,719: INFO/MainProcess] mingle: all alone
[2018-04-01 18:44:15,742: INFO/MainProcess] celery@foo ready.
[2018-04-01 18:46:28,572: INFO/MainProcess] Received task: celery_worker_stop.add[ac8a65ff-5aad-41a6-a2d6-a659d021fb9b]
[2018-04-01 18:46:28,585: INFO/ForkPoolWorker-4] Task celery_worker_stop.add[ac8a65ff-5aad-41a6-a2d6-a659d021fb9b] succeeded in 0.005628278013318777s: 3   
[2018-04-01 18:46:28,665: WARNING/MainProcess] Got shutdown from remote

注意:广播将关闭所有工作人员。如果要关闭特定工作人员,请使用名称

启动工作人员
celery -A celeryapp  worker -n self_killing --concurrency=1 --pool=solo

现在您可以使用目标参数关闭它。

app.control.broadcast('shutdown', destination=['celery@self_killing'])

答案 2 :(得分:0)

If you need to shutdown a specific worker and don't know it's name in advance, you can get it from the task properties. Based on the answers above, you can use:

app.control.shutdown(destination=[self.request.hostname])

or

app.control.broadcast('shutdown', destination=[self.request.hostname])

Note:

  • A worker should be started with a name (option '-n');
  • The task should be defined with bind=True parameter.