本文对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. java数组排序(冒泡、直排)反转

    package lianxi; public class maopao { public static void main(String[] args){ int[] i=new int[]{45,6 ...

  2. Mybitis配置文件中的别名以及mapper中的namespace

    1 基本知识 MyBatis中如果每次配置类名都要写全称也太不友好了,我们可以通过在主配置文件中配置别名,就不再需要指定完整的包名了. 别名的基本用法: <configuration> & ...

  3. Maven “Failed to execute goal org.apache.maven.plugins:maven-archetype-plugin:2.4:create...”问题总结

    今天学习Maven的过程中,一直遇到一个问题:用maven指令构建新项目时,一直报错,用的 Maven 3.2 , JDK 6. 构建的命令: 错误信息: 解决方案: 在StackOverFlow上找 ...

  4. Orchard 学习

    https://github.com/OrchardCMS/Orchard  源码下载 http://www.orchardch.com/  中文介绍网站

  5. windows安装设备的驱动程序软件遇…

    问题描述: 有时候我们在使用一些设备的时候需要获取电脑的驱动.安装方式一共有三种: 第一种是让系统自动更新和安装. 第二种是打开"资源管理器"选择指定文件夹路径更新. 第三种是使用 ...

  6. swift 录制多个音频 并将音频转换为mp3 并合成多个mp3文件为一个文件

    我的需求是可以录制多个文件,最后生成的文件格式为mp3形式,查了下各种资料,因为swift无法直接将音频录制为mp3格式,所以最后我采取的解决方案为先将每个单独的文件转为mp3,最后逐一合并形成一个m ...

  7. 弱校ACM奋斗史

    看到这篇文章, 已是大三了, 我的ACM之路也即将走向终点, 感慨自己还是不够努力, 给自己的大学留下诸多遗憾. 和他们相比, 我差的就是太远了, 值得高兴的是我们学校有一个好老师-----赵靖老师, ...

  8. HTML 基本标签02

    02-html基本标签 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> & ...

  9. 轻松学JVM(二)——内存模型、可见性、指令重排序

    上一篇我们介绍了JVM的基本运行流程以及内存结构,对JVM有了初步的认识,这篇文章我们将根据JVM的内存模型探索java当中变量的可见性以及不同的java指令在并发时可能发生的指令重排序的情况. 内存 ...

  10. SpringBoot构建RESTful service完成Get和Post

    一个基本的RESTfule service最进场向外提供的请求Method就是Get和Post. 在Get中,常用的都会在请求上带上参数,或者是路径参数.响应Json. 在Post中,常用的会提交fo ...