一、Celery的定义
- Celery(芹菜)是一个简单、灵活且可靠的,处理大量消息的分布式系统,并且提供维护这样一个系统的必需工具。
- 我比较喜欢的一点是:Celery支持使用任务队列的方式在分布的机器、进程、线程上执行任务调度。然后我接着去理解什么是任务队列。
- 任务队列
任务队列是一种在线程或机器间分发任务的机制。
- 消息队列
消息队列的输入是工作的一个单元,称为任务,独立的职程(Worker)进程持续监视队列中是否有需要处理的新任务。
Celery 用消息通信,通常使用中间人(Broker)在客户端和职程间斡旋。这个过程从客户端向队列添加消息开始,之后中间人把消息派送给职程,职程对消息进行处理。如下图所示:
Celery 系统可包含多个职程和中间人,以此获得高可用性和横向扩展能力。
- Celery的架构
Celery的架构由三部分组成,消息中间件(message broker),任务执行单元(worker)和任务执行结果存储(task result store)组成。
消息中间件
Celery本身不提供消息服务,但是可以方便的和第三方提供的消息中间件集成,包括,RabbitMQ,Redis,MongoDB等,这里我先去了解RabbitMQ,Redis。
任务执行单元
Worker是Celery提供的任务执行的单元,worker并发的运行在分布式的系统节点中
任务结果存储
Task result store用来存储Worker执行的任务的结果,Celery支持以不同方式存储任务的结果,包括Redis,MongoDB,Django ORM,AMQP等,这里我先不去看它是如何存储的,就先选用Redis来存储任务执行结果。
二、简单操作
cereryconfig.py
BROKER_URL = "redis://:@127.0.0.1/1"
CELERY_RESULT_BACKEND = "redis://:@127.0.0.1:6379/2"
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24 # 任务过期时间
CELERY_ACCEPT_CONTENT = ["json"]
"""
CELERY_DEFAULT_QUEUE # 默认队列
CELERY_BROKER_URL # Broker 地址
CELERY_RESULT_BACKEND # 结果存储地址
CELERY_TASK_SERIALIZER # 任务序列化方式
CELERY_RESULT_SERIALIZER # 任务执行结果序列化方式
CELERY_TASK_RESULT_EXPIRES # 任务过期时间
CELERY_ACCEPT_CONTENT # 指定任务接受的内容类型(序列化)
"""
celerys.py
from celery import Celery
"""
1、Celery第一个参数是给其设定一个名字, 第二参数我们设定一个中间人broker, 在这里我们使用Redis作为中间人。
my_task函数是我们编写的一个任务函数, 通过加上装饰器app.task, 将其注册到broker的队列中
2、如果我们想跟踪任务的状态,Celery需要将结果保存到某个地方。
有几种保存的方案可选:SQLAlchemy、Django ORM、Memcached、 Redis、RPC (RabbitMQ/AMQP)。
配置backend参数
"""
app = Celery("demo")
# 从单独的配置模块中加载配置
app.config_from_object('celeryconfig')
# 创建任务函数
@app.task # 将其注册到broker的队列中。
def my_task(a, b):
print("任务在执行。。。。")
return a + b
tasks.py
from celerys import my_task
"""
任务加入到broker队列中,以便刚才我们创建的celery workder服务器能够从队列中取出任务并执行。
如何将任务函数加入到队列中,可使用delay()。
解决错误:ask handler raised error: ValueError('not enough values to unpack (expected 3, got 0)',)
Traceback (most recent call last):
pip install eventlet
celery -A <mymodule> worker -l info -P eventlet
"""
ret = my_task.delay(1, 2)
ret.failed() # 错误结果
print(ret.result) # 获取返回值
"""
返回值:
<AsyncResult: 2c0f6100-d499-44a0-aa0d-57ebec7fdd4c>:这个对象可以用来检查任务的状态或者获得任务的返回值。
"""
三、celery基本结构
cereryconfig.py
from celery.beat import crontab
BROKER_URL = "redis://:@127.0.0.1/1"
CELERY_RESULT_BACKEND = "redis://:@127.0.0.1:6379/2"
# Routing
CELERY_ROUTES = ({
'celery_proj.tasks.my_task1': {'queue': 'queue1'},
'celery_proj.tasks.my_task2': {'queue': 'queue1'},
'celery_proj.tasks.my_task3': {'queue': 'queue2'},
})
# 配置周期性任务,或者定时任务,5秒执行一次(celery beat)
BEAT_SCHEDULE = {
'every-5-seconds':
{
'task': 'celery_proj.tasks.my_task1',
'schedule': 5.0,
# 'args': (16, 16),
}
}
# 如果我们想指定在某天某时某分某秒执行某个任务,可以执行cron任务, 增加配置信息如下:
beat_schedule = {
'every-5-minute':
{
'task': 'celery_proj.tasks.period_task',
'schedule': 5.0,
'args': (16, 16), # 参数函数
},
'add-every-monday-morning': {
'task': 'celery_proj.tasks.period_task',
'schedule': crontab(hour=7, minute=30, day_of_week=1),
'args': (16, 16),
},
}
"""
启动队列:
celery -A celery_proj.celerys worker -l info -Q queue1 -P eventlet
celery -A proj worker --loglevel=info -Q queue1,queue2 直接开启两个队列
"""
celerys.py
from celery import Celery
# 创建celery实例
app = Celery('demo')
app.config_from_object('celery_proj.celeryconfig')
# 自动搜索任务
app.autodiscover_tasks(['celery_proj'])
tasks.py
from celery_proj.celerys import app as celery_app
# 创建任务函数
@celery_app.task
def my_task1():
print("任务函数(my_task1)正在执行....")
@celery_app.task
def my_task2():
print("任务函数(my_task2)正在执行....")
@celery_app.task
def my_task3():
print("任务函数(my_task3)正在执行....")
@celery_app.task
def my_task4(a, b):
print("任务函数(my_task4)正在执行....")
return a + b
@celery_app.task
def my_task5(a, b):
print("任务函数(my_task5)正在执行....")
return a + b
@celery_app.task
def my_task6(a, b):
print("任务函数(my_task6)正在执行....")
return a + b
@celery_app.task
def period_task(a, b):
print(a + b)
test.py
from celery_proj.tasks import my_task1, my_task2, my_task3, my_task4, my_task5, my_task6
from celery import group
from celery import chain
# my_task1.delay()
"""
调用任务:
可以使用apply_async()方法,该方法可让我们设置一些任务执行的参数,
例如,任务多久之后才执行,任务被发送到那个队列中等等.
my_task.apply_async((2, 2), queue='my_queue', countdown=10)
任务my_task将会被发送到my_queue队列中,并且在发送10秒之后执行。
如果我们直接执行任务函数,将会直接执行此函数在当前进程中,并不会向broker发送任何消息。
无论是delay()还是apply_async()方式都会返回AsyncResult对象,
方便跟踪任务执行状态,但需要我们配置result_backend.
每一个被调用的任务都会被分配一个ID,我们叫Task ID.
queue="queue1": 指定用哪个队列
"""
# my_task2.apply_async(queue="queue1", countdown=10)
"""
一个signature包装了一个参数和执行选项的单个任务调用。我们可将这个signature传递给函数。
我们将my_task1()任务包装称一个signature:10秒后执行
"""
# t3 = my_task3.signature(countdown=10)
# t3.delay()
"""
Primitives:
这些primitives本身就是signature对象,因此它们可以以多种方式组合成复杂的工作流程。primitives如下:
group: 一组任务并行执行,返回一组返回值,并可以按顺序检索返回值。
chain: 任务一个一个执行,一个执行完将执行return结果传递给下一个任务函数.
"""
# 将多个signature放入同一组中
# my_group = group((my_task4.s(11, 12), my_task5.s(1, 12), my_task6.s(11, 2)))
# ret = my_group() # 执行组任务
# print(ret.get()) # 输出每个任务结果
"""
将多个signature组成一个任务链
my_task4的运行结果将会传递给my_task5
my_task5的运行结果会传递给my_task6
"""
# my_chain = chain(my_task4.s(10, 10) | my_task5.s(10) | my_task6.s(10))
# ret = my_chain() # 执行任务链
# print(ret.get()) # 输出最终结果
"""
Routing(要写配置文件)
假如我们有两个worker,一个worker专门用来处理邮件发送任务和图像处理任务,一个worker专门用来处理文件上传任务。
我们创建两个队列,一个专门用于存储邮件任务队列和图像处理,一个用来存储文件上传任务队列。
Celery支持AMQP(Advanced Message Queue)所有的路由功能,我们也可以使用简单的路由设置将指定的任务发送到指定的队列中.
"""
# my_task1.apply_async(queue='queue1')
# my_task3.apply_async(queue='queue2')
"""
Periodic Tasks:(要写配置文件)
celery beat是一个调度器,它可以周期内指定某个worker来执行某个任务。
如果我们想周期执行某个任务需要增加beat_schedule配置信息.
不能在windows上运行
启动woker处理周期性任务:celery -A proj worker --loglevel=info --beat
celery需要保存上次任务运行的时间在数据文件中,文件在当前目录下名字叫celerybeat-schedule. beat需要访问此文件:
celery -A proj beat -s /home/celery/var/run/celerybeat-schedule
"""
三、celery在Django中的应用
tasks.py
from django_celery.celerys import app
import time
# 加上app对象的task装饰器
# 此函数为任务函数
@app.task
def my_task():
print("任务开始执行....")
time.sleep(5)
print("任务执行结束....")
# 用于定时执行的任务
@app.task
def interval_task():
print("我每隔5秒钟时间执行一次....")
views.py
from django.shortcuts import render
from django.http import HttpResponse
from .tasks import my_task
# Create your views here.
"""
django-admin startproject celery_demo 创建Django项目
python manage.py startapp demo 创建App
python -m pip install --upgrade pip
"""
def index(request):
# 将my_task任务加入到celery队列中
# 如果my_task函数有参数,可通过delay()传递
# 例如 my_task(a, b), my_task.delay(10, 20)
my_task.delay()
return HttpResponse("<h1>服务器返回响应内容!</h1>")
celery.py
from celery import Celery
from django.conf import settings
import os
# 为celery设置环境变量
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_celery.settings')
# 创建应用
app = Celery("app1")
# 配置应用
app.conf.update(
# 配置broker, 这里我们用redis作为broker
BROKER_URL='redis://:@127.0.0.1:6379/1',
# 使用项目数据库存储任务执行结果
CELERY_RESULT_BACKEND='django-db',
# 配置定时器模块,定时器信息存储在数据库中
CELERYBEAT_SCHEDULER='django_celery_beat.schedulers.DatabaseScheduler'
)
# 设置app自动加载任务
# 从已经安装的app中查找任务
app.autodiscover_tasks(settings.INSTALLED_APPS)
# @app.task(bind=True)
# def test(self):
# print('Request:{0!r}'.format(self.request))
settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app1.apps.App1Config',
'django_celery_results', # 注意此处应用名为下划线
'django_celery_beat', # 安装应用
]
urls.py
from django.conf.urls import url
from django.contrib import admin
from app1.views import index
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^index/', index),
]