文章目录
- Celery概述
- Celery架构
- celery 组件
- 使用场景
- Celery使用
- 基本使用
- 多任务结构
- 使用配置
- Celery执行定时任务
- 设定时间让celery执行一个任务
- 类似于contab的定时任务
- Django中使用
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 组件
- Celery Beat : 任务调度器. Beat 进程会读取配置文件的内容, 周期性的将配置中到期需要执行的任务发送给任务队列.
- Celery Worker : 执行任务的消费者, 通常会在多台服务器运行多个消费者, 提高运行效率.
- Broker : 消息代理, 队列本身. 也称为消息中间件. 接受任务生产者发送过来的任务消息, 存进队列再按序分发给任务消费方(通常是消息队列或者数据库).
- Producer : 任务生产者. 调用 Celery API , 函数或者装饰器, 而产生任务并交给任务队列处理的都是任务生产者.
- Result Backend : 任务处理完成之后保存状态信息和结果, 以供查询.
使用场景
异步任务:将耗时操作任务提交给Celery去异步执行,比如发送短信/邮件、消息推送、音视频处理、清理/设置缓存等等
定时任务:定时执行某件事情,比如每天数据统计
Celery使用
首先要安装框架:
pip3 install celery
接下来的操作中使用的消息中间件和结果存储都是用的是Redis
,所以确保已经安装好Redis
。
基本使用
首先创建一个py
文件: celery_task .py
from celery import Celery
# redis没有密码时
# 指定broker(消息中间件)
broker = 'redis://127.0.0.1:6379/0'
# backend(结果存储)
backend = 'redis://127.0.0.1:6379/1'
# redis有密码时
- broker='redis://:密码@127.0.0.1:6379/0'
- backend='redis://:密码@127.0.0.1:6379/1'
# 实例化产生一个Celery对象,指定名字test
app = Celery('test', broker=broker, backend=backend)
# 任务其实就是函数
# 需要用一个装饰器装饰,表示该任务是被celery管理的,且可用celery执行
@app.task
def add(x, y):
import time
time.sleep(10)
return x+y
接下来创建py
文件:add_task.py
,用来进行调用任务
# 用于提交任务的py文件
import celery_task
# 提交任务到消息队列中,只是把任务提交到消息队列中,并没有执行
# 通过delay来将任务添加到worker中
ret=celery_task_s1.add.delay(3,4) # 返回结果是任务的id号:a5ea035f-0cc3-44ba-b334-f5d7c7ce681d
print(ret) # 对于返回的结果需要进行保存,用来取出任务的结果
当我们运行上面的py文件是就会把任务添加到队列中。
接下来启动worker去执行任务,有两种方式:
1、创建py文件:run.py,执行任务
from celery_task import app
if __name__ == '__main__':
app.worker_main()
# cel.worker_main(argv=['--loglevel=info')
2、在项目文件夹中执行启动命令(celery_task是任务模块的名字)
linux:celery worker -A celery_app_task -l info
各个参数含义:
- worker: 代表第启动的角色是work当然还有beat等其他角色;
- -A :项目路径,这里我的目录是project
- -l:启动的日志级别,更多参数使用celery --help查看
因为在celery不支持windows,在windows中执行命令时要增加-P eventlet
,要安装eventlet模块。
windows:celery worker -A celery_app_task -l info -P eventlet
最后创建py文件:result.py,查看任务执行结果
from celery.result import AsyncResult
from celery_app_task import cel
async = AsyncResult(id="a5ea035f-0cc3-44ba-b334-f5d7c7ce681d", app=cel)
if async.successful():
result = async.get()
print(result)
# result.forget() # 将结果删除
elif async.failed():
print('执行失败')
elif async.status == 'PENDING':
print('任务等待中被执行')
elif async.status == 'RETRY':
print('任务异常后正在重试')
elif async.status == 'STARTED':
print('任务已经开始被执行')
AsyncResult除了get方法用于常用获取结果方法外还提以下常用方法或属性:
- state: 返回任务状态;
- task_id: 返回任务id;
- result: 返回任务结果,同get()方法;
- ready(): 判断任务是否以及有结果,有结果为True,否则False;
- info(): 获取任务信息,默认为结果;
- wait(t): 等待t秒后获取结果,若任务执行完毕,则不等待直接获取结果,若任务在执行中,则wait期间一直阻塞,直到超时报错;
- successfu(): 判断任务是否成功,成功为True,否则为False;
前面使用 delay()
来调用任务, 也可以使用apply_async()
方法来调用任务。事实上,delay
方法封装了 apply_async
。delay
是使用 apply_async
的快捷方式。apply_async
支持更多的参数,apply_async 常用的参数如下:
- countdown:指定多少秒后执行任务
task1.apply_async(args=(2, 3), countdown=5) # 5 秒后执行任务
- eta (estimated time of arrival):指定任务被调度的具体时间,参数类型是 datetime
from datetime import datetime, timedelta
# 当前 UTC 时间再加 10 秒后执行任务
task1.multiply.apply_async(args=[3, 7], eta=datetime.utcnow() + timedelta(seconds=10))
- expires:任务过期时间,参数类型可以是 int,也可以是 datetime
task1.multiply.apply_async(args=[3, 7], expires=10) # 10 秒后过期
更多的参数列表可以在官方文档中查看。
多任务结构
pro_cel
├── celery_task# celery相关文件夹
│ ├── celery.py # celery连接和配置相关文件,必须叫这个名字
│ └── tasks1.py # 所有任务函数
│ └── tasks2.py # 所有任务函数
├── check_result.py # 检查结果
└── send_task.py # 触发任务
from celery import Celery
cel = Celery('celery_demo',
broker='redis://127.0.0.1:6379/0',
backend='redis://127.0.0.1:6379/1',
# 包含以下两个任务文件,去相应的py文件中找任务,对多个任务做分类
include=['celery_task.tasks1',
'celery_task.tasks2'
])
# 时区
cel.conf.timezone = 'Asia/Shanghai'
# 是否使用UTC
cel.conf.enable_utc = False
import time
from celery_task.celery import cel
@cel.task
def test_celery1(res):
time.sleep(5)
return "test_celery任务结果:%s"%res
import time
from celery_task.celery import cel
@cel.task
def test_celery2(res):
time.sleep(5)
return "test_celery2任务结果:%s"%res
send_task.py
from celery_task.tasks1 import test_celery1
from celery_task.tasks2 import test_celery2
# 立即告知celery去执行test_celery任务,并传入一个参数
result = test_celery1.delay('第一个的执行')
print(result.id)
result = test_celery2.delay('第二个的执行')
print(result.id)
check_result.py
from celery.result import AsyncResult
from celery_task.celery import cel
async = AsyncResult(id="08eb2778-24e1-44e4-a54b-56990b3519ef", app=cel)
if async.successful():
result = async.get()
print(result)
# result.forget() # 将结果删除,执行完成,结果不会自动删除
# async.revoke(terminate=True) # 无论现在是什么时候,都要终止
# async.revoke(terminate=False) # 如果任务还没有开始执行呢,那么就可以终止。
elif async.failed():
print('执行失败')
elif async.status == 'PENDING':
print('任务等待中被执行')
elif async.status == 'RETRY':
print('任务异常后正在重试')
elif async.status == 'STARTED':
print('任务已经开始被执行')
执行顺序:
- 添加任务(执行send_task.py),
- 开启work:celery worker -A celery_task -l info -P eventlet,
- 检查任务执行结果(执行check_result.py)
使用配置
在上面的例子中,我们直接把 Broker 和 Backend 的配置写在了程序当中,更好的做法是将配置项统一写入到一个配置文件中,通常我们将该文件命名为 celeryconfig.py
。Celery 的配置比较多,可以在官方文档查询每个配置项的含义。
下面,我们再看一个例子。项目结构如下:
celery_demo # 项目根目录
├── celery_app # 存放 celery 相关文件
│ ├── __init__.py
│ ├── celeryconfig.py # 配置文件
│ ├── task1.py # 任务文件 1
│ └── task2.py # 任务文件 2
└── client.py # 应用程序
__init__.py
代码如下:
# -*- coding: utf-8 -*-
from celery import Celery
app = Celery('demo') # 创建 Celery 实例
app.config_from_object('celery_app.celeryconfig') # 通过 Celery 实例加载配置模块
celeryconfig.py
代码如下:
BROKER_URL = 'redis://127.0.0.1:6379' # 指定 Broker
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/0' # 指定 Backend
CELERY_TIMEZONE='Asia/Shanghai' # 指定时区,默认是 UTC
# CELERY_TIMEZONE='UTC'
CELERY_IMPORTS = ( # 指定导入的任务模块
'celery_app.task1',
'celery_app.task2'
)
其他模块代码一致。
Celery执行定时任务
设定时间让celery执行一个任务
add_task.py
from celery_task import add
from datetime import datetime,timedelta
```
# 第一种获取时间
v1 = datetime(2019, 2, 13, 18, 19, 56) # 固定设置一个时间执行任务
print(v1)
# v1.timestamp()将上面的时间转换为时间戳
v2 = datetime.utcfromtimestamp(v1.timestamp())
print(v2)
# 取出要执行任务的时间对象,调用apply_async方法,args是参数,eta是执行的时间
result = add.apply_async(args=[1, 3], eta=v2)
print(result.id)
```
# 第二种获取时间
ctime = datetime.now()
# 默认用utc时间
utc_ctime = datetime.utcfromtimestamp(ctime.timestamp())
time_delay = timedelta(seconds=10) # 可以设置延时几天,几分,几秒后执行
task_time = utc_ctime + time_delay
# 使用apply_async并设定时间
result = add.apply_async(args=[4, 3], eta=task_time)
print(result.id)
类似于contab的定时任务
下面的场景是每天的什么时候,或者每月的某个时候定时做某些工作。比如:秒杀推送信息,每天某个点同步redis的数据到数据库等。
多任务结构中celery.py修改如下
from datetime import timedelta
from celery import Celery
from celery.schedules import crontab
cel = Celery('tasks',
broker='redis://127.0.0.1:6379/1',
backend='redis://127.0.0.1:6379/2',
include=['celery_task.tasks1',
'celery_task.tasks2',
])
cel.conf.timezone = 'Asia/Shanghai'
cel.conf.enable_utc = False
cel.conf.beat_schedule = {
# 名字随意命名
'add-every-10-seconds': {
# 执行tasks1下的test_celery函数
'task': 'celery_task.tasks1.test_celery',
# 每隔2秒执行一次
# 'schedule': 1.0,
# 'schedule': crontab(minute="*/1"),
'schedule': timedelta(seconds=2),
# 传递参数
'args': ('test',)
},
# 'add-every-12-seconds': {
# 'task': 'celery_task.tasks1.test_celery',
# 每年4月11号,8点42分执行
# 'schedule': crontab(minute=42, hour=8, day_of_month=11, month_of_year=4),
# 'schedule': crontab(minute=42, hour=8, day_of_month=11, month_of_year=4),
# 'args': (16, 16)
# },
}
启动beat用来发送任务:celery beat -A celery_task -l info
启动work用来执行任务:celery worker -A celery_task -l info -P eventlet
还有更多定时配置方式如下:
Example | Meaning |
| 每分钟执行 |
| 每天0点执行 |
| 每3小时执行: midnight, 3am, 6am, 9am, noon, 3pm, 6pm, 9pm. |
| 同上 |
| 每15分钟执行 |
| 周天的每分钟执行 |
| 同上 |
| 周三、五,3-4 am, 5-6 pm, and 10-11 pm,每10分钟执行 |
| 每小时/2和每小时/3,执行 |
| 每小时/5,执行 |
| 每小时/3,8am-5pm,执行 |
| 每个月第二天执行。 |
| 每个偶数日执行。 |
| 在本月的第一周和第三周执行。 |
| 每年五月十一日执行。 |
| 每个季度的第一个月执行。 |
Django中使用
django中虽然提供了django-celery,但是对于django和celery的版本的匹配度要求很高,所以我们还是使用原生的celery。
首先我们把之前的celery_task文件夹可以直接拷贝到我们项目的目录下使用,最主要还是那个celery.py配置,其他的任务根据项目需求来就行了。
from django.shortcuts import HttpResponse
def index(request):
from celery_task.tasks1 import test_celery
# 提交任务,返回id
res = test_celery.delay('Django中执行')
return HttpResponse(res.id)
def check_result(request):
res = request.GET.get('id')
async = AsyncResult(id=res, app=app)
from celery_task.user_task import user_add
from celery.result import AsyncResult
from celery_task.celery import cel
if async.successful():
result = async.get()
return HttpResponse(result)
在Django中执行和前面一样要启动worker。
注意:在实际的项目中,可能要在任务函数中去操作数据库,但是上面只是将对应的文件夹直接拷贝到里Django的项目中,Django项目是无法识别的,所以要在对应的文件中添加下面的语句。
# 单独脚本调用Django内容时,需配置脚本的环境变量
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "untitled15.settings")
import django
django.setup()
上面的语句可以让Django中的py文件当作脚本进行执行,并且可以使用Django的环境。