本文对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. (转)Vi命令详解

    vi编辑器是所有Unix及Linux系统下标准的编辑器,它的强大不逊色于任何最新的文本编辑器,这里只是简单地介绍一下它的用法和一小部分指令.由于对Unix及Linux系统的任何版本,vi编辑器是完全相 ...

  2. 安装cocoaPods第三方类库

    *1 检测gem 镜像文件     输入指令: gem sources -l     回车后得到镜像地址.可能是一个,也可能有好几个,常见两个如下 https://rubygems.org/     ...

  3. Asp.net MVC Razor常见问题及解决方法

    没有经验的童鞋就是这样磕磕碰碰出来的经验. 1,Datatype的错误提示消息无法自定义 这也许是Asp.net MVC的一个Bug.ViewModel中定义了DataType为Date字段: [Re ...

  4. HDU1124 Factorial

    Problem Description The most important part of a GSM network is so called Base Transceiver Station ( ...

  5. JSON风格指南

    中文版:https://github.com/darcyliu/google-styleguide/blob/master/JSONStyleGuide.md 英文版:https://google.g ...

  6. maven快速上手

    1.maven安装 首先下载apache-maven-3.3.3-bin.zip(版本可以自己根据自己想要的下载). 解压后如下:   接下来配置系统环境变量: 到此,maven安装好了,接下来输入 ...

  7. ORACLE - 用户和角色的权限管理

    在ORACLE中,创建用户后需要授权才能使用. 一.用户管理 1. 用户和角色信息查询 --查询所有用户 SQL> select * from dba_users; --经授予的用户或角色的系统 ...

  8. Java 期末考试

    一: 题目:打印出100-999之间所有的"水仙花数",所谓"水仙花数"是指一个三位数,其各位数字立方和等于该数本身.   例如:153是一个"水仙花 ...

  9. 4G内存服务器的MySQL配置优化

    公司网站访问量越来越大(日均超10万PV),MySQL自然成为瓶颈,关于 MySQL 的优化,最基本的是 MySQL 系统参数的优化. MySQL对于web架构性能的影响最大,也是关键的核心部分.My ...

  10. 关于bootstrap的一些想法

    老实说,作为一个前端人员,我不怎么会去用bootstrap,但是我会去看,会去了解. 首先,bootstrap其实是给后台以及前端新人用来快速完成一个页面的简单布局,不是按照设计稿psd格式给定做的那 ...