python初识celery
简单介绍
官网简介
Celery 是一个简单、灵活且可靠的分布式系统,用于 处理大量消息,同时为操作提供 维护此类系统所需的工具。
这是一个专注于实时处理的任务队列,同时也 支持任务调度。
官网地址:https://docs.celeryq.dev/en/stable/
celery基本组成部分
- 生产者(application):要做什么事,什么时候做由他决定
- 队列或管道(broker):存放任务的容器。举个例子,想象一下我们在一家餐厅吃饭,前台写好单子挂在一根滑索上滑到后厨去,厨师从滑索上把单子取下来按单子做菜。那么这个滑索就是任务管道,滑索上的每一个单子就是一个任务
- 消费者(worker):处理任务的人。例子中的厨师,可以不止一个
- 处理结果存储(result store):任务的返回值,可以没有,如果有的话其存放的位置。例子中厨师做好菜把菜放到指定的餐台上以便其他人出菜,餐台对应的就是这个角色。
大致的流程也就跟上述的例子一样,有一个或多个人有生成了任务,然后任务统一存放在一个容器中,又有专门的人从容器中拿出来处理掉,再把处理后的结果放到另一个指定的容器中,谁要结果就直接到多对应的容器中取就是了。所以站在生产者的角度上来看,他只需要提出任务,然后等结果就可以了,而且等结果的过程中还可以去做其他事。
写一个简单的celery项目执行异步任务
根据上述的组成部分,我们写一个celery项目要做的事情很简单。
- 首先肯定是确定存放任务和结果的两个容器
- 实现定义好我们的消费者能够处理哪些任务,总不能让一个只会川菜的厨子做九转大肠吧
- 生产者生产任务
- 从结果容器中取结果
不管是异步任务还是定时任务都是这个逻辑,万变不离其宗。实际写代码的顺序跟上面的逻辑没有太大的关系,知道要做这些事就可以了
准备工作
python加载三方库celery
pip install celery
启动数据库服务,我这里使用的是redis作为任务管道和存放结果的容器。开发环境是windows
redis-server.exe redis.windows.config
创建一个celery实例,定义任务函数
从代码的角度来看,一个celery项目实际上就是一个Celery实例加配置,再利用这个自定义任务函数,最后调用。这里先创建一个celery_app.py文件,用来存放celery的实例和配置等
# celery_app.py
from celery import Celery
import time
# 创建实例
app = Celery(
"mycelery", # 实例名称,随便取
broker="redis://127.0.0.1:6379/1", # 任务管道
backend="redis://127.0.0.1:6379/2", # 结果容器
)
# 定义任务函数,使用对象名.task作为装饰器
@app.task
def send_mail(name):
print(f"向{name}发送邮件")
time.sleep(5)
print("发送完毕")
return "ok"
执行Celery实例所在的文件
在上一个步骤的celery_app.py文件中,我们其实就已经完成了celery项目的绝大多数工作,剩下的就是执行这个文件把celery服务启动起来,之后无非就是调用任务、获取结果
开发终端在celey_app.py所在的目录下执行启动celery服务命令:
celery -A celey_app worker -l info -P eventlet
- 命令-A表示应用,后面跟的是celery实例及配置文件或目录的路径;
- 命令worker表示让消费者监听任务
- 命令-l (小写L)表示日志等级,值通常是info
- 命令-P表示pool,通常有默认值不用管,但是在windows环境下启动需要指定为eventlet,eventlet这个python三方库也需要额外加载(pip install eventlet)
执行后显示ready就算成功启动了
在这个信息上面我们还可以看到,worker所监听的全部任务函数,也就是本文开头例子中提到的餐馆厨子能做的菜
这个启动celery服务的命令可以启动多个,表示有多个worker同时监听任务。换成例子上的意思就是这家餐厅有多个厨子等着做菜
生产任务
还是用例子的话来讲,走到上面启动celery服务的时候就表示这家餐厅已经开起来了,现在就等顾客来点餐吃东西了。点餐吃东西其实就是调用任务函数,调用任务函数主要是用到delay方法,另创建生成任务的文件或者直接在python解释器上执行代码都可以,这里用创建文件再执行的方式:
创建文件produce_tasks.py
# produce_task.py
# 导入任务函数
from celery_app import send_mail
# 通过任务函数名.delay()创建任务,delay方法的参数即任务函数的参数
reuslt1 = send_mail.delay("张三")
result2 = send_mail.delay("李四")
print(result1.id)
print(result2.id)
运行produce_task.py文件后可以发现,当我们有多个任务需要执行时,celery并不是等待前一个任务完全执行完毕之后才开始执行下一个任务,这就是celery在异步执行任务而非同步。
另外,通过delay调用任务函数后,并不是直接返回任务函数的执行结果,而是一个AsyncResult对象,任务函数的返回值(如果有)是写到结果容器中的。毕竟我们不是每次执行任务都能百分百成功,这中间可能有各种各样的原因导致任务执行失败。我们可以通过AsyncResult对象任务执行的结果或任务执行的状态。
查看任务执行情况
通过produce_task.py文件中AsyncResult对象的id值,我们可以获取任务函数的返回值或执行状态
创建文件result.py并运行
# result.py
from celery.result import AsyncResult
from celery_app import app
async_result = AsyncResult(
id="3e26d7e3-f896-441b-82ca-7f687f75366a", # produce_task.py中获取的AsyncResult对象id值
app=app # celery实例对象
)
if async_result.successfult():
# 如果执行成功则获取任务函数返回值
result = async_result.get()
print(result)
elif async_result.failed():
print("执行失败")
else:
# 打印任务执行的状态(执行完毕、等待执行等)
print(async_result.status)
到这里,一个简单的celery异步任务项目就做好了。想要利用celery做一个定时任务需要就需要用另外的方法和命令调用
celery项目在指定时间执行任务
执行这类延时的任务与异步任务唯一的区别就是在调用任务函数传参的时候需要再加一个时间的参数,所以这里需要将delay方法换成apply_async方法
# produce_task.py
...
from datetime import datetime
# 定义执行的时间,并将该时间转换为utc时间
dt = datetime(2023,11,23,12,0,0)
dt = datetime.utcfromtimestamp(dt.timestamp())
# 生成任务(调用任务函数)
result3 = send_mail.apply_async(
args=("王麻子",), # 任务函数的参数
eta=dt # 执行时间
)
print(result3.id)
大一点的celery项目
但凡对celery的配置稍微复杂一点的,celery应用都不会是单个文件,都是将前文中的celery_app.py文件换成一个包来。结构如下:
celery_app_pack
| __init__.py
| celery.py
| tasks01.py
| tasks02.py
…
在这个包中,celery.py文件用来定义主要的配置信息,任务函数单独写到其他文件中(tasks01.py
),可以有多个。代码如下
# celery.py
from celery import Celery
# 任务管道和结果容器
app_backend = "redis://127.0.0.1:6379/1"
app_broker = "redis://127.0.0.1:6379/2"
# 创建实例
app = Celery(
"myceleryApp",
broker=app_broker,
backend=app_backend,
include=[ # 所有任务函数所在的路径
"celery_app_pack.tasks01",
"celery_app_pack.tasks02",
]
)
# 时区
app.conf.timezone = "Asia/Shanghai"
# 是否使用utc
app.conf.enable_utc = False
# 启动Celery时是否尝试重新连接到消息代理(broker)
app.conf.broker_connection_retry_on_startup = True
# tasks01.py,其他任务函数所在文件同理文件名随意
from celery_app_pack import app
import time
# 定义任务函数
@app.task
def send_mail(name):
print(f"向{name}发送邮件")
time.sleep(2)
print("邮件发送成功")
return "ok"
其他的跟单个celery应用文件都是一样的,启动服务的命令也是一样,在celery_app_pack这个包所在的目录执行启动命令
celery -A celery_app_pack worker -l info -P eventlet
利用celery重复执行某些任务
比如我要每隔10秒就向张三发一次邮件,如果还是用produce_task.py的方式手动去调用任务函数就不太合适了,celery中有一个beat_schedule的调度器会帮我们自动创建任务添加到管道中。只需要在celery实例的配置中添加一个beat_schedule属性即可
# celery_app_pack/celery.py
...
# 通过配置项的定时调度器beat_schedule添加定时任务
app.conf.beat_schedule = {
# 每个键值对就是一个定时任务,键就是定时任务的名称可以随便取
"add_every_10_seconds":{
# 需要执行的任务函数
"task":"celery_app_pack.task01.send_mail",
# 时间间隔
"schedule":timedelta(seconds=6),
# 任务函数所需参数
"args":("张三",)
}
}
添加定时任务之后,在celery worker服务已经启动的情况下,还需要再启动一个celery beat服务,它将代替produce_task文件循环的创建那些需要重复执行的任务:
celery -A celery_app_pack beat
:timedelta(seconds=6),
# 任务函数所需参数
"args":("张三",)
}
}
添加定时任务之后,在celery worker服务已经启动的情况下,还需要再启动一个celery beat服务,它将代替produce_task文件循环的创建那些需要重复执行的任务:
celery -A celery_app_pack beat