分布式任务队列:Celery使用记录

目录

一、基本介绍

二、注意事项 && 问题记录

1、Celery使用带密码的Redis

2、存储结果

3、不存储结果

4、关于队列使用的问题

5、以守护进程方式运行worker

6、Flower的使用

7、Celery + Flower的启动问题

8、记得在配置文件中import你的任务文件,不然会报下面的错误

9、任务task之间是可以互相调用的,但是需要注意参数传递

10、获取任务的apply_async执行的结果

11、在Flask使用Celery

12、Celery启动是否是需要占用端口?


一、基本介绍

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

参考:Celery教程-以守护进程方式运行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.

参考:persistence - Flush Flower database occasionally and/or exit gracefully from Docker? - Stack Overflow

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等等。

参考:

Celery 框架学习笔记

在 Flask 中使用 Celery — using celery with flask 1.00 documentation

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

rs勿忘初心

您的鼓励将是我的最大创动原动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值