本文对Celery进行了研究,由于其实现相对比较复杂没有足够的时间和精力对各方各面的源码进行分析,因此本文根据Celery的使用方法以及实际行为分析其运行原理,并根据查阅相关代码进行了一定程度的验证。

希望本文能有助于读者理解celery是如何工作的,从而能够更好地使用这个任务框架,而不仅仅是复制官网上的例子来配置。

Celery是Python中任务队列的事实标准。其特点在于:

  • 启动后,本身是一个任务分发进程,会启动若干个worker进程完成任务
  • 需要依赖一个消息队列来负责任务从客户端到Celery进程的派发。这样的好处是,客户端代码只需要向MQ中派发任务请求以及参数,Celery进程就可以从MQ中读取消息并派发给worker,从而达到了客户端程序与Celery进程解耦的效果。而且Celery进程并不需要监听任何端口,减少了配置的复杂性。常用的消息队列实现可以使用RabbitMQ,Redis等等。

下面我们结合Celery的基本使用来分析一下Celery是怎么工作的。本文以Python2为例。

1. 定义Celery配置文件并启动

首先,我们需要定义我们的Celery进程访问哪个Redis进程(假设我们使用Redis作为message backend,在celery的术语中叫做broker)。

Celery提供的方式是创建一个celery instance。我们假设文件目录如下:

lab
- play
- __init__.py
- celery.py
- tasks.py

然后创建lab/play/celery.py文件:

from __future__ import absolute_import, unicode_literals
from celery import Celery app = Celery('play',
broker='redis://127.0.0.1:6379',
include=['play.tasks']) if __name__ == '__main__':
app.start()

由于可能会有多个celery进程访问同一个redis,为了让它们之间隔离开就需要给每个celery实例一个名字,我们这里就叫play

除了name和broker参数以外,还使用了include参数来告诉所有的works到哪里去import tasks的代码,因为workers才是真正执行所有这些任务的单位。

好了,接下来就可以启动celery了。在lab目录下执行:

celery -A play.celery worker -l info

即可启动celery进程。Python的路径和模块系统还是比较复杂的,因此在指定包名的时候要注意。

除了使用celery命令以外,由于我们再celery.py中已经加了if __name__ == '__main__':部分代码,因此也可以在lab下直接执行:

python -m play.celery -A play.celery worker -l info

在启动了celery以后,celery进程监听redis消息,并fork出多个worker进程准备将监听到的消息分发给它们执行。

2. 编写任务并执行

现在执行的部分有了,我们开始定义真正需要执行的部分。

我们可以专门写一个文件来存放任务代码(也可以直接写在celery.py里面):

# lab/play/tasks.py
from __future__ import absolute_import, unicode_literals
import time
from celery import Celery app = Celery('play',
broker='redis://127.0.0.1:6379') @app.task
def say_hi():
print 'hi!'

使用另一个Python进程(也可以使用交互式python或者ipython),在lab下执行:

>>> from play.tasks import say_hi
>>> say_hi.delay()
>>> <AsyncResult: db6737ba-ecee-4fd2-8227-a76c594ba338>
>>>

结果就是say_hi函数向消息队列中发出了一个调用请求由某个worker执行。Celery进程会输出:

[2017-09-03 13:49:57,340: INFO/MainProcess] Received task: play.tasks.say_hi[85ff01ca-d7c9-4401-bfa3-0a9ad96c7192]
[2017-09-03 13:49:57,343: WARNING/ForkPoolWorker-1] hi!
[2017-09-03 13:49:57,344: INFO/ForkPoolWorker-1] Task play.tasks.say_hi[85ff01ca-d7c9-4401-bfa3-0a9ad96c7192] succeeded in 0.0016004400095s: None

现在我们来分析一下tasks.py这个文件。很奇怪的一点是,一上来我们又创建了一个app实例。当我们import了task文件后会不会又创建了一个celery进程呢?答案是不会的,因为只有调用了app.start()才会启动。这只有手动调用或者借助celery命令执行后才会发生。如果只是new了一个instance出来,相当于创建了一个配置文件,不会发生任何重要的实质性的操作。

但是这个app对象也不是什么都不干的。接下来我们定义了两个task函数,并将这个两个函数使用@app.task包装了起来。这样的效果是把这两个普通函数包装成了celery的task对象,这样他们就有了delay方法。当我们执行delay方法时,这些task会找自己所属的那个celery instance,从中获取配置信息(主要是broker的地址)后将调用请求发往消息队列。

不过,这样定义task的方法并不是很好,因为需要在代码中就显式将task函数和一个具体的celery instance绑定了起来。这就使得我们无法复用这些tasks。因此我们可以使用celery的另一种定义tasks的方式来重写我们现有的代码(这也是推荐给django使用的方案):

from __future__ import absolute_import, unicode_literals
import time
from celery import shared_task @shared_task
def say_hi():
print 'hi!'

这里我们不再创建app实例,而是直接使用@shared_task来包装。这样就没有绑定哪个app的问题了。但是正如我们之前所说,在调用tasks的时候,task还是会去寻找自己属于哪个celery instance从而获取配置信息。如果你都不绑定app instance,配置信息哪里来呢?

答案是,tasks和celery instance之间仍然具有绑定或关联的关系,只不过不再是显式的了。简单来说,每个celery instance被创建以后,它就会被自动的注册到某个全局的位置。当一个shared task被执行时,这个task就会自己去这个全局的位置找有哪些celery instances可以从中获取配置信息。如果有多个celery instance都注册了,那么可能它们的消息队列都会被这个task发消息(没有确认过,只是猜测。但这可能就是shared_task的来源)。这就意味着,只要在我们Python进程的任何一个地方(对Django服务器进程也是如此),只要随便哪个地方创建一个celery instance就可以,然后只要import tasks然后使用delay执行即可。这样就解决了celery tasks复用的问题。代码之间的耦合也更小。

更进一步,在我们的python进程中,甚至都不用再手写一遍celery instance的创建调用。直接import play.celery 就可以了,这个文件虽然被celery进程用作了配置文件,但这不妨碍我们在自己的进程中也用这个文件。不如说这是更好的一种解决方案。

Celery基本原理探讨的更多相关文章

  1. 基于celery的任务管理

    1.celery基本原理 Celery是一个由python编写的简单.灵活.可靠的用来处理大量信息的分布式系统,同时提供了操作和维护分布式系统所需要的工具,说白了就是一个用来管理分布式队列的工具. C ...

  2. 移动Web之响应式布局的探讨

    响应式布局的探讨 响应式布局的两种方式 基于百分比的布局 例:Bootstrap 基于rem的布局 例:淘宝触屏版 这两种布局都需要依赖于CSS3的media query来设置布局断点(或者通过js监 ...

  3. celery 异步任务小记

    这里有一篇写的不错的:http://www.jianshu.com/p/1840035cb510 自己的"格式化"后的内容备忘下: 我们总在说c10k的问题, 也做了不少优化, 然 ...

  4. [专业名词·硬件] 2、DC\DC、LDO电源稳压基本常识(包含基本原理、高效率模块设计、常见问题、基于nRF51822电源管理模块分析等)·长文

    综述先看这里 第一节的1.1简单介绍了DC/DC是什么: 第二节是关于DC/DC的常见的疑问答疑,非常实用: 第三节是针对nRF51822这款芯片电源管理部分的DC/DC.LDO.1.8的详细分析,对 ...

  5. LDPC编译码基本原理

    LDPC编译码基本原理     学习笔记 V1.1 2015/02/18 LDPC编译码基本原理   概述   本文是个人针对LDPC的学习笔记,主要针对LDPC译码算法做了简要的总结.该版本主要致力 ...

  6. cas系列(一)--cas单点登录基本原理

    (这段时间打算做单点登录,因此研究了一些cas资料并作为一个系列记录下来,一来可能会帮助一些人,二来对我自己所学知识也是一个巩固.) 一.为什么要实现单点登录 随着信息化不断发展,企业的信息化过程是一 ...

  7. (转)从内存管 理、内存泄漏、内存回收探讨C++内存管理

    http://www.cr173.com/html/18898_all.html 内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟 ...

  8. Oracle数据库容灾备份技术探讨

    Oracle数据库容灾备份技术探讨 三种Oracle灾备技术 对于Oracle数据库的灾备技术,我们可以从Data Guard,GoldenGate和CDP角度去考虑. Oracle Data Gua ...

  9. 深度学习新星:GAN的基本原理、应用和走向

    深度学习新星:GAN的基本原理.应用和走向 (本文转自雷锋网,转载已获取授权,未经允许禁止转载)原文链接:http://www.leiphone.com/news/201701/Kq6FvnjgbKK ...

随机推荐

  1. 快速搞定selenium grid分布式

    写这篇文章,似乎有点重复造轮子的嫌疑.当看了几篇相关文章后,我还是决定把半年前的半成品给完成了. 以传统的方式部署分布式Selenium Grid集群需要耗费大量时间和机器成本来准备测试环境. Sna ...

  2. MySQL_第三方数据库引擎_tokudb

    前阵子迁移zabbix到tokudb,整理部分操作笔记到这篇博文.       如果转载,请注明博文来源: www.cnblogs.com/xinysu/   ,版权归 博客园 苏家小萝卜 所有.望各 ...

  3. 计时器C#

    用于测某一方法执行所用的时间: Stopwatch sw = new Stopwatch(); sw.Start(); //功能代码块 sw.Stop(); long totalTime = sw.E ...

  4. MySQL buffer pool中的三种链

    三种page.三种list.LRU控制调优 一.innodb buffer pool中的三种页 1.free page:从未用过的页 2.clean page:干净的页,数据页的数据和磁盘一致 3.d ...

  5. Linux修改网卡名称、主机名

    Linux修改网卡名称.主机名 环境:VirtualBox 5.0.14 + RHEL 6.5 需求:个人实验搭建一套Standby RAC时,为了节约时间,直接复制之前安装RAC的主机模板. 但复制 ...

  6. 关于Oracle连接超时的问题

    测试环境ORACLE 11.2.0. 如果连接池设置单个连接闲置时间大于数据库连接超时时间,则连接池中的连接发出数据请求时会出现Connect timeout occurred错误, 这是由于连接超时 ...

  7. node调用phantomjs-node爬取复杂页面

    什么是phantomjs phantomjs官网是这么说的,'整站测试,屏幕捕获,自动翻页,网络监控',目前比较流行用来爬取复杂的,难以通过api或正则匹配的页面,比如页面是通过异步加载.phanto ...

  8. mk框架,一个基于react、nodejs全栈框架

    在这个前端技术爆炸的时代,不自己写套开源框架出门都不好意思跟别人打招呼,作为一个前端领域的小学生,去年年初接触了react,之后一发不可收拾爱上了它,近期重构了自己去年开源的一个项目,废话到此结束句号 ...

  9. Kafka 存储机制和副本

    1.概述 Kafka 快速稳定的发展,得到越来越多开发者和使用者的青睐.它的流行得益于它底层的设计和操作简单,存储系统高效,以及充分利用磁盘顺序读写等特性,和其实时在线的业务场景.对于Kafka来说, ...

  10. Asp.net中,从弹出窗体取选择值(转)

    在Asp.net中,从A页面中弹出B页面,在B页面中选择数据后,关闭并将数据更新到A页面,是一种常用 的方式.只是我对Javascript不熟悉,所以捣鼓了一下午,终于有了一点成绩:测试项目有两个页面 ...