一、概念
在一个应用服务中, 对于时效性要求没那么高的业务场景,我们没必要等到所有任务执行完才返回结果, 例如用户注册场景中, 保存了用户账号密码之后. 就可以立即返回, 后续的账号激活邮件, 可以用一个种异步的形式去处理, 这种异步操作可以⽤队列服务来实现. 否则, 如果等到邮件发送成功可能⼏秒过去了.
Celery是Python语言实现的分布式队列服务, 除了支持持即时任务, 还支持定时任务, Celery有5个核心角色.
1.Task
任务(Task)就是你要做的事情, 例如一个注册流程里面有很多任务, 给用户发验证邮件就是一个任务, 这种耗时任务可以交给Celery去处理; 还有一种任务是定时任务, 比如每天定时统计网站的注册人数, 这个也可以交给Celery周期性的处理.
2.Broker
Broker的中文意思是经纪人, 指为市场上买卖双方提供中介服务的人. 在是Celery中它介于生产者和消费者之间经纪人, 这个角色相当于数据结构中的队列. 例如一个Web系统中, 生产者是处理核心业务的Web程序, 业务中可能会产生一些耗时的任务; 比如短信 生产者会将任务发送给Broker, 就是把这个任务暂时放到队列中, 等待消费者来处理. 消费者是Worker, 是专门用于执行任务的后台服务. Worker将实时监控队列中是否有新的任务, 如果有就拿出来进行处理. Celery本身不提供队列服务, 一般用Redis或者RabbitMQ来扮演Broker的角色.
3.Worker
Worker 就是那个一直在后台执行任务的人, 也称为任务的消费者, 它会实时地监控队列中有没有任务, 如果有就立即取出来执行.
4.Beat
Beat是一个定时任务调度器, 它会根据配置定时将任务发送给Broker, 等待Worker来消费.
5.Backend
Backend用于保存任务的执行结果, 每个任务都有返回值, 比如发送邮件的服务会告诉我们有没有发送成功, 这个结果就是存在Backend中.
二、环境搭建
Python3.6.8
通过pip 安装 celery、redis
pip install celery==3.1.26.post2
pip install redis==2.10.6
安装redis服务 redis-3.2.1.tar.gz参考:https://www.cnblogs.com/linux985/p/11344273.html
三、测试小栗子
为了测试Celery能否工作,我运行了一个最简单的任务,编写tasks.py,如下图所示
importtimefrom celery importCelery, platforms
broker= 'redis://127.0.0.1:6379'backend= 'redis://127.0.0.1:6379'platforms.C_FORCE_ROOT= True #允许在root下运行
app = Celery(__file__, broker=broker, backend=backend)
@app.taskdefadd(x, y):
time.sleep(5)return x + y
上面的代码做了几件事:
创建了一个Celery实例app
指定消息中间件用redis, URL为redis://127.0.0.1:6379
指定存储用 redis, URL为redis://127.0.0.1:6379
创建了一个Celery任务add, 当函数被@app.task装饰后, 就成为可被 Celery 调度的任务。
启动Celery任务
celery worker -A tasks --loglevel=info
参数 -A 指定了Celery实例的位置, 本例是在 tasks.py 中,Celery会自动在该文件中寻找Celery对象实例
参数 --loglevel 指定了日志级别, 默认为 warning, 也可以使用 -l info 来表示。
然后看到界面显示结果如下:
我们可以看到Celery正常工作在名称centos版本为3.1.10 ,在下面的[config]中我们可以看到当前APP的名称tasks,运输工具transport就是我们在程序中设置的中间人redis://127.0.0.1:6379//,result redis://127.0.0.1:6379//,然后我们也可以看到worker缺省使用perfork来执行并发,当前并发数显示为4,然后可以看到下面的[queues]就是我们说的队列,当前默认的队列是celery,然后我们看到下面的[tasks]中有一个任务tasks.add.
了解了这些之后,根据文档我重新打开一个terminal,然后执行Python,进入Python交互界面,用delay()方法调用任务,执行如下操作:
t.ready() ##判断任务是否执行完毕
t.get() ##获取任务执行结果
可以看到, 虽然任务函数add需要等待5秒才返回执⾏结果, 但由于它是⼀个异步任务, 不会阻塞当前的主程序.
这个任务已经由之前启动的Worker异步执行了,然后我打开之前启动的worker的控制台,对输出进行查看验证,结果如下:
10:59:09这一行说明worker收到了一个任务:tasks.add,这里我们和之前发送任务返回的AsyncResult对比我们发现,每个task都有一个唯一的ID,第二行说明了这个任务执行succeed,执行结果为3。
查看资料说调用任务后会返回一个AsyncResult实例,可用于检查任务的状态,等待任务完成或获取返回值(如果任务失败,则为异常和回溯)。但这个功能默认是不开启的,需要设置一个 Celery 的结果后端(backend),这块我在下一个例子中进行了学习。
通过这个例子后我对Celery有了初步的了解,然后我在这个例子的基础上去进一步的学习。
1.2 配置文件
在上面的例子中, 我们直接把Broker和 Backend的配置写在了程序当中, 更好的做法是将配置项统一写入到一个配置文件中.
1.2.1目录结构:
celery_demo # 项⽬根⽬录
├── celery_app # 存放 celery 相关⽂件
│ ├── __init__.py
│ ├── config.py # 配置⽂件
│ ├── task1.py # 任务⽂件 1
│ └── task2.py # 任务⽂件 2
└── client.py # 应⽤程序
1.2.2 config.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_IMPORTS = ( # 指定导⼊的任务模块
'celery_app.task1',
'celery_app.task2'
)
1.2.3 __init__.py文件内容
# -*- coding: utf-8 -*-
from celery import Celery,platforms
platforms.C_FORCE_ROOT = True
app = Celery('demo') # 创建 Celery 实例
app.config_from_object('celery_app.config') # 通过 Celery 实例加载配置模块
1.2.4 task1.py
import time
from celery_app import app
@app.task
def add(x, y):
time.sleep(2)
return x + y
1.2.5 task2.py
from celery_app import app
import time
@app.task
def say():
time.sleep(2)
return 'helo'
1.2.6 client.py
from celery_app import task1
from celery_app import task2
task1.add.delay(1, 2)
task2.say.delay()
1.2.7 启动 celery worker
(websocket) [root@gitlab celery_demo]# celery -A celery_app worker -l info
1.2.8 执行client.py
python client.py
1.2.9 运行python client.py后它会发送两个异步任务到Broker, 在Worker的窗口我们可以看到如下输出:
[2018-10-19 06:52:31,389: INFO/MainProcess] Received task: celery_app.task1.add[b32962fe-dd61-443f-bc87-e666db957f24]
[2018-10-19 06:52:31,391: INFO/MainProcess] Received task: celery_app.task2.say[d945e419-aa93-4a2c-aec4-105f81031a64]
[2018-10-19 06:52:33,394: INFO/ForkPoolWorker-2] Task celery_app.task2.say[d945e419-aa93-4a2c-aec4-105f81031a64] succeeded in 2.00137619101s: 'helo'
[2018-10-19 06:52:33,394: INFO/ForkPoolWorker-1] Task celery_app.task1.add[b32962fe-dd61-443f-bc87-e666db957f24] succeeded in 2.00418746s: 3
1.3 定时任务
Celery 除了可以执行异步任务, 也支持执行周期性任务(Periodic Tasks),或者说定时任务, Celery Beat 进程通过读取配置文件的内容, 周期性地将定时任务发往任务队列.
1.3.1 修改配置文件, 增加定时任务
from celery.schedules import crontab
from datetime import timedelta
BROKER_URL = 'redis://127.0.0.1:6379'
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379'
CELERY_TIMEZONE='Asia/Shanghai'
CELERY_IMPORTS = (
'celery_app.task1',
'celery_app.task2'
)
CELERYBEAT_SCHEDULE = {
"task1": {
"task": "celery_app.task1.add",
"schedule": timedelta(seconds=1),
"args":(1, 2),
},
"task2": {
"task": "celery_app.task2.say",
"schedule": timedelta(seconds=2),
"args":(),
},
}
1.3.2 启动celery
celery -B -A celery_app worker --loglevel=info
1.3.3 在worker窗口查看任务输出
[2018-10-19 07:40:31,966: INFO/ForkPoolWorker-2] Task celery_app.task1.add[6b216f23-036a-48ed-b14f-e252bf3f1ffb] succeeded in 2.001080302s: 3
[2018-10-19 07:40:31,968: INFO/ForkPoolWorker-3] Task celery_app.task2.say[28246df4-0eff-49f0-b468-f92b85fe97b3] succeeded in 2.00303293001s: 'helo'
##定时任务遇到的问题就是不能对托管的定时任务做动态更新, 需要重启 celery beat ..
#进阶用法
1.1Celery异步函数回调
经过快速入门的学习后,我们已经能够使用 Celery 管理普通任务,但对于实际使用场景来说这是远远不够的,所以我们需要更深入的去了解 Celery 更多的使用方式。
首先来看之前的task:importtimefrom celery importCelery, platforms
platforms.C_FORCE_ROOT=Truefrom celery.app.task importTask
broker= 'redis://127.0.0.1:6379'backend= 'redis://127.0.0.1:6379'app= Celery(__file__, broker=broker, backend=backend)
@app.taskdefadd(x, y):
time.sleep(2)return x /y
这里的装饰器app.task实际上是将一个正常的函数修饰成了一个celery task对象, 所以这里我们可以给修饰器加上参数来决定修饰后的task对象的一些属性, 我们也可以自己复写task类然后让这个自定义task修饰函数
add, 来做一些自定义操作, 比如celery修饰的函数执行成功 失败 执行完毕时的行为.importtimefrom celery importCelery, platforms
platforms.C_FORCE_ROOT=Truefrom celery.app.task importTask
broker= 'redis://127.0.0.1:6379'backend= 'redis://127.0.0.1:6379'app= Celery(__file__, broker=broker, backend=backend)classMytask(Task):defon_success(self, retval, task_id, args, kwargs):print 'task success 11111'
returnsuper(Mytask, self).on_success(retval, task_id, args, kwargs)defon_failure(self, exc, task_id, args, kwargs, einfo):print 'task failed'
returnsuper(Mytask, self).on_failure(exc, task_id, args, kwargs, einfo)defafter_return(self, status, retval, task_id, args, kwargs, einfo):print 'this is after return'
returnsuper(Mytask, self).after_return(status, retval, task_id, args, kwargs, einfo)defon_retry(self, exc, task_id, args, kwargs, einfo):print 'this is retry'
returnsuper(Mytask,self).on_retry(exc, task_id, args, kwargs, einfo)
@app.task(base=Mytask)defadd(x, y):
time.sleep(2)return x /y
celery-A tasks worker --loglevel=info ##启动任务
In [2]: t = add.delay(1, 2) ##执⾏celery函数
在worker中会有类似如下输出:
[2018-10-19 15:05:18,986: WARNING/Worker-1] task success 11111[2018-10-19 15:05:18,987: WARNING/Worker-1] this is after return[2018-10-19 15:05:18,988: INFO/MainProcess] Task task.add[c41343cf-e5ca-49f7-9478-63f4c2e2797f] succeeded in 2.01661233298s: 0
In [3]: t = add.delay(1, 0) ##执⾏celery函数, 此函数会由于函数代码中分分母0而报错.
在worker中会有类似如下输出:
[2018-10-19 15:13:38,260: WARNING/Worker-2] task failed
[2018-10-19 15:13:38,261: WARNING/Worker-2] this is after return[2018-10-19 15:13:38,262: ERROR/MainProcess] Task task.add[12a852cd-726e-44a0-97a8-eb17308c354e] raised unexpected: ZeroDivisionError('integer division or modulo by zero',)
Traceback (most recent call last):
File"/app_shell/websocket/lib/python2.7/site-packages/celery/app/trace.py", line 240, intrace_task
R= retval = fun(*args, **kwargs)
File"/app_shell/websocket/lib/python2.7/site-packages/celery/app/trace.py", line 438, in __protected_call__
return self.run(*args, **kwargs)
File"/app_shell/websocket/demo/task.py", line 38, inaddreturn x /y
ZeroDivisionError: integer divisionormodulo by zero1.2将修饰函数成为Task类的绑定方法, 执行中的任务获取到了自己执行任务的各种信息, 可以根据这些信息做很多其他操作.importtimefrom celery importCelery, platforms
platforms.C_FORCE_ROOT=Truefrom celery.app.task importTask
broker= 'redis://127.0.0.1:6379'backend= 'redis://127.0.0.1:6379'app= Celery(__file__, broker=broker, backend=backend)
@app.task(base=Mytask, bind=True)defadd(self, x, y):printself.request
time.sleep(2)return x /y
celery-A tasks worker --loglevel=info ##启动任务
In [2]: t = add.delay(1, 2)
在worker中会有类似如下输出:2019/7/14 2.Celery进阶使用127.0.0.1:8888/notebooks/Celery/2.Celery进阶使用.ipynb 2/2In [ ]:
[2018-10-19 15:05:16,972: WARNING/Worker-1]
63f4c2e2797f','errbacks': None,'taskset': None,'id':'c41343cf-e5ca-49f7-9478-63f4c2e2797f', u'headers': {},'called_directly': False,'utc': True,'task':'task.add', u'group': None, 'callbacks': None, u'delivery_info': {u'priority': 0, u'redelivered': None, u'routing_key': u'celery', u'exchange': u'celery'},
u'hostname': 'celery@gitlab.example.com', 'expires': None, 'timelimit': (None, None), 'eta': None, 'kwargs': {}, u'reply_to': u'46af07e6-8e03-3574-9608-
fc8c0b10a98e','_protected': 1}>
[2018-10-19 15:05:18,986: WARNING/Worker-1] task success 11111[2018-10-19 15:05:18,987: WARNING/Worker-1] this is after return
进阶用法
四、django-celery实现异步HTTP请求
1、安装django-celery
1.1 安装django-celery之前需先安装Celery, 安装的包的版本不是越新越好, 建议安装Celery的版本为4.0.0以下, 否则可能存在兼容性问题. 如果pip官方源不能安装, 可以使用aliyun的pip源
pip install celery==4.0.0 -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
pip install django-celery==3.2.2 -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
pip install redis==2.10.6 -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
2、在django项目配置文件settings.py中配置celery.
2.1 加入以下参数:
import djcelery #引入django-celery包
djcelery.setup_loader()#当项目运行时, celery会去查看installd_apps下包含的所有app目录中的task.py⽂件, 并找到标记为task的方法, 将他们注册成为Celery task.
from celery import Celery, platforms
BROKER_URL = 'redis://127.0.0.1:6379'#指定消息中间件所在位置
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379'#指定任务执行结果的存储位置
platforms.C_FORCE_ROOT = True #使celery可以在root下启动
CELERY = Celery(__file__, broker=BROKER_URL, backend=CELERY_RESULT_BACKEND)
2.2 修改INSTALLED_APP在其中加入djcelery
INSTALLED_APPS = [
...
...
'djcelery',
]
3、在APP目录下创建task.py
import os
import sys
from django.conf import settings
import time
app = settings.CELERY
@app.task
def f1():
time.sleep(10)
print("heelo!")
return "hello return!"
4、 views.py中添加要异步执行的任务代码.
from __future__ import unicode_literals
from index.task import f1
def celery_index(request):
r = f1.delay()
print(r)
return HttpResponse("this is index page!")
5、启动项目
(proj_b) [root@nfs webadmins]# python manage.py runserver 0.0.0.0:8901 #启动Django项⽬
(proj_b) [root@nfs webadmins]# python manage.py celery worker -c 4 --loglevel=info #启动Celery
6、验证
6.1 请求URL http://172.16.70.233:8901/celery_index/
6.2 验证celery异步任务执行
在学习Celery之前,我先简单的去了解了一下什么是生产者消费者模式。
生产者消费者模式
在实际的软件开发过程中,经常会碰到如下场景:某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程、进程等)。产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。
单单抽象出生产者和消费者,还够不上是生产者消费者模式。该模式还需要有一个缓冲区处于生产者和消费者之间,作为一个中介。生产者把数据放入缓冲区,而消费者从缓冲区取出数据,如下图所示:
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过消息队列(缓冲区)来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给消息队列,消费者不找生产者要数据,而是直接从消息队列里取,消息队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个消息队列就是用来给生产者和消费者解耦的。------------->这里又有一个问题,什么叫做解耦?
解耦:假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(也就是耦合)。将来如果消费者的代码发生变化,可能会影响到生产者。而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低了。生产者直接调用消费者的某个方法,还有另一个弊端。由于函数调用是同步的(或者叫阻塞的),在消费者的方法没有返回之前,生产者只好一直等在那边。万一消费者处理数据很慢,生产者就会白白糟蹋大好时光。缓冲区还有另一个好处。如果制造数据的速度时快时慢,缓冲区的好处就体现出来了。当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。等生产者的制造速度慢下来,消费者再慢慢处理掉。
因为太抽象,看过网上的说明之后,通过我的理解,我举了个例子:吃包子。
假如你非常喜欢吃包子(吃起来根本停不下来),今天,你妈妈(生产者)在蒸包子,厨房有张桌子(缓冲区),你妈妈将蒸熟的包子盛在盘子(消息)里,然后放到桌子上,你正在看巴西奥运会,看到蒸熟的包子放在厨房桌子上的盘子里,你就把盘子取走,一边吃包子一边看奥运。在这个过程中,你和你妈妈使用同一个桌子放置盘子和取走盘子,这里桌子就是一个共享对象。生产者添加食物,消费者取走食物。桌子的好处是,你妈妈不用直接把盘子给你,只是负责把包子装在盘子里放到桌子上,如果桌子满了,就不再放了,等待。而且生产者还有其他事情要做,消费者吃包子比较慢,生产者不能一直等消费者吃完包子把盘子放回去再去生产,因为吃包子的人有很多,如果这期间你好朋友来了,和你一起吃包子,生产者不用关注是哪个消费者去桌子上拿盘子,而消费者只去关注桌子上有没有放盘子,如果有,就端过来吃盘子中的包子,没有的话就等待。对应关系如下图:
考察了一下,原来当初设计这个模式,主要就是用来处理并发问题的,而Celery就是一个用python写的并行分布式框架。
然后我接着去学习Celery
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来存储任务执行结果。
然后我接着去安装Celery,在安装Celery之前,我已经在自己虚拟机上安装好了Python,版本是2.7,是为了更好的支持Celery的3.0以上的版本。
因为涉及到消息中间件,所以我先去选择一个在我工作中要用到的消息中间件(在Celery帮助文档中称呼为中间人),为了更好的去理解文档中的例子,我安装了两个中间件,一个是RabbitMQ,一个redis。
在这里我就先根据Celery3.1的帮助文档安装和设置RabbitMQ, 要使用 Celery,我们需要创建一个 RabbitMQ 用户、一个虚拟主机,并且允许这个用户访问这个虚拟主机。下面是我在个人虚拟机Ubuntu14.04上的设置:
$ sudo rabbitmqctl add_user forward password
#创建了一个RabbitMQ用户,用户名为forward,密码是password
$ sudo rabbitmqctl add_vhost ubuntu
#创建了一个虚拟主机,主机名为ubuntu
$ sudo rabbitmqctl set_permissions -p ubuntu forward ".*" ".*" ".*"
#允许用户forward访问虚拟主机ubuntu,因为RabbitMQ通过主机名来与节点通信
$ sudo rabbitmq-server
之后我启用RabbitMQ服务器,结果如下,成功运行:
之后我安装Redis,它的安装比较简单,如下:
$ sudo pip install redis
然后进行简单的配置,只需要设置 Redis 数据库的位置:
BROKER_URL = 'redis://localhost:6379/0'
URL的格式为:
redis://:password@hostname:port/db_number
URL Scheme 后的所有字段都是可选的,并且默认为 localhost 的 6379 端口,使用数据库 0。我的配置是:
redis://:password@ubuntu:6379/5
之后安装Celery,我是用标准的Python工具pip安装的,如下:
$ sudo pip install celery
为了测试Celery能否工作,我运行了一个最简单的任务,编写tasks.py,如下图所示:
编辑保存退出后,我在当前目录下运行如下命令:
$ celery -A tasks worker --loglevel=info
#查询文档,了解到该命令中-A参数表示的是Celery APP的名称,这个实例中指的就是tasks.py,后面的tasks就是APP的名称,worker是一个执行任务角色,后面的loglevel=info记录日志类型默认是info,这个命令启动了一个worker,用来执行程序中add这个加法任务(task)。
然后看到界面显示结果如下:
我们可以看到Celery正常工作在名称ubuntu的虚拟主机上,版本为3.1.23,在下面的[config]中我们可以看到当前APP的名称tasks,运输工具transport就是我们在程序中设置的中间人redis://127.0.0.1:6379/5,result我们没有设置,暂时显示为disabled,然后我们也可以看到worker缺省使用perfork来执行并发,当前并发数显示为1,然后可以看到下面的[queues]就是我们说的队列,当前默认的队列是celery,然后我们看到下面的[tasks]中有一个任务tasks.add.
了解了这些之后,根据文档我重新打开一个terminal,然后执行Python,进入Python交互界面,用delay()方法调用任务,执行如下操作:
这个任务已经由之前启动的Worker异步执行了,然后我打开之前启动的worker的控制台,对输出进行查看验证,结果如下:
绿色部分第一行说明worker收到了一个任务:tasks.add,这里我们和之前发送任务返回的AsyncResult对比我们发现,每个task都有一个唯一的ID,第二行说明了这个任务执行succeed,执行结果为12。
查看资料说调用任务后会返回一个AsyncResult实例,可用于检查任务的状态,等待任务完成或获取返回值(如果任务失败,则为异常和回溯)。但这个功能默认是不开启的,需要设置一个 Celery 的结果后端(backend),这块我在下一个例子中进行了学习。
通过这个例子后我对Celery有了初步的了解,然后我在这个例子的基础上去进一步的学习。
因为Celery是用Python编写的,所以为了让代码结构化一些,就像一个应用,我使用python包,创建了一个celery服务,命名为pj。文件目录如下:
celery.py
from __future __ import absolute_import
#定义未来文件的绝对进口,而且绝对进口必须在每个模块的顶部启用。
from celery import Celery
#从celery导入Celery的应用程序接口
App.config_from_object(‘pj.config’)
#从config.py中导入配置文件
if __name__ == ‘__main__’:
app.start()
#执行当前文件,运行celery
app = Celery(‘pj’,
broker=‘redis://localhost’,
backend=‘redis://localhost’,
include=[‘pj.tasks’]
)
#首先创建了一个celery实例app,实例化的过程中,制定了任务名pj(与当前文件的名字相同),Celery的第一个参数是当前模块的名称,在这个例子中就是pj,后面的参数可以在这里直接指定,也可以写在配置文件中,我们可以调用config_from_object()来让Celery实例加载配置模块,我的例子中的配置文件起名为config.py,配置文件如下:
在配置文件中我们可以对任务的执行等进行管理,比如说我们可能有很多的任务,但是我希望有些优先级比较高的任务先被执行,而不希望先进先出的等待。那么需要引入一个队列的问题. 也就是说在我的broker的消息存储里面有一些队列,他们并行运行,但是worker只从对应 的队列里面取任务。在这里我们希望tasks.py中的add先被执行。task中我设置了两个任务:
所以我通过from celery import group引入group,用来创建并行执行的一组任务。然后这块现需要理解的就是这个@app.task,@符号在python中用作函数修饰符,到这块我又回头去看python的装饰器(在代码运行期间动态增加功能的方式)到底是如何实现的,在这里的作用就是通过task()装饰器在可调用的对象(app)上创建一个任务。
了解完装饰器后,我回过头去整理配置的问题,前面提到任务的优先级问题,在这个例子中如果我们想让add这个加法任务优先于subtract减法任务被执行,我们可以将两个任务放到不同的队列中,由我们决定先执行哪个任务,我们可以在配置文件中这样配置:
先了解了几个常用的参数的含义:
Exchange:交换机,决定了消息路由规则;
Queue:消息队列;
Channel:进行消息读写的通道;
Bind:绑定了Queue和Exchange,意即为符合什么样路由规则的消息,将会放置入哪一个消息队列;
我将add这个函数任务放在了一个叫做for_add的队列里面,将subtract这个函数任务放在了一个叫做for_subtract的队列里面,然后我在当前应用目录下执行命令:
这个worker就只负责处理for_add这个队列的任务,执行这个任务:
任务已经被执行,我在worker控制台查看结果:
可以看到worker收到任务,并且执行了任务。
在这里我们还是在交互模式下手动去执行,我们想要crontab的定时生成和执行,我们可以用celery的beat去周期的生成任务和执行任务,在这个例子中我希望每10秒钟产生一个任务,然后去执行这个任务,我可以这样配置:
使用了scheduler,要制定时区:CELERY_TIMEZONE = 'Asia/Shanghai',启动celery加上-B的参数:
并且要在config.py中加入from datetime import timedelta。
更近一步,如果我希望在每周四的19点30分生成任务,分发任务,让worker取走执行,可以这样配置:
看完这些基础的东西,我回过头对celery在回顾了一下,用图把它的框架大致画出来,如下图: