目录
8、记得在配置文件中import你的任务文件,不然会报下面的错误
一、基本介绍
Github地址:https://github.com/celery/celery
最新的中文文档托管在 前言 - Celery 中文手册 中,包含用户指南、教程、API接口等。
Celery 是一款非常简单、灵活、可靠的分布式系统,可用于处理大量消息,并且提供了一整套操作此系统的一系列工具。
Celery 是一款消息队列工具,可用于处理实时数据以及任务调度。
二、注意事项 && 问题记录
1、Celery使用带密码的Redis
celery 使用密码连接redis,其中xxxxx是密码,密码前必须加冒号。
BROKER_URL='redis://:xxxxx@127.0.0.1:6379/2'
2、存储结果
详情可参考:使用Redis - Celery 中文手册
如果后端使用资源进行存储结果(比如Redis),必须要针对调用任务后返回每一个 AsyncResult 实例调用 get() 或 forget() ,进行资源释放。意思就是使用完运行的结果数据后,需要把数据删除,不然随着时间的迁移,数据量会暴增。
如果执行任务为:result = add.delay(4, 4),那么释放数据的方式可以为:result.forget()
3、不存储结果
这个要视具体的业务场景来看,如果对结果不关心,或者任务的执行本身会对数据产生影响,通过对数据的判断可以知道执行的结果那就不需要返回celery任务的退出状态,分两种情况:
注意设置完后重启生效:
- 全部结果都不保存:配置文件设置
CELERY_IGNORE_RESULT = True
- 指定任务不保存:ignore_result=True
@app.task(ignore_result=True)
def mytask(…):
something()
4、关于队列使用的问题
假设我们有两个2任务,分别是add和subtract,我们想让add这个加法任务优先于subtract减法任务被执行,我们可以将两个任务放到不同的队列中,由我们决定先执行哪个任务,我们可以在配置文件中这样配置
app.conf.update(
result_expires=3600,
task_routes = {
"celery-demo.tasks.add": {"queue": "for_add", "routing_key": "for_add"},
"celery-demo.tasks.subtract": {"queue": "for_subtract", "routing_key": "for_subtract"},
},
)
我将add这个函数任务放在了一个叫做for_add的队列里面,将subtract这个函数任务放在了一个叫做for_subtract的队列里面。
如果启动的worker只负责处理for_add这个队列的任务,启动命令如下:
celery -A celery_demo worker -Q for_add -l info
如果想启动的worker同时可以处理for_add以及for_subtract队列的任务,启动命令如下:
celery -A celery_demo worker -Q for_add,for_subtract -l info
说明:默认没有指定队列的任务一般是放在了celery这个队列来处理的(历史原因被命名为celery),当然也可以通过配置来修改。app.conf.task_default_queue = 'default'
假设现在我们启动了一个worker,且指处理for_add任务,如下:
Ps: 从打印的结果可以看出这里消息中间件(broker)和存储结果终端(backend)我们都用的redis,DB分别为20和21。
我们现在去执行subtract函数,如下,使用Python的终端:
>>> from tasks import subtract
>>>
>>> subtract.apply_async((2, 3), queue='for_subtract')
<AsyncResult: 4215043c-f2d4-4abc-bf32-78c63a8cdb46>
从消息中间件(broker)可以看出,for_subtract队列有一条消息没有被处理(积压)。这是符合预期的,因为我们只启动了一个只处理add任务的worker。
10.xx.xxx.xxx:7555[20]> keys *
celery
_kombu.binding.for_add
_kombu.binding.hipri
hipri
_kombu.binding.celeryev
_kombu.binding.for_subtract
for_subtract
_kombu.binding.celery
_kombu.binding.lowpri
_kombu.binding.celery.pidbox
10.xx.xxx.xxx:7555[20]>
10.xx.xxx.xxx:7555[20]> LLEN for_subtract
1
10.xx.xxx.xxx:7555[20]>
10.xx.xxx.xxx:7555[20]> LRANGE for_subtract 0 -1
{"body": "W1syLCAzXSwge30sIHsiY2FsbGJhY2tzIjogbnVsbCwgImVycmJhY2tzIjogbnVsbCwgImNoYWluIjogbnVsbCwgImNob3JkIjogbnVsbH1d", "content-encoding": "utf-8", "content-type": "application/json", "headers": {"lang": "py", "task": "tasks.subtract", "id": "4b2279e9-9b62-4d45-84b5-ad8840fa5b12", "shadow": null, "eta": null, "expires": null, "group": null, "group_index": null, "retries": 0, "timelimit": [null, null], "root_id": "4b2279e9-9b62-4d45-84b5-ad8840fa5b12", "parent_id": null, "argsrepr": "(2, 3)", "kwargsrepr": "{}", "origin": "gen3040@xxxxxx.xxx.xxxx.com"}, "properties": {"correlation_id": "4b2279e9-9b62-4d45-84b5-ad8840fa5b12", "reply_to": "6e2c2593-ff67-3bab-8c63-19e176a7fdd6", "delivery_mode": 2, "delivery_info": {"exchange": "", "routing_key": "for_subtract"}, "priority": 0, "body_encoding": "base64", "delivery_tag": "fd8a1a8c-19e2-4a97-ae2b-bd23fabf2bbd"}}
5、以守护进程方式运行worker
文中前面介绍的化脚本和配置脚本必须使用root用户,权限不足的用户不能够使用初始化脚本,可以使用celery multi工具(或者celery worker --detach)。
后台启动worker,可以使用--detach,命令示例如下:
celery -A celery_demo worker -Q for_add -l info --detach
或者使用multi命令:
celery multi start worker1 -A celery_demo worker -Q for_add --pidfile="/home/sokf/rs/celery-demo/pidflie/%n.pid" --logfile="/home/sokf/rs/celery-demo/log/%n%I.log"
6、Flower的使用
持久化任务命令示例如下:
celery -A proj flower --persistent=True --db=/flower/flower --max_tasks=100
This limits number of tasks that will be stored in the db. Once limit is reached, it will discard old tasks.
7、Celery + Flower的启动问题
我尝试用下面的命令启动flower:
celery flower --broker=redis://:xxxx@10.xx.xxx.xx:75xx/20
结果报错:
[W 220215 16:13:11 connection:632] No hostname was supplied. Reverting to default 'localhost'
看到了提示信息:
You have incorrectly specified the following celery arguments after flower command: ['--broker']. Please specify them after celery command instead following this template: celery [celery args] flower [flower args].
很明显,意思是说 ['--broker']的配置不对,因为"--broker"是celery的参数,应该放在flower的前面,接着我们修改下启动命令:
celery flower --broker=redis://:xxxx@10.xx.xxx.xx:75xx/20
现在就可以正常启动,并连接指定的broker了。
$ celery --broker='redis://:xxxx@10.xx.xxx.xx:75xx/20' flower
[I 220215 16:25:03 command:154] Visit me at http://localhost:5555
[I 220215 16:25:03 command:159] Broker: redis://:**@10.xx.xxx.xxx:xx55/20
[I 220215 16:25:03 command:162] Registered tasks:
['celery.accumulate',
'celery.backend_cleanup',
'celery.chain',
'celery.chord',
'celery.chord_unlock',
'celery.chunks',
'celery.group',
'celery.map',
'celery.starmap']
[I 220215 16:25:03 mixins:226] Connected to redis://:**@10.xx.xxx.xxx:xx55/20
8、记得在配置文件中import你的任务文件,不然会报下面的错误
新添加任务文件的时候比较容易忘记,比如我们新加了调试的任务文件tasks_debug,这个时候就需要import
报错信息:Received unregistered task of type 'tasks_xxx.xxx'.The message has been ignored and discarded.
Did you remember to import the module containing this task?
Or maybe you're using relative imports?
imports = ("tasks", "tasks_debug")
9、任务task之间是可以互相调用的,但是需要注意参数传递
假设我们现在2个任务:job_one和job_set,job_one在执行的过程中会调用job_set的逻辑,此时需要使用celery的方式调试,比如:job_set.apply_async,且只有一个参数时后面的逗号不能省略,详情可以参考如下的代码:
from __future__ import absolute_import, unicode_literals
from celery_app import app
@app.task
def job_one(x, y):
result = {"message": "success one", "code": 200, "data": {"add": x + y, "k_add": "v_add"}}
# 正确的调用方式, 作为消息传递给另一个队列处理, 注意只有一个参数时, 后面的逗号不能少
r = job_set.apply_async((result, ), queue='for_job_set')
# 注意下面的这种属于无效的调用
# job_set(result)
return result
@app.task
def job_set(x):
result = {"message": "success set", "code": 200, "data": x}
return result
这里其实有个问题,比如我们执行了job_set.apply_async((result, ), queue='for_job_set')这个命令(在任务中执行了子任务),想拿到其运行的结果供后续逻辑使用,如果你使用了r.get()的方式,如下,会报错:
......
r = job_one.apply_async((result, ), queue='for_job_set')
r.get(timeout=3, disable_sync_subtasks=False)
......
报错信息如下: "RuntimeError: Never call result.get() within a task!"
......
RuntimeError: Never call result.get() within a task!
See http://docs.celeryq.org/en/latest/userguide/tasks.html#task-synchronous-subtasks
......
可以看出celery建议避免在一个任务中启动同步子任务,参考:Tasks — Celery 5.2.3 documentation
可以使用chain()的形式,如下:
def update_page_info(url):
# fetch_page -> parse_page -> store_page
chain = fetch_page.s(url) | parse_page.s() | store_page_info.s(url)
chain()
@app.task()
def fetch_page(url):
return myhttplib.get(url)
@app.task()
def parse_page(page):
return myparser.parse_document(page)
@app.task(ignore_result=True)
def store_page_info(info, url):
PageInfo.objects.create(url=url, info=info)
当然如果你必须要这么使用的话,需要设置disable_sync_subtasks=False,如下所示,但这不是celery建议的,容易出现各种问题,比如死锁等,应该优化代码逻辑,使用类似上面chain()的方式。
r.get(disable_sync_subtasks=False)
10、获取任务的apply_async执行的结果
参考:【python小随笔】celery异步任务与调用返回值
11、在Flask使用Celery
参考:在 Flask 中使用 Celery — using celery with flask 1.00 documentation
Flask 与 Celery 整合是十分简单,不需要任何插件。详情可以看上面的文档及代码链接,注意的是:celery和flask是需要单独启动的,说明如下:
12、Celery启动是否是需要占用端口?
Celery是Python中任务队列的事实标准。其特点在于:
- 启动后,本身是一个任务分发进程,会启动若干个worker进程完成任务
- 需要依赖一个消息队列来负责任务从客户端到Celery进程的派发。这样的好处是,客户端代码只需要向MQ中派发任务请求以及参数,Celery进程就可以从MQ中读取消息并派发给worker,从而达到了客户端程序与Celery进程解耦的效果。而且Celery进程并不需要监听任何端口,减少了配置的复杂性。常用的消息队列实现可以使用RabbitMQ,Redis等等。
参考:
在 Flask 中使用 Celery — using celery with flask 1.00 documentation