首先解释下目标的概念:celery任务消息会由各种途径(比如手动通过python shell触发、通过tornado触发等)发往统一的一个celery broker,然后任务消息会由不同server上的worker去获取并执行。具体点说就是,借助celery消息路由机制,celery broker中开不同的消息队列来接收相应类型的任务消息,然后不同server上开启worker来处理目标消息队列里面的任务消息,即任务统一收集、分发到不同server上执行。
测试
项目架构如下:一个服务,一部分task运行在server1上,一部分task运行在server2上,所有的任务都可以通过网页向tornado(部署在server1上)发起、tornado接到网页请求调用相应的task handler、task handler向celery broker相应的queue发任务消息、最后server1上的worker和server2上的worker各自去相应的队列中获取任务消息并执行任务。server1是上海集群的10.121.72.94,server2是济阳集群的10.153.104.76,celery broker是redis数据库:redis://10.121.76.204:17016/1。
首先来看一下server1上的代码结构,
| start_worker.sh
| proj
|__init__.py (空文件)
|celery.py
|hotplay_task.py
| hotplay_tornado_server.py
上面的代码包含了响应网页请求的tornado server构建代码、server1上的celery服务。
先来看server1上的celery调度器,
celery.py
hotplay_task.py
start_worker.sh
上面的代码定义了一个celery实例,该实例有两个队列,注册了两个celery task function,最后启动一个worker来处理默认队列
hotplay_sh_default_queue
(celery.py中重命名过的默认队列)中的任务消息。
tornado server是所有celery任务的发起者,server1和server2上celery task都由
tornado server相应的handler发起。
hotplay_tornado_server.py
代码中定义了3个handler,前两个负责在接收到相应的网页请求后,发起server1上定义的两个task function任务消息,消息发往
celery broker的默认队列
hotplay_sh_default_queue
(使用task_name.delay函数发出的请求会加入到默认队列,使用task_name.apply_async或send_task函数则可以指定目标队列),最后由server1上的worker执行。网页请求的格式类似——http://10.121.72.94:10501/do_catchup_jy/?hotplay_id=pxftest&start_dt=2015-08-12&end_dt=2015-08-14。
第3个handler发起一个名为
tasks.test1
的任务消息,发往celery broker的另一个队列
hotplay_jy_queue
,
tasks.test1
任务并没有在server1上的celery调度器中实现(也叫注册),而是放在了server2上,相应的,处理
队列
hotplay_jy_queue
的worker也在
server2上运行。这里,由于
tasks.test1task function没有注册在server1上,所以使用send_task函数来发送任务消息;这是因为task_name.delay、task_name.apply_async函数发送任务请求需要先import task_name相应的python function,而send_task函数发送任务消息其实就相当于往celery broker发送一个字符串类似的任务请求、不需要调用事先写好的task function,然后该字符串类似的任务消息由worker获取、worker根据任务消息去寻找实际的task function来执行。这种机制也是celery实现任务统一收集、分发执行的基础。
来看server2上的celery调度器,
|tasks.py
(注意,要和tornado server中send_task()函数用的file_name一样)
|start_server.sh
由于只是功能测试,写得比较简单,
tasks.py
start_server.sh
server2上调度器主要就是开了一个worker来取tornado server发往
hotplay_jy_queue
队列的任务并执行,当然,任务在哪里执行、相应的任务函数就应该放在哪里。此外,server2和server1上的celery实例app的消息队列配置应该保持一致,因为它们是对同一个celery broker的配置。
总结
最后总结下上面项目架构的实现:所有的celery任务都由tornado server发起,统一由
celery broker收集、不过
分别由celery broker的
hotplay_sh_default_queue
和
hotplay_jy_queue
两个消息队列接收,最后分别由server1和server2上的worker去执行。
在上面的项目架构中,tornado server是和server1上的celery调度器放在一起的,这是有必要的,因为send_task函数发送任务消息的时候,至少应该要知道celery broker等信息,而这些信息在server1的celery调度器上有(请注意
hotplay_tornado_server.py
中
from proj
.
hotplay_task
import
do_init_catchup
,
do_catchup
语句,该语句不仅import两个任务函数,还获取了celery实例app的信息,从而获得了
celery broker等配置信息)
。在这之后,如果有其他任务要集成进来,直接在
hotplay_tornado_server.py中增加相应的handler(调用send_task函数向目标队列发送相应的任务消息,目标队列不需要在server1上申明)、并在其他server上写好相应的celery调度器(申明消息队列、实现celery task function、开启worker)即可。
这时,
tornado server负责所有任务(不止是本文提到的3个任务)的触发(通过网页触发比较方便)、然后使用
send_task函数往某一个固定的celery broker发送任务消息、不同种类的任务消息发到celery broker上特定的消息队列,每种任务的执行由任务部署的服务器上的celery调度器(就和server2上的调度器)完成,由各个服务器上的celery调度器的worker会到自己目标队列中取任务消息来执行。这样做的好处是:一个broker搞定所有任务,不过有多少种不同的任务、broker上就会有多少个消息队列。
后续
上文总结中提到tornado server需要和server1上的celery调度器放在一起,以获取celery broker的信息,经过尝试,tornado server是可以完全独立出来的。
在tornado server的py文件中添加以下代码:
from celery import Celery
app = Celery(broker = "redis://10.121.76.204:17016/1",)
接着,改
send_task
('tasks.test1', args=[hotplay_id, start_dt, end_dt],queue='hotplay_jy_queue')
为
app.send_task('tasks.test1', args=[hotplay_id, start_dt, end_dt], queue='hotplay_jy_queue')
然后,就可以去掉下面两行了:
from celery.executeimport send_task
from proj.hotplay_taskimport do_init_catchup, do_catchup
这样子,
tornado server就可以完全独立出来运行,而不必再和任何任务绑在一起以获得celery broker的信息,因为celery broker的信息直接写在
tornado server的代码里了。当然,
hotplay_tornado_server.py
代码经过上面的修改、完全独立出来后,
do_init_catchup
.
delay
(
user_name
,
album_id
,
album_name
,
channel_name
)
和
do_catchup
.
delay
(
hotplay_id
,
start_dt
,
end_dt
)
需要用send_task函数改写,
app.send_task('proj.hotplay_task.do_init_catchup', args=[user_name, album_id, album_name, channel_name]) #send to default queue: hotplay_default_sh_queue
app.send_task('proj.hotplay_task.do_catchup', args=[hotplay_id, start_dt, end_dt])
最后说明一下,
tornado server完全独立出来的好处:
如果不完全独立出来,那么和
tornado server放在一起的celery调度器需要修改的话,则celery worker和
tornado server也需要重启(
tornado server代码调用了
celery调度器的任务函数以及broker信息,所以要重启
),
tornado server至少和一个
celery调度器存在耦合
;完全独立后,
解除了tornado server代码和celery调度器之间的耦合,这时tornado server中使用send_task函数发送任务消息、无需经过实际实现的celery任务函数,所以任何celery调度器的改动(只要别改任务函数名和任务函数的参数)都无需重启tornado server、而只要重启celery worker即可,也就是说任务的提交和任务的执行完全分离开来了。
参考: