Celery学习笔记(一)

来源:互联网 发布:webpack php 编辑:程序博客网 时间:2024/06/05 22:36

Tips:在学习Celery过程中,使用的系统为Windows 10、Celery版本为3.1.18①、中间人使用RabbitMQ。

C:\Users\foolf>celery --version3.1.18 (Cipater)

什么是任务队列

任务队列是一种在线程或者机器之间分发任务的机制。
消息队列的输入是工作的一个单元,称为任务,独立的职程(Worker)进程持续监视队列中是否有需要处理的新任务。

Celery使用消息通信,通信一般使用中间人(Broker)在客户端和职程之间斡旋。这个过程从客户端想队列中添加消息开始,之后中间人将消息派送给职程。

Celery是Python编写的,但协议可以使用任何语言实现。

需要什么

Celery需要一个发送和接受消息的传述者。RabbbitMQ和Redis中间人的消息支持所有的特性,我们主要是使用RabbitMQ作中间人(关于中间人RabbitMQ的安装可以网上搜索,有很多详细的教程)。

Celery优势

在程序运行过程中,我们经常会遇到一些耗时耗资源的操作,为了避免阻塞主程序,我们会采用异步或者多线程来处理任务。比如在主程序中调用一个函数,并从该函数中获取函数返回值。如果这个函数不能很快执行完成并返回,那么主程序就会阻塞,知直到函数返回。
Celery是一个强大的分布式任务队列,它可以让人物的执行完全脱离主程序,甚至可以被分配到其他的主机上运行。

Celery架构:


架构

从图上可以看出Celery包含几个模块:

  • 任务模块
    主要包异步任务和定时任务,异步任务通常在业务逻辑中被触发并发送到任务队列中,而定时任务是由Celery Beat进程周期性的将任务发往任务队列。
  • 消息中间件Broker
    Broker就是任务调度队列,接受任务生产者发送来的消息,将任务存入队列,之所以需要中间人的原因是Celrey本身是不提供消息队列的服务,所以需要第三方组件实现。
  • 任务执行单元Worker
    Worker是执行任务的单元,它实时监控消息队列,如果有任务就获取任务并执行它。
  • 任务存储Backend
    Backend用于存储任务只想的结果,存储可以使用RabbitMQ或者Redis或者数据库等。
安装Celery

Celery已经提交到Pypi上,所以我们可是使用Python的工具pip来安装。

pip install celery==3.1.25

上面的安装命令,如果没有指定版本,系统会默认安装最新版本的Celery,但是这里可能在后面的学习中遇到问题。关于问题放到最后解释,暂且我们先安装3.1.18版本的Celery。

创建Celery实例

如果你已经安装好了Celery,那么现在就可以创建Celery实例了:
tasks.py

# coding:utf-8from celery import Celeryapp = Celery('tasks', broker='amqp://guest@localhost//')@app.taskdef add(x, y):    return x + y

Celery的第一个参数是当前模块名称,这个参数是必须的,第二个参数是中间人关键字参数,指定我们所使用的的消息中间人的URL,这里我们使用的是RabbitMQ。我们定义了一个单一的任务,称为add,返回两个数字的和。

启动Celery职程服务器(Worker)

 celery -A tasks worker --loglevel=info

参数-A指定了Celery实例的位置,这个实例是在tasks.py文件中,Celery会自动在该文件中查找Celery对象实例。
--loglevel指定日志的级别,默认是warning。
如果启动正常,就会看到下面的输出。


running
调用任务

现在我们已经开启了一个Worker了,这样我们可以在应用程序中使用 delay()或者 apply_async()方法来调用任务。
在tasks.py文件所在的目录打开终端。

>>> from tasks import add>>> add.delay(2, 8)<AsyncResult: 1b50f449-8fa2-478a-9eea-561a3c29fd43>>>>

我们先从tasks.py文件中导入add任务对象,然后使用delay()方法将任务发送到消息中间件,我们之前开启的那个Worker会一直监控任务队列,知道有任务到来,就会执行。
我们到Worker中可以看到多了几条日志信息:

[2017-03-09 19:45:35,351: INFO/MainProcess] Received task: tasks.add[1b50f449-8fa2-478a-9eea-561a3c29fd43][2017-03-09 19:45:40,920: INFO/MainProcess] Task tasks.add[1b50f449-8fa2-478a-9eea-561a3c29fd43] succeeded in 5.56299996376s: 10

说明我们的任务被调度并执行成功了。

获得结果

刚我们在命令行中调用任务,很明显任务执行完成,但是我们并不知道任务执行后得到的结果是什么。如果我们想获得执行后的结果可以这样:

>>> result = add.delay(2, 8)>>> result.ready()  # 查看任务执行的状态,此刻任务没有执行完成,显示FalseFalse>>> result.ready()True  # 表示任务已经执行完成>>> result.get()  # 获取任务的执行结果10>>>
注解:

①:之前我是在windows下学习的Celery,安装的Celery版本是4.0.2;在运行Worker过程中遇到如下ed错误:

I:\Celery\celery-examples>celery -A tasks worker --loglevel=info -------------- celery@DESKTOP-N53SFFK v4.0.2 (latentcall)---- **** -------- * ***  * -- Windows-10-10.0.14393 2017-02-28 00:32:22-- * - **** ---- ** ---------- [config]- ** ---------- .> app:         tasks:0x4700908- ** ---------- .> transport:   amqp://guest:**@localhost:5672//- ** ---------- .> results:     disabled://- *** --- * --- .> concurrency: 4 (prefork)-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker)--- ***** ----- -------------- [queues]                .> celery           exchange=celery(direct) key=celery[tasks]  . tasks.add[2017-02-28 00:32:22,619: CRITICAL/MainProcess] Unrecoverable error: TypeError('argument 1 must be an integer, not _subprocess_handle',)Traceback (most recent call last):  File "c:\python27\lib\site-packages\celery\worker\worker.py", line 203, in start    self.blueprint.start(self)  File "c:\python27\lib\site-packages\celery\bootsteps.py", line 119, in start    step.start(parent)  File "c:\python27\lib\site-packages\celery\bootsteps.py", line 370, in start    return self.obj.start()  File "c:\python27\lib\site-packages\celery\concurrency\base.py", line 131, in start    self.on_start()  File "c:\python27\lib\site-packages\celery\concurrency\prefork.py", line 112, in on_start    **self.options)  File "c:\python27\lib\site-packages\billiard\pool.py", line 1008, in __init__    self._create_worker_process(i)  File "c:\python27\lib\site-packages\billiard\pool.py", line 1117, in _create_worker_process    w.start()  File "c:\python27\lib\site-packages\billiard\process.py", line 122, in start    self._popen = self._Popen(self)  File "c:\python27\lib\site-packages\billiard\context.py", line 383, in _Popen    return Popen(process_obj)  File "c:\python27\lib\site-packages\billiard\popen_spawn_win32.py", line 64, in __init__    _winapi.CloseHandle(ht)TypeError: argument 1 must be an integer, not _subprocess_handleI:\Celery\celery-examples>Traceback (most recent call last):  File "<string>", line 1, in <module>  File "c:\python27\lib\site-packages\billiard\spawn.py", line 159, in spawn_main    new_handle = steal_handle(parent_pid, pipe_handle)  File "c:\python27\lib\site-packages\billiard\reduction.py", line 121, in steal_handle    _winapi.PROCESS_DUP_HANDLE, False, source_pid)WindowsError: [Error 87]

经过搜索发现是因为winsows是不支持celery4的。参照的回答在这https://github.com/celery/celery/issues/3551
所以我机制的将版本降低到3,运行正常。记录下来仅仅是避免其他人在学习中不会再这个小问题上浪费时间。

手动配置路由

运行环境:
  • Win10
  • celery 3.1.18
  • RabbitMQ
1、需求

我们有多种不同的任务,这些任务优先级不同,比如我们有视频上传和压缩任务等,照片压缩上传等任务还有其他不重要的任务。这些任务耗时不同需要使用不同的worker去处理。只是用celery默认的队列就不能满足我们的需求了。
这就需要我们将不同的task路由到不同队列,让不同的worker处理不同种类的task

2、创建队列和交换机

关于交换机和队列可以先看看http://rabbitmq.mr-ping.com/

default_exchange = Exchange('dedfault', type='direct')# 定义一个媒体交换机,类型是直连交换机media_exchange = Exchange('media', type='direct')# 创建三个队列,一个是默认队列,一个是video、一个imageCELERY_QUEUES = (    Queue('default', default_exchange, routing_key='default'),    Queue('videos', media_exchange, routing_key='media.video'),    Queue('images', media_exchange, routing_key='media.image'))# 定义默认队列和默认的交换机routing_keyCELERY_DEFAULT_QUEUE = 'default'CELERY_DEFAULT_EXCHANGE = 'default'CELERY_DEFAULT_ROUTING_KEY = 'default'

3、创建task

from celery import Celeryimport timeapp = Celery()app.config_from_object('celeryconfig')# 视频压缩@app.taskdef video_compress(video_name):    time.sleep(10)    print 'Compressing the:', video_name    return 'success'@app.taskdef video_upload(video_name):    time.sleep(5)    print u'正在上传视频'    return 'success'# 压缩照片@app.taskdef image_compress(image_name):    time.sleep(10)    print 'Compressing the:', image_name    return 'success'# 其他任务@app.taskdef other(str):    time.sleep(10)    print 'Do other things'    return 'success'

我们已经定义了三个队列,现在我们想将操作视频和操作照片的task分别路由到特定的队列。

4、指定路由

CELERY_ROUTES = ({'tasks.image_compress': {                        'queue': 'images',                        'routing_key': 'media.image'                 }},{'tasks.video_upload': {                        'queue': 'videos',                        'routing_key': 'media.video'                 }},{'tasks.video_compress': {                        'queue': 'videos',                        'routing_key': 'media.video'                 }}, )

通过CELERY_ROUTES来为每一个task指定队列,如果有任务到达时,通过任务的名字来让指定的worker来处理。

5、task注册

关于任务的名字可以看看这篇文档http://docs.jinkan.org/docs/celery/userguide/tasks.html
celery可以自动生成名字,如果任务没有注册,就会出错。搜索后发现有人使用下面方法解决。

CELERY_IMPORTS = ("tasks",)

tasks是我保存任务的模块名,这样在创建worker时就可以将任务注册到worker,如下面这样:



图片中的[tasks]下面的几个任务就是我tasks文件中的任务。

6、完整代码

tasks.py

from celery import Celeryimport timeapp = Celery()app.config_from_object('celeryconfig')# 视频压缩@app.taskdef video_compress(video_name):    time.sleep(10)    print 'Compressing the:', video_name    return 'success'@app.taskdef video_upload(video_name):    time.sleep(5)    print u'正在上传视频'    return 'success'# 压缩照片@app.taskdef image_compress(image_name):    time.sleep(10)    print 'Compressing the:', image_name    return 'success'# 其他任务@app.taskdef other(str):    time.sleep(10)    print 'Do other things'    return 'success'

celeryconfig.py

from kombu import Exchange, Queuefrom routers import MyRouter# 配置市区CELERY_TIMEZONE = 'Asia/Shanghai'CELERY_BROKER = 'amqp://localhost'# 定义一个默认交换机default_exchange = Exchange('dedfault', type='direct')# 定义一个媒体交换机media_exchange = Exchange('media', type='direct')# 创建三个队列,一个是默认队列,一个是video、一个imageCELERY_QUEUES = (    Queue('default', default_exchange, routing_key='default'),    Queue('videos', media_exchange, routing_key='media.video'),    Queue('images', media_exchange, routing_key='media.image'))CELERY_DEFAULT_QUEUE = 'default'CELERY_DEFAULT_EXCHANGE = 'default'CELERY_DEFAULT_ROUTING_KEY = 'default'#CELERY_ROUTES = ({'tasks.image_compress': {                        'queue': 'images',                        'routing_key': 'media.image'                 }},{'tasks.video_upload': {                        'queue': 'videos',                        'routing_key': 'media.video'                 }},{'tasks.video_compress': {                        'queue': 'videos',                        'routing_key': 'media.video'                 }}, )# 在出现worker接受到的message出现没有注册的错误时,使用下面一句能解决CELERY_IMPORTS = ("tasks",)

注意在启动worker的时候需要制定队列,需要在保存任务的目录中打开终端启动worker,这个问题我还没有搞明白!

# 启动默认的workercelery worker -Q default --loglevel=info# 启动处理视频的workercelery worker -Q videos --loglevel=info# 启动处理图片的workercelery worker -Q images --loglevel=info

这样我们就可以把不同类的任务路由到不同的worker上处理了。

遇到的坑

在你修改了配置文件需要重启worker时,记得把python shell也关掉重启。
如果调用任务但是worker出现这样的错误

unregistered task of type

就说明你的任务没有注册,需要加上我上面提到的CELERY_IMPORTS = ("tasks",)到配置文件中。
建议将还是自己多动手调试,把芹菜的官方文档看看,里面的东西会让我们看了豁然开朗。

原创粉丝点击