在 Celery worker 中捕获 Heroku SIGTERM 以优雅地关闭 worker

Capture Heroku SIGTERM in Celery workers to shutdown worker gracefully

我对此进行了大量研究,令我惊讶的是我还没有在任何地方找到一个好的答案。

我在 Heroku 上 运行 运行一个大型应用程序,我有一些 celery 任务需要 运行 很长时间的处理,并在任务结束时保存结果。每次我在 Heroku 上重新部署时,它都会发送 SIGTERM(最后是 SIGKILL)并杀死我的 运行ning worker。我正在尝试找到一种方法让工作实例正常关闭自己并重新排队等待稍后处理,以便最终我们可以保存所需的结果而不是丢失排队的任务。

我找不到让工作人员正确收听 SIGTERM 的方法。我得到的最接近的,当 运行ning python manage.py celeryd 直接工作,但 NOT 使用工头模拟 Heroku 时,工作如下:

@app.task(bind=True, max_retries=1)
def slow(self, x):
    try:
        for x in range(100):
            print 'x: ' + unicode(x)
            time.sleep(10)
    except exceptions.MaxRetriesExceededError:
        logger.error('whoa')
    except (exceptions.WorkerShutdown, exceptions.WorkerTerminate) as exc:
        logger.error(u'retrying, ' + unicode(exc))
        raise self.retry(exc=exc, countdown=10)
    except (KeyboardInterrupt, SystemExit) as exc:
        print 'retrying'
        raise self.retry(exc=exc, countdown=10)
    else:
        return x
    finally:
        logger.info('task ended!')

当我在 foreman 中启动这个 celery 任务 运行ning 并按下 Ctrl+C 时,会发生以下情况:

^CSIGINT received
22:20:59 system   | sending SIGTERM to all processes
22:20:59 web.1    | exited with code 0
22:21:04 system   | sending SIGKILL to all processes
Killed: 9

很明显,none 的 celery 异常,以及我在其他帖子中看到的 KeyboardInterruptSystemExit 异常,都可以正确捕获 SIGTERM 并关闭 worker。

正确的做法是什么?

celery 不幸的是没有被设计成干净关闭。曾经。我是认真的。 celery workers 响应 SIGTERM 但如果任务未完成,则 worker 进程将等待完成任务然后退出。在这种情况下,如果工作人员没有在合理的时间内关闭,您可以发送 SIGKILL,但在这种情况下会丢失信息,即您可能不知道哪些工作仍未完成。

您可以使用 acks_late or task_acks_late.

任务将从队列 任务完成执行之后而不是之前确认。因此,如果工作人员正常关闭,任务将重新生成。

从版本 >= 4 开始,Celery 带有一个特殊功能,仅适用于 Heroku,开箱即用地支持此功能:

$ REMAP_SIGTERM=SIGQUIT celery -A proj worker -l info

来源:https://devcenter.heroku.com/articles/celery-heroku#using-remap_sigterm