celery实例

1>http://windrocblog.sinaapp.com/?p=1585

Celery入门

 

Celery是一个用Python写的分布式任务队列系统。

我想用Celery作任务调度器,并异步处理用户交互任务。

开始使用

介绍Celery最简单的应用,包括:

  • 选择并安装消息代理(broker)
  • 安装Celery并创建任务
  • 运行worker并调用任务
  • 跟踪任务执行状态,检查任务返回值

选择代理

Celery需要一个用来发送和接受消息的消息代理(message broker),有多种选择,官方推荐使用RabbitMQ。

RabbitMQ

安装RabbitMQ

安装信息中显示

表示RabbitMQ服务器已经运行。

其它选项暂不考虑。

安装Celery

Celery是Python包,所以可以使用pip安装。

应用

需要一个Celery示例,叫做celery应用或简称celery app。

最简单的一个示例:tasks.py

这里的broker使用本机的RabbitMQ。

运行Celery worker服务器

前台启动worker服务器

界面显示如下:

从信息中可以看到当前配置、消息队列、任务列表等。

还可以后台启动,守护进程启动,详情参见官方文档。

执行任务

使用 delay()方法异步执行任务

可以在之前启动的服务窗口中看到如下的日志信息:

客户端返回一个AsyncResult实例,但我们需要配置result backend才能使用它的一些高级功能。

保存结果

如果想跟踪任务状态,Celery需要存储或发送状态到某地方,这就是Result backend,同样有多种选项。

创建Celery对象时设置backend属性,或者使用配置对象设置CELERY_RESULT_BACKEND。

修改创建app的代码:

重新启动worker服务,屏幕显示:

config下的results属性现在为amqp,表示使用RabbitMQ存储结果。

可以使用ready判断任务是否运行结束,使用get获取返回结果。

当出现异常时,get会重新抛出异常:

可以改变该行为

同样可以获取原来的异常

配置

设置app或者使用专用的配置对象,类似flask config,是一个字典,并且某些属性值单独列出。

直接设置

更新字典

从文件中读取

下面展示一下配置文件的强大力量:

创建celeryconfig.py文件

限制每分钟完成10个任务

如果使用RabbitMQ或Redis作为Broker,可以使用命令在线设置:

服务端显示

下一步

在应用中使用Celery

项目结构

文件

proj/celery.py

broker: 代理
backend: 指定保存结果后端
可以设置没有返回结果,在任务中加入ignore_result属性

include:第一个例子中没有include,表示worker启动时需要导入的模块列表

proj/tasks.py

启动worker

得到类似下面的信息:

transport: 表示broker? 可以用-b属性设置另外一个
concurrency:工作进程个数,默认为CPU核心数,使用-c指定
queues:获取任务的队列

关闭worker

Ctrl+C

后台运行

使用celery multi命令

启动 start

重启restart

停止stop

等待停止

命令后续参数必须一致才可以控制worker

默认在当前目录创建pid和log文件,可以指定位置

关于–app参数

module.path:attribute

可以简写:

–app=proj
搜索路径
proj.app
proj.celery
proj中是否有Celery应用,或者找子模块proj.celery

proj.celery.app
proj.celery.celery
proj.celery中是否有Celery应用

小项目可以用proj:app
大项目可以用proj.celery:app

调用任务

delay()函数是对apply_async()函数的封装,下面的两条语句等价。

applay_async函数可以设置选项:

任务将被发送到lopri队列,最早在10秒后执行。

直接调用则直接运行

返回结果

每个任务有一个UUID标识

delay和applay_async方法都返回一个AsyncResult 对象,但必须制定一个result backend,否则不会保存。

result backend不用来控制tasks和workers,Cleery使用dedicated event messages

获取值

任务id号

get默认会传递任意错误:

不希望传递(propagate)错误,需要制定propagate参数

检查任务是否运行成功

任务状态

默认任务状态流程
PENDING -> STARTED -> SUCCESS

PENDING状态是默认任务的状态,包括未知任务

任务重试可能的状态
PENDING -> STARTED -> RETRY -> STARTED -> RETRY -> STARTED -> SUCCESS

Canvas: 设计工作流

创建子任务subtasks

使用subtask创建子任务

也有简写形式

子任务调用与普通任务相同,可以使用delay和apply_async。
>>> res = s1.delay()
>>> res.get()
4

但可以创建partials,不完整的signatures

关键词参数也可以添加,并后续更新

subtasks调用语法

原语

本身为subtask,可以组合成复杂的工作流

group
chain
chord
map
starmap
chunks

示例

Groups

一组任务并行执行

Partial group

Chains

连续执行

也可以写成这样

patial chain

Chords
一组带回调函数的任务

group连接到另一个任务可以自动转换为chord

Routing 路由

支持所有AMQP的路由特性,并提供发送给指定名字队列的简单路由。

CELERY_ROUTES属性

添加简单路由

设置消费队列的worker,使用-Q指定队列,逗号分隔多个队列

显示

执行任务时设置queue属性

远程控制

RabbitMQ,Redis和MongoDB可以在线控制worker

查看哪些worker正在工作

指定workers响应,使用–destination选项,用逗号分隔

celery inspect 命令不修改worker的任何信息

celery control 修改woker

例如:
开启事件消息

服务器端响应

开启事件后,可以查看worker在干什么

或者直接开启curses interface

关闭事件

celery status 查看状态

时区

默认使用UTC

可用CELERY_TIMEZONE配置

优化

默认没有优化

使用RabbitMQ则需要安装librabbitmq模块: pip install librabbitmq

 

 

 

 

 

 

 

 

 

2.

认识

这里有几个概念,task、worker、broker。
顾名思义,task 就是老板交给你的各种任务,worker 就是你手下干活的人员。

那什么是 Broker 呢?

老板给你下发任务时,你需要 把它记下来, 这个它 可以是你随身携带的本子,也可以是 电脑里地记事本或者excel,或者是你的 任何时间管理工具。

Broker  则是 Celery 记录task的地方。
作为一个任务管理者的你,将老板(前端程序)发给你的 安排的工作(Task) 记录到你的本子(Broker)里。接下来,你就安排你手下的IT程序猿们(Worker),都到你的本子(Broker)里来取走工作(Task)

1. broker为rabbitmq

#tasks.py

复制代码
from celery import Celery

app = Celery('tasks', broker='amqp://admin:admin@localhost:5672')

@app.task
def add(x, y):
    return x + y
复制代码

启动

celery -A tasks worker --loglevel=info

运行

复制代码
>>> from tasks import add
>>> add(1, 3)
4
>>> add.delay(1,3)
<AsyncResult: 07614cef-f314-4c7b-a33f-92c080cadb83>
>>> 
复制代码

:delay是使用异步的方式,会压入到消息队列。否则,不会使用消息队列。

文件名为tasks.py,则其中代码app = Celery('tasks', broker=),Celery第一个参数为工程名,启动时也是celery -A tasks worker --loglevel=info

对比

:投入到指定的队列用:add.delay(1, 3, queue='queue_add1') 

test_2.py

复制代码
from celery import Celery

app = Celery('proj', broker='amqp://admin:admin@localhost:5672', include='test_2')

@app.task
def add(x, y):
    return x + y
复制代码

2. 以python+文件名的方式启动

例1:

#test.py

复制代码
from celery import Celery
import time
app = Celery('test', backend='amqp', broker='amqp://admin:admin@localhost:5672')

@app.task
def add(x, y):
    print "------>"
    time.sleep(5)
    print "<--------------"
    return x + y

if __name__ == "__main__":
    app.start()
复制代码

启动

python test.py worker 

celery默认启动的worker数为内核个数,如果指定启动个数,用参数-c,例

python test.py worker -c 2

例2:

#test.py

复制代码
from celery import Celery
import time
app = Celery('test', backend='amqp', broker='amqp://admin:admin@localhost:5672')

@app.task
def add(x, y):
    print "------>"
    time.sleep(2)
    print "<--------------"
    return x + y

if __name__ == "__main__":
    app.start()
复制代码

#eg.py

复制代码
from test import *
import time

rev = []
for i in range(3):
    rev.append(add.delay(1,3))

print "len rev:", len(rev)
while 1:
    tag = 1
    for key in rev:
        if not key.ready():
            tag = 0
            time.sleep(1)
            print "sleep 1"
    if tag:
        break
print "_____________________>"
复制代码

3. broker为redis

#test_redis.py

复制代码
from celery import Celery
import time
#app = Celery('test_redis', backend='amqp', broker='redis://100.69.201.116:7000')
app = Celery('test_redis', backend='redis', broker='redis://100.69.201.116:7000')

@app.task
def add(x, y):
    print "------>"
    time.sleep(5)
    print "<--------------"
    return x + y

if __name__ == "__main__":
    app.start()
复制代码

启动

python test_redis.py worker -c 2

测试

复制代码
from celery import group
from test_redis import *
g = group(add.s(2, 3)).apply_async()
g = group(add.s(2, 3)).apply_async()
g = group(add.s(2, 3)).apply_async()
g = group(add.s(2, 3)).apply_async()
g = group(add.s(2, 3)).apply_async()
for ret in g.get():
    print ret
print "end-----------------------------------"
复制代码

结果

5
end-----------------------------------

4. 两个队列(redis)

#test_redis.py

复制代码
from celery import Celery
import time
#app = Celery('test_redis', backend='amqp', broker='redis://100.69.201.116:7000')
app = Celery('test_redis', backend='redis', broker='redis://100.69.201.116:7000')

@app.task
def add(x, y):
    print "------>"
    time.sleep(5)
    print "<--------------"
    return x + y

if __name__ == "__main__":
    app.start()
复制代码

#test_redis_2.py

复制代码
from celery import Celery
import time
#app = Celery('test_redis', backend='amqp', broker='redis://100.69.201.116:7000')
app = Celery('test_redis_2', backend='redis', broker='redis://100.69.201.116:7001')

@app.task
def add_2(x, y):
    print "=======>"
    time.sleep(5)
    print "<================="
    return x + y

if __name__ == "__main__":
    app.start()
复制代码

测试

复制代码
from celery import group
from test_redis import *
from test_redis_2 import *
ll = [(1,2), (3,4), (5,6)]
g = group(add.s(key[0], key[1]) for key in ll).apply_async()
for ret in g.get():
    print ret
print "end redis_1 -----------------------------------"

ll = [(1,2), (3,4), (5,6)]
g = group(add_2.s(key[0], key[1]) for key in ll).apply_async()
for ret in g.get():
    print ":", ret
print "end redis_2 -----------------------------------"
复制代码

结果

复制代码
3
7
11
end redis_1 -----------------------------------
: 3
: 7
: 11
end redis_2 -----------------------------------
复制代码

5. 两个队列(同一个rabbitmq)

注释:需要提前设置下队列

##例1

#test.py

复制代码
from celery import Celery
import time
app = Celery('test', backend='amqp', broker='amqp://admin:admin@localhost:5672//')

@app.task
def add(x, y):
    print "------>"
    time.sleep(5)
    print "<--------------"
    return x + y

if __name__ == "__main__":
    app.start()
复制代码

 

#test_2.py

复制代码
from celery import Celery
import time
app = Celery('test_2', backend='amqp', broker='amqp://admin:admin@localhost:5672//hwzh')

@app.task
def add_2(x, y):
    print "=====>"
    time.sleep(5)
    print "<=========="
    return x + y

if __name__ == "__main__":
    app.start()
复制代码

测试

复制代码
from celery import group
from test import *
from test_2 import *

ll = [(1,2), (3,4), (7,8)]
g = group(add.s(key[0], key[1]) for key in ll).apply_async()
for ret in g.get():
    print ret

ll = [(1,2), (3,4), (7,8)]
g = group(add_2.s(key[0], key[1]) for key in ll).apply_async()
for ret in g.get():
    print ret
复制代码

结果

复制代码
3
7
15
3
7
15
复制代码

 

##例2

#test.py

复制代码
from celery import Celery
import time
app = Celery('test', backend='amqp', broker='amqp://admin:admin@localhost:5672//mq4')

@app.task
def add(x, y):
    print "------>"
    time.sleep(2)
    print "<--------------"
    return x + y

@app.task
def sum(x, y):
    print "------>"
    time.sleep(2)
    print "<--------------"
    return x + y

if __name__ == "__main__":
    app.start()
复制代码

#eg2.py

复制代码
from test import *
import time

rev = []
for i in range(3):
    rev.append(add.delay(1,3))

for i in range(3):
    rev.append(sum.delay(1,3))

print "len rev:", len(rev)
while 1:
    tag = 1
    for key in rev:
        if not key.ready():
            tag = 0
            time.sleep(1)
            print "sleep 1"
    if tag:
        break
print "_____________________>"
复制代码

6. 保存结果

复制代码
from celery import Celery

app = Celery('tasks', backend='amqp', broker='amqp://admin:admin@localhost')

@app.task
def add(x, y): 
    return x + y
复制代码

启动

celery -A tasks_1 worker --loglevel=info

与前例不同:

- ** ---------- [config]
- ** ---------- .> app: tasks:0x7f8057931810
- ** ---------- .> transport: amqp://admin:**@localhost:5672//
- ** ---------- .> results: amqp

运行

复制代码
>>> from tasks_1 import add
>>> result = add.delay(1, 3)
>>> result.ready()
True
>>> result.get()
4
复制代码

7. 多个队列

复制代码
from celery import Celery
from kombu import Exchange, Queue
BROKER_URL = 'amqp://admin:admin@localhost//'
app = Celery('tasks', backend='amqp',broker=BROKER_URL)
app.conf.update(
     CELERY_ROUTES={
          "add1":{"queue":"queue_add1"},
          "add2":{"queue":"queue_add2"},
          "add3":{"queue":"queue_add3"},
          "add4":{"queue":"queue_add4"},
        },
)
@app.task
def add1(x, y):
     return x + y

@app.task
def add2(x, y):
     return x + y

@app.task
def add3(x, y):
     return x + y

@app.task
def add4(x, y):
     return x + y
复制代码

8. 消息路由

文件:tasks.py

复制代码
from celery import Celery, platforms
import time
import os

app = Celery('proj', broker='amqp://admin:admin@ip:5672',
             include=['tasks']
             )
app.conf.update(
    CELERY_ROUTES={
        'tasks.fun_1': {
            'queue': "q_1" 
        },
        'tasks.fun_2': {
            'queue': "q_2"
        }
    }
)
platforms.C_FORCE_ROOT = True 

@app.task
def fun_1(n):
    print "(((((((((((((((func_1", n
    return 1

@app.task
def fun_2(n):
    print n, ")))))))))))))))"
    return 2

if __name__ == "__main__":
    app.start()
复制代码

启动

python tasks.py worker -c 2 -Q q_1
python tasks.py worker -c 2 -Q q_2

两个消息队列:q_1, q_2,调用示例

复制代码
>>> from tasks import *
>>> fun_1(1)
(((((((((((((((func_1 1
1
>>> fun_1.delay(1)
<AsyncResult: 528a2ad1-bc16-4bdc-beff-cd166fe3e885>
>>> fun_2.delay(2)
<AsyncResult: ee5881eb-b384-4a39-ba00-08aa8ee53504>
复制代码

9. woker内启多进程

#tasks.py

复制代码
from celery import Celery
import time
import multiprocessing as mp

app = Celery('proj', broker='amqp://admin:admin@ip:5672', include="tasks")

def test_func(i):
    print "beg...:", i
    time.sleep(5)
    print "....end:", i
    return i * 5

@app.task
def fun_1(n):
    curr_proc = mp.current_process()
    curr_proc.daemon = False
    p = mp.Pool(mp.cpu_count())
    curr_proc.daemon = True
    for i in range(n):
        p.apply_async(test_func, args=(i,))
    p.close()
    p.join()
    return 1

if __name__ == "__main__":
    app.start()
复制代码

说明

直接启动多进程是肯定不可以的,因为是守候进程(curr_proc.daemon=True),所以启多进程之前主动设置为非守候进程:curr_proc.daemon=False,启动了以后再设为守候进程

#tasks_callback.py

复制代码
from celery import Celery
import time
import multiprocessing as mp

app = Celery('proj', broker='amqp://admin:admin@ip:5672', include="tasks_callback")
rev = []
def test_func(i):
    print "beg...:", i
    time.sleep(5)
    print "....end:", i
    return i * 5

def callback_log(rev_val):
    rev.append(rev_val)

@app.task
def fun_1(n):
    print "before rev:", rev
    curr_proc = mp.current_process()
    curr_proc.daemon = False
    p = mp.Pool(mp.cpu_count())
    curr_proc.daemon = True
    for i in range(n):
        p.apply_async(test_func, args=(i,), callback=callback_log)
    p.close()
    p.join()
    print "after rev:", rev
    return 1

if __name__ == "__main__":
    app.start()
复制代码

10. 常用参数配置

1. CELERYD_PREFETCH_MULTIPLIER

同时预取得消息个数,比如如果CELERYD_PREFETCH_MULTIPLIER=2,那么如果现在对于1个worker,有一个状态是STARTED, 那么可以有2个处于RECEVED状态(如果有的话),这样就避免了如果消息很多全部分下取,后起来的worker领不到消息的尴尬。

参考代码

复制代码
from celery import Celery, platforms
import time
import os

app = Celery('proj', broker='amqp://admin:admin@localhost:5672',
             include=['tasks']
             )
app.conf.update(
    CELERYD_PREFETCH_MULTIPLIER=2,
    CELERY_ROUTES={
        'tasks.fun_1': {
            'queue': "q_1"
        },
        'tasks.fun_2': {
            'queue': "q_2"
        }
    }
)
platforms.C_FORCE_ROOT = True

@app.task
def fun_1(n):
    print "(((((((((((((((func_1", n
    time.sleep(20)
    return 1

@app.task
def fun_2(n):
    print n, ")))))))))))))))"
    return 2
复制代码

调用

复制代码
>>> from tasks import *
>>> fun_1.delay(3)
<AsyncResult: 609f2216-6785-409e-9f6f-85ae3fcce084>
>>> fun_1.delay(3)
<AsyncResult: 0230b8bd-b237-40ef-bc73-88929f8f8290>
>>> fun_1.delay(3)
<AsyncResult: 8fce172a-93c9-41f8-8c08-377a4363389c>
>>> fun_1.delay(3)
复制代码

 

posted on 2018-09-30 17:16  myworldworld  阅读(259)  评论(0)    收藏  举报

导航