浅谈异步任务队列Celery
Celery简介
Celery与任务队列
Celery是一个简单、灵活且可靠的,能够处理大量消息的分布式系统。它专注于实时处理异步任务队列,同时也支持任务调度。
Celery的架构组成
Celery的架构由三部分组成,分别是消息中间件(message broker)、任务执行单元(worker)和任务执行结果存储(task result store)。
-
消息中间件
Celery本身不提供消息服务,但是可以方便的和第三方提供的消息中间件集成。包括,RabbitMQ,Redis等等。
-
任务执行单元
Worker是Celery提供的任务执行单元,worker并发的运行在分布式的系统节点中。
-
任务结果存储
Task result store用来存储worker执行的任务的结果,Celery支持以下不同方式存储任务的结果,包括AMQP,redis等。
另外,Celery还支持不同的并发和序列化的手段。
任务队列
任务队列一般用于线程或计算机之间分配工作。
任务队列的输入是一个称为任务的工作单元,有专门的职程(Worker)进行不断的监视任务队列,进行执行新的任务工作。
Celery通过消息机制进行通信,通常使用中间人(Broker)作为客户端和职程(Worker)调节。启动一个任务,客户端向消息队列发送一条消息,然后Broker将消息传递给一个Worker,最后由Worker进行执行Broker分配的任务。
Celery可以有多个Worker和Broker,用来提高Celery的高可用性以及横向扩展能力。
Celery是用Python编写的,但协议可以用任何语言实现。除了Python语言实现外,还有Node.js的node-celery和php的celery-php。
Celery的使用场景
-
Web应用
当用户触发一个需要长时间执行的操作时,可以把它作为任务交给Celery去异步执行,执行完再返回给用户。这段时间用户不需要等待,提高网站的整体吞吐量和响应时间。
-
定时任务
生产环境经常会跑一些定时任务。假如你有大量服务器,每台服务器是设置各种定时任务,管理起来比较困难,这时就可以使用Celery来统一管理和设置不同的定时任务。
-
异步任务
将耗时的操作任务提交给Celery去异步执行,如发送给短信/邮件、消息推送、音视频处理等。
Celery的优点
-
简单
Celery使用和维护都十分简单,并且不需要配置文件。
-
高可用
worker和client会在网络连接丢失或者失败时,自动进行重试。并且有的brokers也支持“双主”或者“主/从”的方式实现高可用。
-
快速
单个的Celery进程每分钟可以处理百万级的任务,并且只需要毫秒级的往返延迟(使用RabbitMQ,librabbitmq和优化设置时)
-
灵活
Celery几乎每个部分都可以扩展使用,自定义池实现,序列化、压缩方案、日志记录、调度器、消费者、生产者、broker传输等等。
选择中间人(Broker)
Celery需要一个中间件来进行接收和发送消息,通常以独立的服务形式出现,成为消息中间人(Broker)。
中间人可以使用RabbitMQ、Redis等,官方推荐使用RabbitMQ。本文为了方便演示,选用Redis作为Broker。
开始前你需要安装redis数据库和python的redis包。
pip3 install redis
安装
pip3 install -U Celery
或
python3 -m pip install Celery
Celery的基本使用
简单使用
创建一个Celery实例app,它可能包含Celery中执行操作的所有入口,如创建任务、管理worker等。
-
创建异步任务执行脚本tasks.py
import time from celery import Celery # 无密码:redis://host:port/db # 有密码:redis://password@host:port/db broker = 'redis://:123456@127.0.0.1:6379/0' backend = 'redis://:123456@127.0.0.1:6379/1' app = Celery('test', broker=broker, backend=backend) @app.task def send_email(name): print(f'sending email to {name}...') time.sleep(5) print('finished') return 'success'
Celery的参数说明:
- 第一个参数为当前模块的名称,只有在
__main__
模块中定义任务时才会生产名称 - 第二个参数为中间人(broker)的链接url,消息队列,用来发送和接受消息
- 第三个参数为backend的链接url,用于保存任务结果和状态
创建一个名称为send_email的任务,用于模拟向某人发送邮件。
- 第一个参数为当前模块的名称,只有在
-
创建执行任务文件run.py
from tasks import send_email result1 = send_email.delay('Tony') # 通过delay()方法来执行任务 print(result1.id) result2 = send_email.delay('Jany') print(result2.id)
-
在控制台启动celery执行以下命令
celery worker -A tasks -l info
然后运行run.py执行任务,在控制台就可以看到以下日志记录
[2020-12-10 19:32:16,875: INFO/MainProcess] Received task: tasks.send_email[02f372cf-2a3a-48e3-ad1f-16f74b42153d]
[2020-12-10 19:32:16,876: WARNING/MainProcess] sending email to Tony...
[2020-12-10 19:32:16,877: INFO/MainProcess] Received task: tasks.send_email[a8e4ad04-465a-43d7-8896-fb0c5c0974f5]
[2020-12-10 19:32:16,878: WARNING/MainProcess] sending email to Jany...
注意:如果是在win10下使用celery4.x的话,执行run.py后会报错
解决方法:安装eventlet库
pip3 install eventlet
再次启动celery执行任务:
celery worker -A tasks -l info -P eventlet
运行run.py就可以正常获取结果了
在redis数据库忠可以看到对应id的结果信息,如id为a8e4ad04-465a-43d7-8896-fb0c5c0974f5的信息如下:
{
"status": "SUCCESS",
"result": "success",
"traceback": null,
"children": [],
"date_done": "2020-12-10T11:32:21.864885",
"task_id": "a8e4ad04-465a-43d7-8896-fb0c5c0974f5"
}
其中result为任务的返回值
多任务结构
新建项目celery_project,目录结构如下:
celery_project/
celery_tasks/
__init__.py
celery.py
task1.py
task2.py
get_result.py
run.py
celery.py
from celery import Celery
CELERY_CONFIG = {
'main': 'celery',
'broker': 'redis://:123456@127.0.0.1:6379/0',
'backend': 'redis://:123456@127.0.0.1:6379/1',
'include': ['celery_tasks.task1', 'celery_tasks.task2'] # 包含的任务文件列表
}
app = Celery(**CELERY_CONFIG)
app.conf.timezone = 'Asia/Shanghai' # 时区
app.conf.enable_utc = False # 是否使用UTC(国际协调时间)
task1.py(模拟发送邮件)
import time
from celery_tasks.celery import app
@app.task
def send_email(name):
print(f'sending email to {name}...')
time.sleep(5)
return 'email sent successfully'
task2.py(模拟发送短信)
import time
from celery_tasks.celery import app
@app.task
def send_msg(name):
print(f'sending message to {name}...')
time.sleep(5)
return 'message sent successfully'
在控制台启动Celery
celery worker -A celery_tasks -l info -P eventlet
run.py(异步执行任务)
from celery_tasks.task1 import send_email
from celery_tasks.task2 import send_msg
result1 = send_email.delay('Lisa')
result2 = send_msg.delay('Ben')
print(result1.id)
print(result2.id)
运行run.py,结果如下
c14aee7c-27d4-4785-aeb3-0b34e6f3c87a
bad62286-700c-47f1-ada2-61526c0afde0
get_result.py(获取任务结果)
from celery.result import AsyncResult
from celery_tasks.celery import app
async_result = AsyncResult(id='c14aee7c-27d4-4785-aeb3-0b34e6f3c87a', app=app)
if async_result.successful():
result = async_result.get()
print(result)
elif async_result.failed():
print('failed')
AsyncResult中的id值为上面执行任务获取的任务id,根据任务id就可以查看任务的完成状态和完成结果。
执行结果如下:
email sent successfully
定时任务
-
在上一个项目celery_project下新建一个延时任务执行文件cron_task.p
from datetime import datetime, timezone, timedelta from celery_tasks.task1 import send_email # 默认使用utc时间 utc_ctime = datetime.now(tz=timezone.utc) # 当前的utc时间 delay_time = timedelta(seconds=10) # 设置10秒延迟 task_time = utc_ctime + delay_time # 使用apply_async设置定时任务 result = send_email.apply_async(args=['egon'], eta=task_time) print(result.id)
控制台启动celery后,运行cron_task.py,10s后就可以看见控制台打印相关的任务执行信息。
-
上面只是简单演示了延时10s后执行任务的流程,如果需要每过10s执行一次send_email任务,则需要对celery.py的app.conf.beat_schedule进行配置,修改后如下:
from datetime import timedelta from celery import Celery CELERY_CONFIG = { 'main': 'celery', 'broker': 'redis://:123456@127.0.0.1:6379/0', 'backend': 'redis://:123456@127.0.0.1:6379/1', 'include': ['celery_tasks.task1', 'celery_tasks.task2'] # 包含的任务文件列表 } app = Celery(**CELERY_CONFIG) app.conf.timezone = 'Asia/Shanghai' # 时区 app.conf.enable_utc = False # 是否使用UTC(国际协调时间) app.conf.beat_schedule = { 'task-per-10s': { 'task': 'celery_tasks.task1.send_email', # 任务函数 'schedule': timedelta(seconds=10), # 10s执行一次 # 'schedule': crontab(minute="*/1"), # 也可以使用crontab的方式 'args': ('Mike',) # 传参 } }
-
在项目的根目录下开启两个cmd窗口
第一个窗口启动celery,用于接收并执行任务
celery worker -A celery_tasks -l info -P eventlet
第二个窗口用于发送定时任务
celery beat -A celery_tasks -l info
启动后可以看到第二个窗口每10s中发送一个任务
[2020-12-11 16:58:59,748: INFO/MainProcess] beat: Starting... [2020-12-11 16:59:00,020: INFO/MainProcess] Scheduler: Sending due task task-per-10s (celery_tasks.task1.send_email) [2020-12-11 16:59:10,007: INFO/MainProcess] Scheduler: Sending due task task-per-10s (celery_tasks.task1.send_email) [2020-12-11 16:59:20,008: INFO/MainProcess] Scheduler: Sending due task task-per-10s (celery_tasks.task1.send_email)
第一个窗口接收任务并执行
[2020-12-11 16:59:00,291: INFO/MainProcess] Received task: celery_tasks.task1.sen d_email[31cbccb8-4fc4-4f44-8ee0-0e0a196cb8d7] [2020-12-11 16:59:00,292: WARNING/MainProcess] sending email to Mike... [2020-12-11 16:59:05,303: INFO/MainProcess] Task celery_tasks.task1.send_email[31 cbccb8-4fc4-4f44-8ee0-0e0a196cb8d7] succeeded in 5.01600000000326s: 'email sent s uccessfully' [2020-12-11 16:59:10,011: INFO/MainProcess] Received task: celery_tasks.task1.sen d_email[06e03d73-611b-4750-83bb-9722e5d78660] [2020-12-11 16:59:10,012: WARNING/MainProcess] sending email to Mike... [2020-12-11 16:59:15,016: INFO/MainProcess] Task celery_tasks.task1.send_email[06 e03d73-611b-4750-83bb-9722e5d78660] succeeded in 5.0s: 'email sent successfully'
以上就是关于异步任务队列框架Celery的简单介绍和使用,如果想了解更多关于Celery的功能和应用,可以去Celery官方文档查看。