celery
一. celery 简介
Celery 是一个专注于实时处理和任务调度的分布式任务队列, 同时提供操作和维护分布式系统所需的工具.. 所谓任务就是消息, 消息中的有效载荷中包含要执行任务需要的全部数据.
Celery 是一个分布式队列的管理工具, 可以用 Celery 提供的接口快速实现并管理一个分布式的任务队列.
Celery 本身不是任务队列, 是管理分布式任务队列的工具. 它封装了操作常见任务队列的各种操作, 我们使用它可以快速进行任务队列的使用与管理.
1.Celery 特性 :
- 方便查看定时任务的执行情况, 如 是否成功, 当前状态, 执行任务花费的时间等.
- 使用功能齐备的管理后台或命令行添加,更新,删除任务.
- 方便把任务和配置管理相关联.
- 可选 多进程, Eventlet 和 Gevent 三种模型并发执行.
- 提供错误处理机制.
- 提供多种任务原语, 方便实现任务分组,拆分,和调用链.
- 支持多种消息代理和存储后端.
- Celery 是语言无关的.它提供了python 等常见语言的接口支持
2.Celery 组件
1. Celery 扮演生产者和消费者的角色,
Celery Beat : 任务调度器. Beat 进程会读取配置文件的内容, 周期性的将配置中到期需要执行的任务发送给任务队列.
Celery Worker : 执行任务的消费者, 通常会在多台服务器运行多个消费者, 提高运行效率.
Broker : 消息代理, 队列本身. 也称为消息中间件. 接受任务生产者发送过来的任务消息, 存进队列再按序分发给任务消费方(通常是消息队列或者数据库).
Producer : 任务生产者. 调用 Celery API , 函数或者装饰器, 而产生任务并交给任务队列处理的都是任务生产者.
Result Backend : 任务处理完成之后保存状态信息和结果, 以供查询.
2. 产生任务的方式 :
发布者发布任务(WEB 应用)
任务调度按期发布任务(定时任务)
3. celery 依赖三个库: 这三个库, 都由 Celery 的开发者开发和维护.
billiard : 基于 Python2.7 的 multisuprocessing 而改进的库, 主要用来提高性能和稳定性.
librabbitmp : C 语言实现的 Python 客户端,
kombu : Celery 自带的用来收发消息的库, 提供了符合 Python 语言习惯的, 使用 AMQP 协议的高级接口.
3.选择消息代理
使用于生产环境的消息代理有 RabbitMQ 和 Redis, 官方推荐 RabbitMQ.
4.Celery 序列化
在客户端和消费者之间传输数据需要 序列化和反序列化. Celery 支出的序列化方案如下所示:
方案 | 说明 |
---|---|
pickle | pickle 是Python 标准库中的一个模块, 支持 Pyuthon 内置的数据结构, 但他是 Python 的专有协议. Celery 官方不推荐. |
json | json 支持多种语言, 可用于跨语言方案. |
yaml | yaml 表达能力更强, 支持的数据类型较 json 多, 但是 python 客户端的性能不如 json |
msgpack | 二进制的类 json 序列化方案, 但比 json 的数据结构更小, 更快. |
二. 安装,配置与简单示例
1.安装
pip install celery, redis, msgpack
Celery 配置参数汇总
配置项 | 说明 |
---|---|
CELERY_DEFAULT_QUEUE | 默认队列 |
CELERY_BROKER_URL | Broker 地址 |
CELERY_RESULT_BACKEND | 结果存储地址 |
CELERY_TASK_SERIALIZER | 任务序列化方式 |
CELERY_RESULT_SERIALIZER | 任务执行结果序列化方式 |
CELERY_TASK_RESULT_EXPIRES | 任务过期时间 |
CELERY_ACCEPT_CONTENT | 指定任务接受的内容类型(序列化) |
详情链接 https://blog.csdn.net/libing_thinking/article/details/78812472
2.初始demo
1.目录
2.配置文件
CELERY_BROKER_URL = 'redis://localhost:6379/0' CELERY_RESULT_BACKEND = 'redis://localhost:6379/1' CELERY_TASK_SERIALIZER = 'json' CELERY_RESULT_SERIALIZER = 'json' CELERY_TASK_RESULT_EXPIRES = 60 * 5 # 任务过期时间 CELERY_ACCEPT_CONTENT = ["json"] # 指定任务接受的内容类型.
3.task 文件
import time
from celery import Celery
from celery_test import celery_setting as CelerySetting
# 创建一个Celery实例,这就是我们用户的应用app
app = Celery('celery_test.s1')
app.config_from_object(CelerySetting)
# 为应用创建任务,my_first_task
@app.task
def task_1(x, y):
# time.sleep(3)
return x + y
# celery worker -A celery_test.s1 -n task_1 -c 1 -l info -P gevent
@app.task
def tsum(numbers):
return sum(numbers)
# celery worker -A celery_test.s1 -n tsum -c 1 -l info -P gevent
@app.task
def mul(x, y):
return x * y
# celery worker -A celery_test.s1 -n mul -c 1 -l info -P gevent
4.调用任务及回调文件
from celery_test.s1 import task_1, tsum, app, mul
from celery import chain, chord, group
from celery.result import AsyncResult
# 1.将任务交给Celery的Worker执行
res = task_1.delay(1, 4)
print(res.id) # 返回任务ID
print(res.state) # 状态 如果任务未完成 PENDING
print(res.status) # 状态 如果任务未完成 none
print(res.successful()) # 是否成功 如果任务未完成 none
print(res.ready()) # 返回布尔值, 任务执行完成, 返回 True, 否则返回 False.
print(res.wait()) # 等待任务完成, 返回任务执行结果
print(res.get()) # 获取任务执行结果
print(res.result) # 任务执行结果
# 2.查看结果 异步获取任务返回值
async_task = AsyncResult(id="030df922-b7c3-44c9-b137-bf8e3d5e8eb5", app=app)
# 判断异步任务是否执行成功
print(async_task.successful())
if async_task.successful():
print(async_task.get()) # 获取异步任务的返回值
3.定义任务队列
Celery 默认使用名为 celery 的队列 (可以通过 CELERY_DEFAULT_QUEUE 修改) 来存放任务. 我们可以使用 优先级不同的队列 来确保高优先级的任务优先执行
# coding:utf-8
from __future__ import absolute_import
from celery import Celery
from celery_queue import celery_setting as CelerySetting
from kombu import Queue
app = Celery('celery_queue.s1')
app.config_from_object(CelerySetting)
app.conf.update({
"CELERY_QUEUES": ( # 设置add队列,绑定routing_key
Queue('queue_1', routing_key='queue_1', durable=False, max_priority=10), # durable:持久化 max_priorty:优先级
Queue('queue_2', routing_key='queue_2', durable=False),
Queue('queue_3', routing_key='queue_3'),
),
"CELERY_ROUTES": { # projq.tasks.add这个任务进去add队列并routeing_key为queue_1
'celery_queue.s1.add': {
'queue': 'queue_1',
'routing_key': 'queue_1',
},
'celery_queue.s1.tsum': {
'queue': 'queue_2',
'routing_key': 'queue_2',
},
'celery_queue.s1.mul': {
'queue': 'queue_3',
'routing_key': 'queue_3',
}
}}
)
@app.task()
def add(x, y):
return x + y
# celery worker -A celery_queue.s1 -n add -c 1 -l info -P gevent
@app.task
def tsum(numbers):
return sum(numbers)
# celery worker -A celery_queue.s1 -n tsum -c 1 -l info -P gevent
@app.task
def mul(x, y):
return x * y
# celery worker -A celery_queue.s1 -n mul -c 1 -l info -P gevent
阅后即焚模式
from kombu import Queue
Queue('transient', routing_key='transient', delivery_mode=1)
4.简单实际应用的例子
1. celery_setting.py
'''
celery配置
'''
task_acks_late = True
worker_prefetch_multiplier = 1
# 限制最大使用内存,限制celery执行10个任务,就销毁重建
worker_max_memory_per_child = 150000
task_reject_on_worker_lost = True
broker_pool_limit = 300
timezone = "Asia/Shanghai"
broker_url = 'amqp://guest:guest@localhost:5672/{vhost}?heartbeat=0'
# 优先级参数必须加
celery_acks_late = True
celeryd_prefetch_multiplier = 1
2. my_task.py
import time
from celery import Celery
from my_celery import celery_setting
from kombu import Exchange, Queue
app = Celery('celery1.my_task')
app.config_from_object(celery_setting)
app.conf.update(
broker_url="amqp://guest:guest@localhost:5672/{vhost}?heartbeat=0".format(vhost="test")
)
app.conf.task_queues = [
Queue('priority_test_1', Exchange('default', type='direct'), routing_key='default',
queue_arguments={'x-max-priority': 10}),
Queue('priority_test_2', Exchange('default', type='direct'), routing_key='default',
queue_arguments={'x-max-priority': 10}),
]
@app.task(bind=True,
queue='priority_test_1', # 指定队列名
# max_retries=10, # 最大重试
# default_retry_delay=600, # 重试间隔时间
autoretry_for=(TypeError, KeyError) # 指定重试错误
)
def priority_test_1(self, data):
# print(self)
print(data)
@app.task(bind=True,
queue='priority_test_2',
)
def priority_test_2(self, data):
try:
print(data["1"])
time.sleep(2)
except (TypeError, KeyError) as exc:
raise self.retry(exc=exc, countdown=60 * 5, max_retries=5)
3. add_task.py
from my_celery.my_task import priority_test_1, priority_test_2
# for i in range(1, 10):
# priority_test_1.delay({"name": f"{i}"})
# priority_test_2.s({"name": f"{i}"}).apply_async(priority=i)
priority_test_1.delay({"name": f"{123}"})
priority_test_2.delay({"name": f"{123}"})
5.任务调度
1. celery_setting.py
# coding:utf-8
from kombu import Queue
BROKER_URL = 'amqp://guest:guest@127.0.0.1:5672/test' # 使用RabbitMQ作为消息代理
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/0' # 把任务结果存在了Redis
# CELERY_TASK_SERIALIZER = 'msgpack' # 任务序列化和反序列化使用msgpack方案
CELERY_TASK_SERIALIZER = 'json' # 任务序列化和反序列化使用json方案
CELERY_RESULT_SERIALIZER = 'json' # 读取任务结果一般性能要求不高,所以使用了可读性更好的JSON
CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24 # 任务过期时间,不建议直接写86400,应该让这样的magic数字表述更明显
CELERY_ACCEPT_CONTENT = ['json', 'msgpack'] # 指定接受的内容类型
CELERY_QUEUES = ( # 设置add队列,绑定routing_key
Queue('queue_1', routing_key='queue_1', durable=False, max_priority=10),
Queue('queue_2', routing_key='queue_2', durable=False),
Queue('queue_3', routing_key='queue_3'),
)
CELERY_ROUTES = { # projq.tasks.add这个任务进去add队列并routeing_key为queue_1
'crontab_celery.s1.add': {
'queue': 'queue_1',
'routing_key': 'queue_1',
},
'crontab_celery.s1.tsum': {
'queue': 'queue_2',
'routing_key': 'queue_2',
},
'crontab_celery.s1.mul': {
'queue': 'queue_3',
'routing_key': 'queue_3',
}
}
from celery.schedules import crontab
from datetime import timedelta
# imports = [
# 'crontab_celery.s1.add',
# 'crontab_celery.s1.tsum'
# 'crontab_celery.s1.mul'
# ]
# schedules定时任务
CELERYBEAT_SCHEDULE = {
'crontab_celery.s1.add': {
'task': 'crontab_celery.s1.add',
'schedule': timedelta(seconds=1), # 每 3 秒执行一次
'args': (2, 2) # 任务函数参数
},
'crontab_celery.s1.tsum': {
'task': 'crontab_celery.s1.tsum',
# 'schedule': crontab(minute="*/1"), # 每分执行一次
'schedule': timedelta(seconds=2), # 每 3 秒执行一次
'args': ([1, 2, 3, 4, 5],) # 任务函数参数
},
'crontab_celery.s1.mul': {
'task': 'crontab_celery.s1.mul',
'schedule': crontab(minute="*/1"), # 每分执行一次
'args': (3, 3) # 任务函数参数
}
}
# celery -A crontab_celery.s1 beat # 定时任务启动
2.s1.py
# coding:utf-8
from __future__ import absolute_import
from celery import Celery
from crontab_celery import celery_setting
app = Celery('crontab_celery.s1')
app.config_from_object(celery_setting)
@app.task
def add(x, y):
return x + y
# celery worker -A crontab_celery.s1 -n add -Q queue_1 -c 1 -l info -P gevent
@app.task
def tsum(numbers):
return sum(numbers)
# celery worker -A crontab_celery.s1 -n tsum -Q queue_2 -c 1 -l info -P gevent
@app.task
def mul(x, y):
return x * y
# celery worker -A crontab_celery.s1 -n mul -Q queue_3 -c 1 -l info -P gevent
3.命令
# celery -A crontab_celery.s1 beat # 使用 Beat 进程自动生成任务 # celery worker -A crontab_celery.s1 -n add -Q queue_1 -c 1 -l info -P gevent # 指定任务 add 和队列queue_1 # celery worker -A crontab_celery.s1 -n tsum -Q queue_2 -c 1 -l info -P gevent # 指定 tsum 和 queue_2 # celery worker -A crontab_celery.s1 -n mul -Q queue_3 -c 1 -l info -P gevent # 指定 mul 和queue_3
6.任务绑定, 记录日志, 重试
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 可以获得任务的上下文.
7.信号系统 :
信号可以帮助我们了解任务执行情况, 分析任务运行的瓶颈. Celery 支持 7 种信号类型.
- 任务信号
- before_task_publish : 任务发布前
- after_task_publish : 任务发布后
- task_prerun : 任务执行前
- task_postrun : 任务执行后
- task_retry : 任务重试时
- task_success : 任务成功时
- task_failure : 任务失败时
- task_revoked : 任务被撤销或终止时
- 应用信号
- Worker 信号
- Beat 信号
- Eventlet 信号
- 日志信号
- 命令信号
不同的信号参数格式不同, 具体格式参见官方文档
代码示例 :
from celery.signals import after_task_publish, task_success
@after_task_publish.connect(sender="crontab_celery.s1.add", )
def task_sent_2(sender=None, headers=None, body=None, **kwargs):
# information about task are located in headers for task messages
# using the task protocol version 2.
info = headers if 'task' in headers else body
print(1111111111)
print('crontab_celery.s1.add for task id {info[id]}'.format(
info=info,
))
@after_task_publish.connect(sender="crontab_celery.s1.tsum", )
def task_sent_1(sender=None, headers=None, body=None, **kwargs):
# information about task are located in headers for task messages
# using the task protocol version 2.
info = headers if 'task' in headers else body
print(22222222222)
print('crontab_celery.s1.tsum for task id {info[id]}'.format(
info=info,
))
重新启动 app时候会发现加任务打印
8.子任务与工作流:
1.子任务
from crontab_celery.s1 import add
from celery import signature
# 1.通过签名的方法传给其他任务, 成为一个子任务
task = add.subtask((2, 2), countdown=10) # 快捷方式 add.s((2,2), countdown-10)
task.apply_async()
# 2.通过如下方式生成子任务
task = signature('crontab_celery.s1.mul', args=(2, 2), countdown=10) # 快捷方式 add.s((2,2), countdown-10)
task.apply_async()
# 3.实现偏函数的方式非常有用, 这种方式可以让任务在传递过程中财传入参数.
partial = add.s(2)
partial.apply_async((4,))
2.工作流
子任务支持如下 5 种原语,实现工作流. 原语表示由若干指令组成的, 用于完成一定功能的过程
from crontab_celery.s1 import add, tsum
from celery import chain
from celery import group
from celery import chord
# 1.chain : 调用连, 前面的执行结果, 作为参数传给后面的任务, 直到全部完成, 类似管道.
res = chain(add.s(2, 3), add.s(5), add.s(10))()
res.get()
print(res.get(), "result") # 20
print(res.parent.get(), "result.parent") # 10
print(res.parent.parent.get(), "result.parent.parent") # 5
# 2.管道式:
res = (add.s(2, 2) | add.s(4) | add.s(8))().get()
# 3.group 组任务
print(group(add.s(i, i) for i in range(10))().get())
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
# 4.chord 复合任务函数 任务全部完成时添加一个回调任务
res = chord((add.s(i, i) for i in range(10)), tsum.s())()
print(res.get()) # 90
# 5.map/starmap : 每个参数都作为任务的参数执行一遍, map 的参数只有一个, starmap 支持多个参数.
res = add.starmap(zip(range(10), range(10)))()
print(res)
# 相当于
# @app.task
# def temp():
# return [add(i, i) for i in range(10)]
# 5.chunks : 将任务分块 按10个分块去处理
add_chunks = add.chunks(zip(range(100), range(100)), 10)
result = add_chunks.delay()
print(result.get())
res = add.chunks(zip(range(100), range(100)), 10)()
print(res.get())
# 使用rabbitmq队列没成功!
9. 其他
1.关闭不想要的功能
@app.task(ignore_result=True) # 关闭任务执行结果.
def func():
pass
CELERY_DISABLE_RATE_LIMITS=True # 关闭限速
2.根据任务状态执行不同操作
# s1.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
10. 管理命令
任务状态回调 :
参数 | 说明 |
---|---|
PENDING | 任务等待中 |
STARTED | 任务已开始 |
SUCCESS | 任务执行成功 |
FAILURE | 任务执行失败 |
RETRY | 任务将被重试 |
REVOKED | 任务取消 |
PROGRESS | 任务进行中 |
1.普通启动命令
celery -A proj worker -l info
2.使用deamon方式 multi
$ celery multi start web -A proj -l info --pidfile=/path/to/celery_%n.pid --logfile=/path/to/celery_%n.log
# web 是对项目启动的标识,
# %n 是对节点的格式化用法.
%n : 只包含主机名
%h : 包含域名的主机
%d : 只包含域名
%i : Prefork 类型的进程索引,如果是主进程, 则为 0.
%I : 带分隔符的 Prefork 类型的进程索引. 假设主进程为 worker1, 那么进程池的第一个进程则为 worker1-1
3.常用 multi 相关命令:
$ celery multi show web # 查看 web 启动时的命令 $ celery multi names web # 获取 web 的节点名字 $ celery multi stop web # 停止 web 进程 $ celery multi restart web # 重启 web $ celery multi kill web # 杀掉 web 进程
4.常用监控和管理命令
# shell : 交互时环境, 内置了 Celery 应用实例和全部已注册的任务, 支持 默认解释器,IPython,BPython # celery shell -A proj # result : 通过 task_id 在命令行获得任务执行结果 # celery -A proj result TASK_ID # inspect active : 列出当前正在执行的任务 # celery -A proj inspect active # inspect stats : 列出 worker 的统计数据, 常用来查看配置是否正确以及系统的使用情况. # celery -A proj inspect stats
5.Flower web 监控工具
查看任务历史,任务具体参数,开始时间等信息;
提供图表和统计数据
实现全面的远程控制功能, 包括但不限于 撤销/终止任务, 关闭重启 worker, 查看正在运行任务
提供一个 HTTP API , 方便集成.
6.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
7.Celery 自带的事件监控工具显示任务历史等信息
celery -A proj event
** 需要把 CELERY_SEND_TASK_SEND_EVENT = True 设置, 才可以获取时间
8.使用自动扩展
celery -A proj worker -l info --autoscale=6,3 # 平时保持 3 个进程, 最大时可以达到 6 个.
9.celery命令汇总
celery --help Usage: celery <command> [options] 用法:celery 命令 选项 Show help screen and exit. 显示帮助信息并退出。 Options(选项): -A APP, --app=APP app instance to use (e.g. module.attr_name)-本次操作使用的App实例。 -b BROKER, --broker=BROKER -消息代理(服务器),用于传递数据的URL。 url to broker. default is 'amqp://guest@localhost//' --loader=LOADER name of custom loader class to use. - 自定义载入类的名称。 --config=CONFIG Name of the configuration module-配置模块的名称。 --workdir=WORKING_DIRECTORY- 工作目录。 Optional directory to change to after detaching. -C, --no-color -非彩色显示。 -q, --quiet -静默执行。 --version show program's version number and exit-显示版本号。 -h, --help show this help message and exit -显示本帮助。 ---- -- - - ---- Commands-命令列表 -------------- --- ------------ + Main-主要命令: | celery worker | celery events | celery beat | celery shell | celery multi | celery amqp + Remote Control-远程控制: | celery status | celery inspect --help | celery inspect active | celery inspect active_queues | celery inspect clock | celery inspect conf None | celery inspect memdump | celery inspect memsample | celery inspect objgraph None | celery inspect ping | celery inspect registered | celery inspect report | celery inspect reserved | celery inspect revoked | celery inspect scheduled | celery inspect stats | celery control --help | celery control add_consumer <queue> [exchange [type [routing_key]]] | celery control autoscale [max] [min] | celery control cancel_consumer <queue> | celery control disable_events | celery control enable_events | celery control pool_grow [N=1] | celery control pool_shrink [N=1] | celery control rate_limit <task_name> <rate_limit> (e.g. 5/s | 5/m | 5/h)> | celery control time_limit <task_name> <soft_secs> [hard_secs] + Utils-使用命令: | celery purge | celery list | celery migrate | celery call | celery result | celery report + Extensions: | celery flower ---- -- - - --------- -- - -------------- --- ------------ Type 'celery <command> --help' for help using a specific command. 键入'celery <command> --help'可以获得指定的命令的更详细的帮助信息。
11. 在falsk中使用
Flask 文档: 基于 Celery 的后台任务
在 Flask 中使用 Celery