05-01 celery
一. 官方
Celery 官网:http://www.celeryproject.org/
Celery 官方文档英文版:http://docs.celeryproject.org/en/latest/index.html
Celery 官方文档中文版:http://docs.jinkan.org/docs/celery/
Celery是一个简单、灵活且可靠的,处理大量消息的分布式系统
专注于实时处理的异步任务队列
同时也支持任务调度
注意:
Celery is a project with minimal funding, so we don’t support Microsoft Windows. Please don’t open any issues related to that platform.
Celery是一个资金很少的项目,所以我们不支持微软的Windows。请不要打开任何与该平台相关的问题。
二. Celery异步任务框架
1) 可以不依赖任何服务器,通过自身命令,启动服务(内部支持socket)
2) celery服务为为其他项目服务提供异步解决任务需求的
注:会有两个服务同时运行,一个是项目服务,一个是celery服务,项目服务将需要异步处理的任务交给celery服务,celery就会在需要时异步完成项目的需求
人是一个独立运行的服务 | 医院也是一个独立运行的服务
正常情况下,人可以完成所有健康情况的动作,不需要医院的参与;但当人生病时,就会被医院接收,解决人生病问题
人生病的处理方案交给医院来解决,所有人不生病时,医院独立运行,人生病时,医院就来解决人生病的需求
三. Celery架构
Celery的架构由三部分组成:
# 1. 消息中间件(message broker)
Celery本身不提供消息服务,但是可以方便的和第三方提供的消息中间件集成。包括,RabbitMQ, Redis等等
# 2. 任务执行单元(worker)和
Worker是Celery提供的任务执行的单元,worker并发的运行在分布式的系统节点中。
# 3. 任务执行结果存储(task result store)组成。
Task result store用来存储Worker执行的任务的结果,Celery支持以不同方式存储任务的结果,包括AMQP, redis等
三. 使用场景
异步执行:解决耗时任务,将耗时操作任务提交给Celery去异步执行,比如发送短信/邮件、消息推送、音视频处理等等
延迟执行:解决延迟任务
定时执行:解决周期(周期)任务,比如每天数据统计
四. Celery安装
pip install celery
五. 两种celery任务结构
1. 如果 Celery对象:Celery(...) 是放在一个模块下的
1)终端切换到该模块所在文件夹位置:scripts
2)执行启动worker的命令:celery worker -A 模块名 -l info -P eventlet
注意:
1. windows系统需要eventlet支持: pip insall eventlet
2. Linux与MacOS直接执行:celery worker -A 模块名 -l info
2. 如果 Celery对象:Celery(...) 是放在一个包下的
1)必须在这个包下建一个celery.py的文件,将Celery(...)产生对象的语句放在该文件中
2)执行启动worker的命令:celery worker -A 包名 -l info -P eventlet
注意:
1. windows系统需要eventlet支持: pip insall eventlet
2. Linux与MacOS直接执行:celery worker -A 模块名 -l info
六. Celery执行异步任务
1. 模块结构
1) celery_task/celery_app.py
from celery import Celery
broker = 'redis://127.0.0.1:6379/1'
backend = 'redis://127.0.0.1:6379/2'
# include=[被管理的任务文件路径, ]
app = Celery(__name__, broker=broker, backend=backend, include=('celery_task', ))
# 放在模块下的启动celery命令
'''
windows中:
celery worker -A 模块名 -l info -P eventlet
linux中:
clery worker -A 模块名 -l info
-A: 表示被执行的模块路径. 如果是相对, 那么就需要cd到该模块下执行.
-l: 表示展示日志
'''
windows中启动命令:
celery worker -A celery_app -l info -P eventlet
2) celery_task/celery_task.py
from celery_app import app
@app.task # 添加任务
def add(x, y):
print('x + y:', x + y)
return x + y
3) celery_task/excute_task.py
import utils
from celery_task import add
# 往broker中添加一个任务.
'''
只要是worker一直是在启动的状态, 一旦往broker中添加了任务.
那么这个任务就会立刻被worker执行, 执行的结果存储到backend中
'''
task_uuid = add.delay(1, 2)
# 注意: task_uuid是一个AsyncResult对象. 因此需要str才能保存到reids中
'''
redis.exceptions.DataError: Invalid input of type: 'AsyncResult'. Convert to a bytes, string, int or float first.
'''
# utils.conn.rpush('task_id', task_uuid) # 错误
utils.conn.rpush('task_id', str(task_uuid))
4) celery_task/get_result.py
from celery.result import AsyncResult
import utils
from celery_task import app
if __name__ == '__main__':
# 通过指定任务的id, 指定开启任务的worker实例化一个异步对象. 当一有结果就可以.get获取到对应任务的返回结果.
while True:
task_id = utils.conn.brpop('task_id')[1]
import time
time.sleep(0.5) # 这里就是没办法知道worker什么时候将当前执行任务的id执行成功.
print('task_id:', task_id, type(task_id)) # task_id: c72c2454-6232-477b-986b-3f896e752088 <class 'str'>
async_obj = AsyncResult(id=task_id, app=app)
if async_obj.successful():
result = async_obj.get()
print(result)
elif async_obj.failed():
print('任务失败')
elif async_obj.status == 'PENDING':
print('任务等待中被执行')
elif async_obj.status == 'RETRY':
print('任务异常后正在重试')
elif async_obj.status == 'STARTED':
print('任务已经开始被执行')
2. 包结构
1) scripts/celery_task
__init.py__
celery.py
from celery import Celery
broker = 'redis://127.0.0.1:6379/1'
backend = 'redis://127.0.0.1:6379/2'
app = Celery(__name__, broker=broker, backend=backend, include=['celery_task.task1', 'celery_task.task2'])
# 定时任务
from datetime import timedelta
from celery.schedules import crontab
app.conf.timezone = 'Asia/Shanghai' # 默认是UTC. 这里切换到了上海的时区.
app.conf.enable_utc = False # False表示禁用默认的UTC时间作为当前的定时时间, 而是以上面指定的上海的的时区作为定时开始时间
app.conf.beat_schedule = {
'task1': {
'task': 'celery_task.task1.task1', # 配置定时任务执行task1任务的路径
'schedule': timedelta(seconds=3), # 每3秒种执行一次
# 'schedule': crontab(hour=8, day_of_week=1), # 每周一早八点
'args': (300, 150), # 给task1进行传递的参数. 指定kwargs就可以传递关键字参数.
},
'task2': {
'task': 'celery_task.task2.task2',
'schedule': timedelta(seconds=3),
# 'schedule': crontab(hour=8, day_of_week=1),
'args': (300, 150),
}
}
# 关于使用crontab进行定时的参数
'''
minute='*', # 第几分钟
hour='*', # 第几小时
day_of_week='*', # 每周的第几天
day_of_month='*', # 每月的第几天
month_of_year='*' # 每年的第几月
'''
# 启动worker以后, 在另一个总终端中就可以使用一下命令自动将app.conf.beat_schedule中配置的定时任务函数 或 方法 自动执行
'''
celery beat -A celery_task -l info -P eventlet
'''
task1.py
from .celery import app
@app.task
def task1(x, y):
return x * y
task2.py
from .celery import app
@app.task
def task2(x, y):
return x + y
2) scripts/
execute_task.py
# from scripts.celery_task import task2
# from scripts.celery_task import task1
'''
celery_task作为包, 被命令执行时. 如果不将celery_task作为定级导入, 那么任务的执行将会是未被注册的. 任务将不会被运行的worker获取, 将会抛出异常.
[2020-07-26 19:40:56,983: ERROR/MainProcess] Received unregistered task of type 'scripts.celery_task.task2.task2'.
因此, 为了在项目中能够在任意位置都可以执行任务. 因此celery_task必定要放在项目的根目录下的. 那么无论在任何位置导入, 都是没有问题的. 如下:
from celery_task.a.b.c.d import xxx
如果不是在项目的根目录下, 你的导入也许是这样的.
from scripts.celery_task import task
那么worker将找不到你指定的任务的路径.
其实本质就是由于采用了django的反射机制,使用celery.py所在的celery_task包必须放置项目的根目录下.
'''
from celery_task import task2
from celery_task import task1
# 异步任务
'''
task1_id = task1.task1.delay(3, 7)
task2_id = task2.task2.delay(3, 7)
'''
# 延时任务: (注意: 默认是以utc时间作为当前时间开始往后计时开始定时)
from datetime import datetime, timedelta
eta = datetime.utcnow() + timedelta(seconds=5)
task1_id = task1.task1.apply_async(args=(3, 7), eta=eta)
task2_id = task2.task2.apply_async(args=(3, 7), eta=eta)
# 注意: task1_id返回的是一个AsyncResult类实例化的对象. 不是一个字符串
print(task1_id, type(task1_id)) # 181626b8-ef19-45ea-9f5c-f30381063521 <class 'celery.result.AsyncResult'>
print(task2_id, type(task2_id)) # 8dc9ce08-2ed6-4c1f-a5b6-37fa79ece2cb <class 'celery.result.AsyncResult'>
get_result.py
from celery.result import AsyncResult
# from scripts.celery_task.celery import app
from scripts.celery_task.celery import app
task_id = 'ad25839c-710b-4643-a0b0-7b090852367e'
if __name__ == '__main__':
async_obj = AsyncResult(id=task_id, app=app)
if async_obj.successful():
result = async_obj.get()
print(result)
elif async_obj.failed():
print('任务失败')
elif async_obj.status == 'PENDING':
print('任务等待中被执行')
elif async_obj.status == 'RETRY':
print('任务异常后正在重试')
elif async_obj.status == 'STARTED':
print('任务已经开始被执行')
3. 关于celery执行任务的坑
# from scripts.celery_task import task2
# from scripts.celery_task import task1
'''
celery_task作为包, 被命令执行时. 如果不将celery_task作为定级导入, 那么任务的执行将会是未被注册的. 任务将不会被运行的worker获取, 将会抛出异常.
[2020-07-26 19:40:56,983: ERROR/MainProcess] Received unregistered task of type 'scripts.celery_task.task2.task2'.
因此, 为了在项目中能够在任意位置都可以执行任务. 因此celery_task必定要放在项目的根目录下的. 那么无论在任何位置导入, 都是没有问题的. 如下:
from celery_task.a.b.c.d import xxx
如果不是在项目的根目录下, 你的导入也许是这样的.
from scripts.celery_task import task
那么worker将找不到你指定的任务的路径.
本质: 就是由于采用了django的反射机制,使用celery.py所在的celery_task包必须放置项目的根目录下.
'''