什么是任务队列
任务队列是一种跨线程、跨机器工作的一种机制。
任务队列中包含称作任务的工作单元。有专门的工作进程持续不断的监视任务队列,并从中获得新的任务并处理。
什么是 celery
celery 是一款基于 python 的异步任务处理框架。
celery 通过消息进行通信,通常使用一个叫 Broker(中间人) 来协client (任务的发出者) 和 worker (任务的处理者)。 clients 发出消息到队列中,broker 将队列中的信息派发给 worker 来处理。
一个 celery 系统可以包含很多的 worker 和 broker,可增强横向扩展性和高可用性能。
celery 的安装
(1) 使用 pip 安装:
pip install celery
在安装出现超时现象时,可切换国内源。
(2)从官方下载安装包: https://pypi.python.org/pypi/celery/
task queue
celery 作为框架,也需要一种解决消息的发送和接收的方式,我们把这种用来存储消息的中间装置叫做 message broker,也可以叫做消息中间人。
可选的 broken 方案
1.RabbitMQ
RabbitMQ 是一个功能完备,稳定的并且易于安装的 broker. 它是生产环境中最优的选择。使用RabbitMQ的细节参照以下链接: http://docs.celeryproject.org/en/latest/getting-started/brokers/rabbitmq.html#broker-rabbitmq
如果我们使用的是 Ubuntu 或者 Debian 发行版的 Linux,可以直接通过下面的命令安装 RabbitMQ: sudo apt-get install rabbitmq-server 安装完毕之后,RabbitMQ-server 服务器就已经在后台运行。如果您用的并不是Ubuntu或Debian, 可以在以下网址: http://www.rabbitmq.com/download.html 去查找自己所需要的版本软件。
2.Redis
Redis 也是一款功能完备的broker可选项,但是其更可能因意外中断或者电源故障导致数据丢失的情况。 关于是有那个 Redis 作为 Broker,可访下面网址: http://docs.celeryproject.org/en/latest/getting-started/brokers/redis.html#broker-redis
celery 的使用
使用 celery 第一件要做的最为重要的事情是需要先创建一个 Celery 实例,我们一般叫做 celery 应用,或者更简单直接叫做一个 app。app 应用是我们使用 celery 所有功能的入口,比如创建任务,管理任务等,在使用 celery 的时候,app 必须能够被其他的模块导入。
首先我们创建 tasks.py ,内容为:
from celery import Celery
# 我们这里案例使用redis作为broker
app = Celery('demo', broker='redis://127.0.0.1/8')
# 创建任务函数
@app.task
def my_task():
print("任务函数正在执行.")
Celery 第一个参数是给其设定一个名字, 第二参数我们设定一个中间人 broker , 在这里我们使用 Redis 作为中间人。my_task 函数是我们编写的一个任务函数, 通过加上装饰器 app.task, 将其注册到 broker 的队列中。
然后我们创建一个 worker:
cd 到celery 的同级目录中,执行命令:
celery -A tasks worker --loglevel=info
注意在执行该命令前,需要首先安装 redis。
接下来我们开始调用任务:
我们需要将任务加入到 broker 队列中,以便刚才我们创建的 celery worker 服务器能够从队列中取出任务并执行。如何将任务函数加入到队列中,可使用 delay()。
从 tasks.py 同级目录中进入 python 终端, 执行如下代码:
from tasks import my_task
my_task.delay()
我们通过 worker 的控制台,可以看到我们的任务被 worker处理。调用一个任务函数,将会返回一个 AsyncResult 对象,这个对象可以用来检查任务的状态或者获得任务的返回值。
存储任务的执行结果
如果我们想跟踪任务的状态,Celery 需要将结果保存到某个地方。有几种保存的方案可选: SQLAlchemy、Django ORM、Memcached、 Redis、RPC (RabbitMQ/AMQP)。
例子我们仍然使用 Redis 作为存储结果的方案,任务结果存储配置我们通过 Celery 的 backend 参数来设定。我们创建 tasks2 模块修改如下:
from celery import Celery
# 我们这里案例使用 redis 作为 broker
app = Celery('demo',
backend='redis://127.0.0.1:6379/8',
broker='redis://127.0.0.1:6379/9')
@app.task
def my_task(a, b):
print("任务函数正在执行.")
return a + b
开启终端,尝试将任务加入到 broker 队列中。
开启针对 tasks2 模块的 worker 服务器:
celery -A tasks2 worker --loglevel=info
再次查看任务的状态和运行结果:
同时可以看到, 我们给 Celery 增加了 backend 参数,指定redis 作为结果存储,并将任务函数修改为两个参数,并且有返回值。
更多关于 result 对象信息,请参阅下列网址: http://docs.celeryproject.org/en/latest/reference/celery.result.html#module-celery.result
配置 celery app 的不同方式
创建 tasks3 模块来展示三种配置 app 的方式:
from celery import Celery
# # (1) 在初始化 app 的时候进行配置
# app = Celery('demo',
# backend='redis://127.0.0.1:6379/8',
# broker='redis://127.0.0.1:6379/9')
# # (2) 通过 app 的 conf 属性进行配置
# app = Celery('demo')
# app.conf.update(
# result_backend='redis://127.0.0.1:6379/8',
# broker_url='redis://127.0.0.1:6379/9',
# )
# (3) 通过专用的配置文件来加载配置
app = Celery('demo')
'''
下面我们在tasks.py模块 同级目录下创建配置模块celeryconfig.py:
result_backend = 'redis://127.0.0.1:6379/8'
broker_url = 'redis://127.0.0.1:6379/9'
'''
app.config_from_object('celeryconfig')
@app.task
def my_task(a, b):
print("任务函数正在执行.")
return a + b
更多配置: http://docs.celeryproject.org/en/latest/userguide/configuration.html#configuration
在项目中使用 celery 的结构配置
创建一个项目 mypro, 结构如下:
其中创建 app 的文件被命名为 celery.py:
# celery.py
from celery import Celery
app = Celery('mypro')
app.config_from_object('mypro.celeryconfig')
# 自动搜索任务
app.autodiscover_tasks(['mypro'])
app 的配置文件为 celeryconfig.py:
# celeryconfig.py
BROKER_URL = 'redis://127.0.0.1:6379/8'
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/9'
任务函数写在 tasks.py 中:
# tasks.py
from mypro.celery import app as celery_app
# 创建任务函数
@celery_app.task
def my_task1():
print("任务函数(my_task1)正在执行.")
return "task1 done"
@celery_app.task
def my_task2():
print("任务函数(my_task2)正在执行.")
return "task2 done"
@celery_app.task
def my_task3():
print("任务函数(my_task3)正在执行.")
return 'task3 done'
cd 到 mypro 同级目录, 启动 worker 服务器:
celery -A mypro worker -l info
celery 任务的调用方式
(1) 如果我们直接执行任务函数,将会直接执行此函数在当前进程中,并不会向broker发送任何消息:
(2) 调用任务,可使用 delay() 方法: my_task.delay(2, 2)
也可以使用 apply_async() 方法,该方法可让我们设置一些任务执行的参数,例如,任务多久之后才执行,任务被发送到那个队列中等等.
my_task.apply_async((2, 2), queue='my_queue', countdown=10)
任务 my_task 将会被发送到 my_queue 队列中,并且在发送10秒之后执行。
无论是 delay() 还是 apply_async() 方式都会返回 AsyncResult 对象,方便跟踪任务执行状态,但需要我们配置r esult_backend.
每一个被调用的任务都会被分配一个 ID,我们叫 Task ID.
签名 signature
有时我们并不想简单的将任务发送到队列中,我们想将一个任务函数(由参数和执行选项组成) 作为一个参数传递给另外一个函数中,为了实现此目标,Celery 使用一种叫做 signature 的东西。
一个 signature 包装了一个参数和执行选项的单个任务调用。我们可将这个 signature 传递给函数。
首先我们新增一个任务函数,是我们自定义的加法:
测试直接调用和封装为 signature ,汇入参数再调用的效果:
运行结果:
Primitives
primitives 本身可认为是 signature 对象,因此它们可以以多种方式组合成复杂的工作流程。primitives 如下:
- (1) group: 一组任务并行执行,返回一组返回值,并可以按顺序检索返回值。
- (2) chain: 任务一个一个执行,一个执行完将执行return结果传递给下一个任务函数:
运行结果:
Routing
假如我们有两个 worker ,一个 worker 专门用来处理邮件发送任务和图像处理任务,一个 worker 专门用来处理文件上传任务。
我们创建两个队列,一个专门用于存储邮件任务队列和图像处理,一个用来存储文件上传任务队列。
Celery 支持 AMQP(Advanced Message Queue) 所有的路由功能,我们也可以使用简单的路由设置将指定的任务发送到指定的队列中。
我们需要配置在 celeryconfig.py 模块中配置 CELERY_ROUTES 项, tasks.py 模块修改如下:
我们可以开启两个任务服务器,分别处理自己对应注册的任务:
celery -A mypro worker --loglevel=info -Q queue1
celery -A mypro worker --loglevel=info -Q queue2
我们也可以在一个任务服务器中开启两个队列:
celery -A mypro worker --loglevel=info -Q queue1,queue2
最后,我们还可以在注册任务时主动指定自己所想要使用的队列。
ret = my_add.apply_async(args=(1, 2), queue='queue1')
测试时我们开启三个任务服务器:
运行测试任务, 观察任务进入的队列。
执行周期任务调度
celery beat 是一个调度器,它可以周期内指定某个 worker 来执行某个任务。如果我们想周期执行某个任务需要增加 beat_schedule 配置信息.
在 app 所在的文件中增加配置信息:
app.conf.beat_schedule = {
'every-5-seconds':
{
'task': 'mypro.tasks.my_add',
'schedule': 5.0,
'args': (16, 16),
}
}
配置完成之后,在终端启动定时的 worker 服务,
同时使用 --beat 启动周期任务注册服务:
celery -A mypro worker --loglevel=info -Q queue1 --beat
运行效果:
如果我们想指定在某天某时某分某秒执行某个任务,可以执行cron任务, 增加配置信息如下:
app.conf.beat_schedule = {
# 'every-5-seconds':
# {
# 'task': 'mypro.tasks.my_add',
# 'schedule': 5.0,
# 'args': (16, 16),
# },
# 每周三下午的 2 点 32 分运行一次
'add-every-wednesday-afternoon': {
'task': 'mypro.tasks.my_add',
'schedule': crontab(hour=14, minute=32, day_of_week=3),
'args': (16, 16),
},
}
注意在指定某个时间点运行任务时,需要为 celery 配置所在的时区。
app.conf.timezone = 'Asia/Shanghai'
更多crontab例子:
http://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html
将 worker 服务与 beat 服务分开
单独开启一个celery beat服务:(注意使用 sudo 权限)
sudo celery -A mypro -l info beat
celery 需要保存上次任务运行的时间在数据文件中,文件在当前目录下名字叫celerybeat-schedule. beat, 需要访问此文件:
sudo celery -A mypro -l info beat -s /Users/furuiyang/gitzip/DjangoDemos/celery_learn/celerybeat-schedule
在 django 项目中使用 celery
首先,我们来创建 django app:
pipinstall django
django-admin startproject celery_demo
python manage.py startapp demo
接着,我们在 celery_demo 模块中创建 celery.py 模块.
内容:
# celery.py
from celery import Celery
from django.conf import settings
import os
# 为celery设置环境变量
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'celery_demo.settings')
# 创建应用
app = Celery("demo")
# 配置应用
app.conf.update(
# 配置broker, 这里我们用redis作为broker
BROKER_URL='redis://127.0.0.1:6379/8',
)
# 设置app自动加载任务
# 从已经安装的app中查找任务
app.autodiscover_tasks(settings.INSTALLED_APPS)
接着,我们来创建一个任务:
在 demo app 中创建 tasks.py 文件:
# tasks.py
from celery_demo.celery import app
import time
# 加上app对象的task装饰器
# 此函数为任务函数
@app.task
def my_task():
print("任务开始执行....")
time.sleep(5)
print("任务执行结束....")
然后我们来创建启动任务的类前端视图,以及为这个视图配置相应的路由:
# demo.views.py
from django.http import HttpResponse
from .tasks import my_task
def index(request):
my_task.delay()
return HttpResponse("<h1>服务器返回响应内容!</h1>")
# url.py
from django.conf.urls import url
from django.contrib import admin
from demo.views import index
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^$', index),
]
同时注意在 settings.py 中注册 demo app:
# settings.py
...
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'demo',
]
...
在 django 根目录下启动 celery 的 worker 服务:
celery -A celery_demo worker -l info
启动 django 服务并且请求 http://127.0.0.1:8000/
观察任务的执行:
在 django 中保存任务执行结果
首先我们需要安装一个新的模块:
pipinstall django-celery-results
接着我们在项目的 settings 文件中安装此应用:
接着我们在 celery.py 文件中增加配置信息:
# celery.py
from celery import Celery
from django.conf import settings
import os
# 为celery设置环境变量
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'celery_demo.settings')
# 创建应用
app = Celery("celery_demo")
# 配置应用
app.conf.update(
# 配置broker, 这里我们用redis作为broker
BROKER_URL='redis://127.0.0.1:6379/8',
# 使用项目数据库存储任务执行结果
CELERY_RESULT_BACKEND='django-db',
)
# 设置app自动加载任务
# 从已经安装的app中查找任务
app.autodiscover_tasks(settings.INSTALLED_APPS)
然后我们修改下自己的任务函数,使其有返回值:
最后执行数据库迁移文件:
python manage.py migrate django_celery_results
当我们再次注册任务、启动 worker 服务时,就可以在 sqlite 数据库中看到我们任务的记录了:
在 django 中执行定时任务
首先我们需要安装 celery beat 针对 django 的扩展包:
pip install django_celery_beat
接着,我们需要在 settings 中安装此应用 并且增加对应的配置:
由于定时器信息存储在数据库中,我们需要先生成对应表, 对 diango_celery_beat 执行迁移操作,创建对应表:
python manage.py migrate django_celery_beat
进入后台,创建定时器 以及 针对这个定时器需要启动的任务函数:
其中 Crontabs 用于定时某个具体时间执行某个任务的时间,Intervals 用于每隔多久执行任务的事件,具体任务的执行在 Periodic tasks 表中创建。
我们要创建每隔 5 秒执行某个任务,所以在 Intervals 表名后面点击 Add 按钮:
然后在 Periodic tasks 表名后面,点击 Add 按钮,添加任务:
最后启动定时任务的 worker 服务和 beat 服务:
celery -A celery_demo worker -l info --beat
任务每隔5秒中就会执行一次,如果配置了存储,那么每次任务执行的结果也会被保存到对应的数据库中。
代码
https://github.com/furuiyang0715/celery_learn
参考
- https://www.celerycn.io/yong-hu-zhi-nan/canvas-she-ji-gong-zuo-liu-cheng-canvas-designing-workflows/qian-ming-signatures
- https://www.youtube.com/watch?v=BbPswIqn2VI