学习内容:
- celery介绍,以及原理
- celery安装与配置
- celery使用,路由配置
- celery中其他模块(kombu-Queue,celery.utils.log)
学习目标
- 了解celery的基本原理
- 可配置celery,写简单的服务
- 可编写一个定时任务--待续
一、celery基本介绍以及原理
(1)celery使用原因:
在程序运行过程中,我们经常会遇到一些耗时耗资源的操作,为了避免阻塞主程序,我们会采用异步或者多线程来处理任务。比如在主程序中调用一个函数,并从该函数中获取函数返回值。如果这个函数不能很快执行完成并返回,那么主程序就会阻塞,知直到函数返回。
(2)celery基本概念:
Celery是一个强大的分布式任务队列,它可以让任务的执行完全脱离主程序,甚至可以被分配到其他的主机上运行。它是一个专注于实时处理的任务队列,同时也支持任务调度。
(3)celery实现原理:
原理图如下:
其中包含的模块有:
任务模块 :Task,包含异步任务和定时任务。其中,异步任务通常在业务逻辑中被触发并发往任务队列,而定时任务由 Celery Beat 进程周期性地将任务发往任务队列。
消息中间件:Broker,即为任务调度队列,接收任务生产者发来的消息(即任务),将任务存入队列。Celery 本身不提供队列服务,官方推荐使用 RabbitMQ 和 Redis 等。
任务执行单元 :Worker, 是执行任务的处理单元,它实时监控消息队列,获取队列中调度的任务,并执行它。
任务结果存储 :Backend,用于存储任务的执行结果,以供查询。同消息中间件一样,存储也可使用 RabbitMQ, Redis 和 MongoDB 等。
二、celery下载安装
(1)pip下载安装celery
$ pip install celery==3.1.0
tip:windows上不支持celery4.2版本,必须下载3.x版本
(2)pip下载celery所需队列(选择:redis)
$ pip install celery[redis]
(3)代码中使用
#需要导入包
from celery import Celery
三、celery使用
(1)celery实例的声明以及任务注册
①首先第一步,要导入celery模块
from celery import Celery
②第二步,实例化一个celery
app = Celery()
③第三步,将你的函数注册为celery需要监听的任务
@app.task
def add(a,b):
return a+b
详情可看下面的代码
(2)调用任务,即将任务放入队列执行(以下两种中,看后端代码常用后一种)
①add.delay(a=34,b=45)
②app.send_task(task_routes,task_queue,task_params_list)
(3)celery读取配置
(4)celery配置文件中celery_queue,celery_routes的设置
以上的使用可以详情见后附代码
(如果没有celery模块,代码通过lpush值到redis,然后有一个while无线循坏的代码,通过blpop命令监控这个队列,blpop是redis的一个阻塞式命令,只要队列有值,就会弹出使用,没有值就无限等待,直到有值,就会弹出,celery的原理就是封装了发送任务至队列以及监控队列的脚本,不用开发工程师再去写blpop的脚本,直接配置呢就可以使用。大概原理就是这样。所以如果不用celery,也可以用最原始的方法实现异步消息的推送)
case1:delay()方式发送任务至队列
case1:
#注册任务
send_task.py
# -*- encoding:utf-8 -*-
import time
from celery import Celery
brokers = "redis://localhost:6379/1"
backend = "redis://localhost:6379/2"
celery = Celery('send_task',broker =brokers,backend=backend)
@celery.task
def add(x, y):
time.sleep(20)
return x +y
@celery.task
def multipy(a, b):
return a*b
#任务调用
app.py
from send_task import add
if __name__ == '__main__':
print "start task"
add.delay(2,4)
#将任务放置在队列中了,woker监控到队列有值之后,那么会自动取出任务执行
print 'end task..'
疑问:
1.只指定了broker,(broker配置的地址仅到redis的某个库而已)但是没有具体指定队列?任务是发送至哪里了?worker监控哪个队列然后取任务执行的?是有默认的celery_task队列?
回答:celery有默认的队列,在设置了broker之后,add任务会默认发送至celery默认的default队列,worker会默认检测default队列有任务,并取出任务执行!当配置了backend之后,执行的结果存放在backend默认的default队列,当然也可以配置自己想要存放的队列
2.当未指定backend时,worker从broker-default队列取出“add”任务执行,任务结果会存放?为什么在worker服务启动界面还是可以看到返回的结果?如下:
[2018-11-21 16:08:07,469: INFO/MainProcess] Received task: send_task.add[1ac1d46e-35c1-4f77-80fc-df6252566417]
[2018-11-21 16:08:07,470: WARNING/Worker-1] action
[2018-11-21 16:09:07,470: WARNING/Worker-1] over
[2018-11-21 16:09:07,490: INFO/MainProcess] Task send_task.add[1ac1d46e-35c1-4f77-80fc-df6252566417] succeeded in 60.0209999084s: 6
回答:界面上显示了执行结果,表示broker的任务被取出执行了,如果没有执行,那说明可能卡在队列中了,与结果放置在backend中没有关系,即未设置backend就不会存储结果,配置了backend,就会存在对应的backend中
3.如果设置了backend,当add任务加入队列之后,worker取出并执行add方法,得到的结果应该在backend默认队列中,为什么去redis里面查看backend默认队列,没有结果?
回答:是有结果,
不过因为celery安全性,会给结果进行加密,直接在redis命令行通过get查看
case2:send_task()方式发送任务至队列
#发送结果至队列的启动函数
#app.py
# -*- encoding:utf-8 -*-
from send_task import app
if __name__ == '__main__':
print "start task"
app.send_task('send_task.add', queue='for_one',args=[56,311])
app.send_task('send_task.multiply',queue='for_two',args=[55,2])
#任务生产者,将add任务发送至for_add队列,args是该任务执行所需的参数
"""
参数解析:
①任务路径及任务名称:给定该参数,会去这个路径下找该任务,并将该任务添加至后一个指定的队列;send_task.add:表示将哪个文件下的哪个任务放置队列中,如果给定一个错误的或者没有定义的任务,那么找不到,那就没有办法将这个任务放在队列中了
②队列名称:给定该参数,会将任务放置在该队列中,当worker启动中,会自动检测到该队列有任务并执行;
③任务所需参数:该参数执行,执行的任务所需的参数,即:add方法是需要两个参数的,如果缺少时,在添加任务至队列时,代码不会报错,在执行时报错
"""
print 'end task..'
#send_task.py
#具体的任务函数
# -*- encoding:utf-8 -*-
from celery import Celery
app = Celery('task')
app.config_from_object('celeryconfig_send')
@app.task
def add(x, y):
print "add"
return x+y
@app.task
def multiply(a, b):
print "multiply"
return a * b
#celery配置文件
# -*- encoding:utf-8 -*-
from kombu import Queue
BROKER_URL = "redis://localhost:6379/1"
CELERY_RESULT_BACKEND= "redis://localhost:6379/2"
CELERY_TIMEZONE='Aisa/shanghai'
CELERY_DEFULT_QUEUE = 'default'
#创建任务队列
CELERY_QUEUES = (
Queue('default', routing_key='default'),#不指定这行代码会报错
Queue('for_one', routing_key='for_one'),
Queue('for_two', routing_key='for_two')
)
#任务路由,相当于告诉worker,从哪里取获取要执行的哪个任务函数,当找不到任务对应的函数方法时,会报错
CELERY_ROUTES = {
'send_task.tasks.add':{
"queue":"for_one",
"routing_key":"for_one"
},
'send_task.tasks.multiply': {
"queue": "for_two",
"routing_key": "for_two"
}
}
学习代码已上传至github:https://github.com/zjojo/python_study/tree/master/A03_celery