简介
- Celery是一个简单的/灵活且可靠的,处理大量消息的分布式系统
- 专注于实时处理的异步任务队列
- 同时也支持任务调度
架构图
组件介绍
-
Producer : 任务生产者. 调用 Celery API , 函数或者装饰器, 而产生任务并交给任务队列处理的都是任务生产者。
-
Broker : 消息代理, 队列本身. 也称为消息中间件. 接受任务生产者发送过来的任务消息, 存进队列再按序分发给任务消费方(通常是消息队列或者数据库). 通常用 RabbitMQ或者Redis
-
Celery Beat : 任务调度器. Beat 进程会读取配置文件的内容, 周期性的将配置中到期需要执行的任务发送给任务队列.例如如下配置的一个周期性执行的一个任务:
CELERYBEAT_SCHEDULE = { 'send_mail': { 'task': 'work.notify.email.send_mail', # 'schedule': timedelta(minute=1), 'schedule': crontab(minute='*/1'), 'args': ('usr', 'sub', 'msg') } }
任务调度主要是为了解决业务场景中定时或周期任务,分别使用timedelta和crontab来定义计划任务,crontab的精度无法精确到秒时可使用timedelta代替,CELERYBEAT_SCHEDULE下可以定义多个计划/周期任务,send_mail为任务名称,task为任务单元导入名,schedule为具体调度,args为任务单元的参数.
运行时可先启动work进程池(celery worker -A work.app -l info)然后再启动beat进程池(celery beat -A work.app -l info),观察会发现beat进程每分钟生成一个任务,work进程发现任务后立即执行
-
Celery Worker : 执行任务的消费者, 通常会在多台服务器运行多个消费者, 提高运行效率.
-
Result Backend : 任务处理完成之后保存状态信息和结果, 以供查询.
依赖库
- billiard : 基于 Python2.7 的 multisuprocessing 而改进的库, 主要用来提高性能和稳定性
- librabbitmp : C 语言实现的 Python 客户端,
- kombu : Celery 自带的用来收发消息的库, 提供了符合 Python 语言习惯的, 使用 AMQP 协议的高级借口.
消息代理
使用于生产环境的消息代理有 RabbitMQ 和 Redis, 官方推荐 RabbitMQ.
使用场景
- 异步任务,将耗时操作提交给Celery去异步执行,比如发送短信/邮件/消息推送。音视频处理等等
- 定时任务。类似于crontab,比如每日数据统计
序列化
方案 | 说明 |
---|---|
pickle | pickle 是Python 标准库中的一个模块, 支持 Pyuthon 内置的数据结构, 但他是 Python 的专有协议. Celery 官方不推荐. |
son | json 支持多种语言, 可用于跨语言方案. |
yaml | yaml 表达能力更强, 支持的数据类型较 json 多, 但是 python 客户端的性能不如 json |
msgpack | 二进制的类 json 序列化方案, 但比 json 的数据结构更小, 更快. |
配置文件参数说明
配置项 | 说明 |
---|---|
CELERY_DEFAULT_QUEUE | 默认队列 |
CELERY_BROKER_URL | Broker 地址 |
CELERY_RESULT_BACKEND | 结果存储地址 |
CELERY_TASK_SERIALIZER | 任务序列化方式 |
CELERY_RESULT_SERIALIZER | 任务执行结果序列化方式 |
CELERY_TASK_RESULT_EXPIRES | 任务过期时间 |
CELERY_ACCEPT_CONTENT | 指定任务接受的内容类型(序列化) |
代码示例:
# 安装
$ pip install celery, redis, msgpack
# 配置文件 celeryconfig.py
CELERY_BROKER_URL = 'redis://localhost:6379/1'
CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24 # 任务过期时间
CELERY_ACCEPT_CONTENT = ["json"] # 指定任务接受的内容类型.
# 初始化文件 celery.py
from __future__ import absolute_import
from celery import Celery
app = Celery('proj', include=["proj.tasks"])
app.config_from_object("proj.celeryconfig")
if __name__ == "__main__":
app.start()
# 任务文件 tasks.py
from __future__ import absolute_import
from proj.celery import app
@app.task
def add(x, y):
return x + y
# 启动消费者
$ celery -A proj worker -l info
# 在终端中测试
> from proj.tasks import add
> r = add.delay(2,4)
> r.result
6
> r.status
u"SUCCESS"
> r.successful()
True
> r.ready() # 返回布尔值, 任务执行完成, 返回 True, 否则返回 False.
> r.wait() # 等待任务完成, 返回任务执行结果.
> r.get() # 获取任务执行结果
> r.result # 任务执行结果.
> r.state # PENDING, START, SUCCESS
> r.status # PENDING, START, SUCCESS
# 使用 AsyncResult 方式获取执行结果.
# AsyncResult 主要用来存储任务执行信息与执行结果(类似 js 中的 Promise 对象),
> from celery.result import AsyncResult
> AsyncResult(task_id).get()
4
任务调度的方法
-
delay
task.delay(args1, args2, kwargs=value_1, kwargs2=value_2)
-
apply_async
delay 实际上是 apply_async 的别名, 还可以使用如下方法调用, 但是 apply_async 支持更多的参数:task.apply_async(args=[arg1, arg2], kwargs={key:value, key:value})
支持的参数
-
countdown : 等待一段时间再执行.
add.apply_async((2,3), countdown=5)
-
eta : 定义任务的开始时间
add.apply_async((2,3), eta=now+tiedelta(second=10))
-
expires : 设置超时时间.
add.apply_async((2,3), expires=60)
-
retry : 定时如果任务失败后, 是否重试.
add.apply_async((2,3), retry=False)
-
retry_policy : 重试策略.
- max_retries : 最大重试次数, 默认为 3 次.
- interval_start : 重试等待的时间间隔秒数, 默认为 0 , 表示直接重试不等待.
- interval_step : 每次重试让重试间隔增加的秒数, 可以是数字或浮点数, 默认为 0.2
- interval_max : 重试间隔最大的秒数, 即 通过 interval_step 增大到多少秒之后, 就不在增加了, 可以是数字或者浮点数, 默认为 0.2
自定义发布者,交换机,路由键, 队列, 优先级,序列方案和压缩方法:
task.apply_async((2,2), compression='zlib', serialize='json', queue='priority.high', routing_key='web.add', priority=0, exchange='web_exchange')
-
使用任务调度
from datetime import timedelta
from celery.schedules import crontab
BORKER_URL = 'redis://127.0.0.1:6379/1'
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/2'
# 设置时区
CELERY_TIMEZONE = 'Asia/Shanghai'
# 导入指定的任务模块
CELERY_IMPORT = (
'celery_app.task1',
'celery_app.task2',
)
CELERYBEAT_SCHEDULE = {
# 每隔10秒钟执行任务
'task1':{
'task':'celery_app.task1.add',
'schedule':timedelta(seconds=10),
'args':(2,8)
},
# 每天的19:28分执行任务
'task2':{
'task':'celery_app.task2.multiply',
'schedule':crontab(hour=19,minute=28),
'args':(4,5)
}
}
# 使用
# Worker celery worker -A task -l INFO 启动worker
# Beat: celery beat -A task -l INFO 启动beat
# celery -B -A task worker -l INFO 同时启动beat worker
# celery worker --help
使用自定义调度类还可以实现动态添加任务. 使用 Django 可以通过 Django-celery 实现在管理后台创建,删除,更新任务, 是因为他使用了自定义的 调度类 djcelery.schedulers.DatabaseScheduler .
任务绑定, 记录日志, 重试
# 修改 tasks.py 文件.
from celery.utils.log import get_task_logger
logger = get_task_logger(__name__)
@app.task(bind=True)
def div(self, x, y):
logger.info(('Executing task id {0.id}, args: {0.args!r}'
'kwargs: {0.kwargs!r}').format(self.request))
try:
result = x/y
except ZeroDivisionError as e:
raise self.retry(exc=e, countdown=5, max_retries=3) # 发生 ZeroDivisionError 错误时, 每 5s 重试一次, 最多重试 3 次.
return result
当使用 bind=True 参数之后, 函数的参数发生变化, 多出了参数 self, 这这相当于把 div 编程了一个已绑定的方法, 通过 self 可以获得任务的上下文.
信号系统
信号可以帮助我们了解任务执行情况, 分析任务运行的瓶颈. Celery 支持 7 种信号类型.
-
任务信号
- before_task_publish : 任务发布前
- after_task_publish : 任务发布后
- task_prerun : 任务执行前
- task_postrun : 任务执行后
- task_retry : 任务重试时
- task_success : 任务成功时
- task_failure : 任务失败时
- task_revoked : 任务被撤销或终止时
-
应用信号
-
Worker 信号
-
Beat 信号
-
Eventlet 信号
-
日志信号
-
命令信号
不同的信号参数格式不同, 具体格式参见官方文档
# 在执行任务 add 之后, 打印一些信息.
@after_task_publish
def task_send_handler(sender=None, body=None, **kwargs):
print 'after_task_publish: task_id: {body[id]}; sender: {sender}'.format(body=body, sender=sender)
根据任务状态执行不同操作
# tasks.py
class MyTask(Task):
def on_success(self, retval, task_id, args, kwargs):
print 'task done: {0}'.format(retval)
return super(MyTask, self).on_success(retval, task_id, args, kwargs)
def on_failure(self, exc, task_id, args, kwargs, einfo):
print 'task fail, reason: {0}'.format(exc)
return super(MyTask, self).on_failure(exc, task_id, args, kwargs, einfo)
# 正确函数, 执行 MyTask.on_success() :
@app.task(base=MyTask)
def add(x, y):
return x + y
# 错误函数, 执行 MyTask.on_failure() :
@app.task #普通函数装饰为 celery task
def add(x, y):
raise KeyError
return x + y
Flower web 监控工具
- 查看任务历史,任务具体参数,开始时间等信息;
- 提供图表和统计数据
- 实现全面的远程控制功能, 包括但不限于 撤销/终止任务, 关闭重启 worker, 查看正在运行任务
- 提供一个 HTTP API , 方便集成.
Flower 的 supervisor 管理配置文件:
[program:flower]
command=/opt/PyProjects/venv/bin/flower -A celery_worker:celery --broker="redis://localhost:6379/2" --address=0.0.0.0 --port=5555
directory=/opt/PyProjects/app
autostart=true
autorestart=true
startretries=3
user=derby
stdout_logfile=/var/logs/%(program_name)s.log
stdout_logfile_maxbytes=50MB
stdout_logfile_backups=30
stderr_logfile=/var/logs/%(program_name)s-error.log
stderr_logfile_maxbytes=50MB
stderr_logfile_backups=3