celery

一. celery 简介

Celery 是一个专注于实时处理和任务调度的分布式任务队列, 同时提供操作和维护分布式系统所需的工具.. 所谓任务就是消息, 消息中的有效载荷中包含要执行任务需要的全部数据.

Celery 是一个分布式队列的管理工具, 可以用 Celery 提供的接口快速实现并管理一个分布式的任务队列.

Celery 本身不是任务队列, 是管理分布式任务队列的工具. 它封装了操作常见任务队列的各种操作, 我们使用它可以快速进行任务队列的使用与管理.

1.Celery 特性 :

  • 方便查看定时任务的执行情况, 如 是否成功, 当前状态, 执行任务花费的时间等.
  • 使用功能齐备的管理后台或命令行添加,更新,删除任务.
  • 方便把任务和配置管理相关联.
  • 可选 多进程, Eventlet 和 Gevent 三种模型并发执行.
  • 提供错误处理机制.
  • 提供多种任务原语, 方便实现任务分组,拆分,和调用链.
  • 支持多种消息代理和存储后端.
  • Celery 是语言无关的.它提供了python 等常见语言的接口支持

2.Celery 组件

1. Celery 扮演生产者和消费者的角色,
Celery Beat : 任务调度器. Beat 进程会读取配置文件的内容, 周期性的将配置中到期需要执行的任务发送给任务队列.
Celery Worker : 执行任务的消费者, 通常会在多台服务器运行多个消费者, 提高运行效率.
Broker : 消息代理, 队列本身. 也称为消息中间件. 接受任务生产者发送过来的任务消息, 存进队列再按序分发给任务消费方(通常是消息队列或者数据库).
Producer : 任务生产者. 调用 Celery API , 函数或者装饰器, 而产生任务并交给任务队列处理的都是任务生产者.
Result Backend : 任务处理完成之后保存状态信息和结果, 以供查询.
2. 产生任务的方式 : 发布者发布任务(WEB 应用) 任务调度按期发布任务(定时任务)
3. celery 依赖三个库: 这三个库, 都由 Celery 的开发者开发和维护. billiard : 基于 Python2.7 的 multisuprocessing 而改进的库, 主要用来提高性能和稳定性. librabbitmp : C 语言实现的 Python 客户端, kombu : Celery 自带的用来收发消息的库, 提供了符合 Python 语言习惯的, 使用 AMQP 协议的高级接口.

3.选择消息代理

使用于生产环境的消息代理有 RabbitMQ 和 Redis, 官方推荐 RabbitMQ.

4.Celery 序列化

在客户端和消费者之间传输数据需要 序列化和反序列化. Celery 支出的序列化方案如下所示:

方案说明
pickle pickle 是Python 标准库中的一个模块, 支持 Pyuthon 内置的数据结构, 但他是 Python 的专有协议. Celery 官方不推荐.
json json 支持多种语言, 可用于跨语言方案.
yaml yaml 表达能力更强, 支持的数据类型较 json 多, 但是 python 客户端的性能不如 json
msgpack 二进制的类 json 序列化方案, 但比 json 的数据结构更小, 更快.

二. 安装,配置与简单示例

1.安装

pip install celery, redis, msgpack

Celery 配置参数汇总

配置项说明
CELERY_DEFAULT_QUEUE 默认队列
CELERY_BROKER_URL Broker 地址
CELERY_RESULT_BACKEND 结果存储地址
CELERY_TASK_SERIALIZER 任务序列化方式
CELERY_RESULT_SERIALIZER 任务执行结果序列化方式
CELERY_TASK_RESULT_EXPIRES 任务过期时间
CELERY_ACCEPT_CONTENT 指定任务接受的内容类型(序列化)

详情链接  https://blog.csdn.net/libing_thinking/article/details/78812472

2.初始demo

1.目录

 2.配置文件 

CELERY_BROKER_URL = 'redis://localhost:6379/0'
CELERY_RESULT_BACKEND = 'redis://localhost:6379/1'
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TASK_RESULT_EXPIRES = 60 * 5  # 任务过期时间
CELERY_ACCEPT_CONTENT = ["json"]  # 指定任务接受的内容类型.
setting

3.task 文件

import time
from celery import Celery
from celery_test import celery_setting as CelerySetting

# 创建一个Celery实例,这就是我们用户的应用app
app = Celery('celery_test.s1')
app.config_from_object(CelerySetting)


# 为应用创建任务,my_first_task
@app.task
def task_1(x, y):
    # time.sleep(3)
    return x + y


# celery worker -A celery_test.s1  -n task_1   -c 1  -l info  -P gevent


@app.task
def tsum(numbers):
    return sum(numbers)


# celery worker -A celery_test.s1  -n tsum   -c 1  -l info  -P gevent


@app.task
def mul(x, y):
    return x * y

# celery worker -A celery_test.s1  -n mul   -c 1  -l info  -P gevent

4.调用任务及回调文件

from celery_test.s1 import task_1, tsum, app, mul
from celery import chain, chord, group
from celery.result import AsyncResult

# 1.将任务交给Celery的Worker执行
res = task_1.delay(1, 4)
print(res.id)  # 返回任务ID
print(res.state)  # 状态 如果任务未完成 PENDING
print(res.status)  # 状态 如果任务未完成 none
print(res.successful())  # 是否成功 如果任务未完成 none
print(res.ready())  # 返回布尔值,  任务执行完成, 返回 True, 否则返回 False.
print(res.wait())  # 等待任务完成, 返回任务执行结果
print(res.get())  # 获取任务执行结果
print(res.result)  # 任务执行结果

# 2.查看结果 异步获取任务返回值
async_task = AsyncResult(id="030df922-b7c3-44c9-b137-bf8e3d5e8eb5", app=app)
# 判断异步任务是否执行成功
print(async_task.successful())
if async_task.successful():
    print(async_task.get())  # 获取异步任务的返回值

3.定义任务队列

Celery 默认使用名为 celery 的队列 (可以通过 CELERY_DEFAULT_QUEUE 修改) 来存放任务. 我们可以使用 优先级不同的队列 来确保高优先级的任务优先执行

# coding:utf-8
from __future__ import absolute_import
from celery import Celery
from celery_queue import celery_setting as CelerySetting
from kombu import Queue

app = Celery('celery_queue.s1')
app.config_from_object(CelerySetting)
app.conf.update({
    "CELERY_QUEUES": (  # 设置add队列,绑定routing_key
        Queue('queue_1', routing_key='queue_1', durable=False, max_priority=10),  # durable:持久化   max_priorty:优先级
        Queue('queue_2', routing_key='queue_2', durable=False),
        Queue('queue_3', routing_key='queue_3'),
    ),
    "CELERY_ROUTES": {  # projq.tasks.add这个任务进去add队列并routeing_key为queue_1
        'celery_queue.s1.add': {
            'queue': 'queue_1',
            'routing_key': 'queue_1',
        },
        'celery_queue.s1.tsum': {
            'queue': 'queue_2',
            'routing_key': 'queue_2',
        },
        'celery_queue.s1.mul': {
            'queue': 'queue_3',
            'routing_key': 'queue_3',
        }
    }}
)

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

# celery worker -A celery_queue.s1  -n add   -c 1  -l info  -P gevent

@app.task
def tsum(numbers):
    return sum(numbers)

# celery worker -A celery_queue.s1  -n tsum   -c 1  -l info  -P gevent

@app.task
def mul(x, y):
    return x * y

# celery worker -A celery_queue.s1  -n mul   -c 1  -l info  -P gevent

阅后即焚模式

from kombu import Queue
Queue('transient', routing_key='transient', delivery_mode=1)

4.简单实际应用的例子

1. celery_setting.py

'''
celery配置
'''
task_acks_late = True
worker_prefetch_multiplier = 1
# 限制最大使用内存,限制celery执行10个任务,就销毁重建
worker_max_memory_per_child = 150000
task_reject_on_worker_lost = True
broker_pool_limit = 300
timezone = "Asia/Shanghai"
broker_url = 'amqp://guest:guest@localhost:5672/{vhost}?heartbeat=0'

# 优先级参数必须加
celery_acks_late = True
celeryd_prefetch_multiplier = 1

2. my_task.py

import time
from celery import Celery
from my_celery import celery_setting
from kombu import Exchange, Queue

app = Celery('celery1.my_task')
app.config_from_object(celery_setting)
app.conf.update(
    broker_url="amqp://guest:guest@localhost:5672/{vhost}?heartbeat=0".format(vhost="test")
)
app.conf.task_queues = [
    Queue('priority_test_1', Exchange('default', type='direct'), routing_key='default',
          queue_arguments={'x-max-priority': 10}),
    Queue('priority_test_2', Exchange('default', type='direct'), routing_key='default',
          queue_arguments={'x-max-priority': 10}),
]


@app.task(bind=True,
          queue='priority_test_1',  # 指定队列名
          # max_retries=10,  # 最大重试
          # default_retry_delay=600,  # 重试间隔时间
          autoretry_for=(TypeError, KeyError)  # 指定重试错误
          )
def priority_test_1(self, data):
    # print(self)
    print(data)


@app.task(bind=True,
          queue='priority_test_2',
          )
def priority_test_2(self, data):
    try:
        print(data["1"])
        time.sleep(2)
    except (TypeError, KeyError) as exc:
        raise self.retry(exc=exc, countdown=60 * 5, max_retries=5)

3. add_task.py

from my_celery.my_task import priority_test_1, priority_test_2

# for i in range(1, 10):
#     priority_test_1.delay({"name": f"{i}"})
#     priority_test_2.s({"name": f"{i}"}).apply_async(priority=i)
priority_test_1.delay({"name": f"{123}"})
priority_test_2.delay({"name": f"{123}"})

5.任务调度

 

1. celery_setting.py

# coding:utf-8
from kombu import Queue

BROKER_URL = 'amqp://guest:guest@127.0.0.1:5672/test'  # 使用RabbitMQ作为消息代理
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/0'  # 把任务结果存在了Redis
# CELERY_TASK_SERIALIZER = 'msgpack'  # 任务序列化和反序列化使用msgpack方案
CELERY_TASK_SERIALIZER = 'json'  # 任务序列化和反序列化使用json方案
CELERY_RESULT_SERIALIZER = 'json'  # 读取任务结果一般性能要求不高,所以使用了可读性更好的JSON
CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24  # 任务过期时间,不建议直接写86400,应该让这样的magic数字表述更明显
CELERY_ACCEPT_CONTENT = ['json', 'msgpack']  # 指定接受的内容类型

CELERY_QUEUES = (  # 设置add队列,绑定routing_key
    Queue('queue_1', routing_key='queue_1', durable=False, max_priority=10),
    Queue('queue_2', routing_key='queue_2', durable=False),
    Queue('queue_3', routing_key='queue_3'),
)
CELERY_ROUTES = {  # projq.tasks.add这个任务进去add队列并routeing_key为queue_1
    'crontab_celery.s1.add': {
        'queue': 'queue_1',
        'routing_key': 'queue_1',
    },
    'crontab_celery.s1.tsum': {
        'queue': 'queue_2',
        'routing_key': 'queue_2',
    },
    'crontab_celery.s1.mul': {
        'queue': 'queue_3',
        'routing_key': 'queue_3',
    }
}
from celery.schedules import crontab
from datetime import timedelta

# imports = [
#     'crontab_celery.s1.add',
#     'crontab_celery.s1.tsum'
#     'crontab_celery.s1.mul'
# ]
# schedules定时任务
CELERYBEAT_SCHEDULE = {
    'crontab_celery.s1.add': {
        'task': 'crontab_celery.s1.add',
        'schedule': timedelta(seconds=1),  # 每 3 秒执行一次
        'args': (2, 2)  # 任务函数参数
    },
    'crontab_celery.s1.tsum': {
        'task': 'crontab_celery.s1.tsum',
        # 'schedule': crontab(minute="*/1"),  # 每分执行一次
        'schedule': timedelta(seconds=2),  # 每 3 秒执行一次
        'args': ([1, 2, 3, 4, 5],)  # 任务函数参数
    },
    'crontab_celery.s1.mul': {
        'task': 'crontab_celery.s1.mul',
        'schedule': crontab(minute="*/1"),  # 每分执行一次
        'args': (3, 3)  # 任务函数参数
    }
}

# celery -A  crontab_celery.s1  beat  # 定时任务启动

2.s1.py

# coding:utf-8
from __future__ import absolute_import
from celery import Celery
from crontab_celery import celery_setting

app = Celery('crontab_celery.s1')
app.config_from_object(celery_setting)

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

# celery worker -A crontab_celery.s1  -n add -Q queue_1  -c 1  -l info  -P gevent

@app.task
def tsum(numbers):
    return sum(numbers)

# celery worker -A crontab_celery.s1  -n tsum  -Q queue_2  -c 1  -l info  -P gevent

@app.task
def mul(x, y):
    return x * y

# celery worker -A crontab_celery.s1  -n mul  -Q queue_3  -c 1  -l info  -P gevent

3.命令

# celery -A  crontab_celery.s1  beat      # 使用 Beat 进程自动生成任务
# celery worker -A crontab_celery.s1  -n add -Q queue_1  -c 1  -l info  -P gevent   # 指定任务 add 和队列queue_1
# celery worker -A crontab_celery.s1  -n tsum  -Q queue_2  -c 1  -l info  -P gevent  # 指定 tsum 和 queue_2
# celery worker -A crontab_celery.s1  -n mul  -Q queue_3  -c 1  -l info  -P gevent  # 指定  mul 和queue_3

6.任务绑定, 记录日志, 重试

from celery.utils.log import get_task_logger
logger = get_task_logger(__name__)
 
@app.task(bind=True)
def div(self, x, y):
    logger.info(('Executing task id {0.id}, args: {0.args!r}'
                 'kwargs: {0.kwargs!r}').format(self.request))
    try:
        result = x/y
    except ZeroDivisionError as e:
        raise self.retry(exc=e, countdown=5, max_retries=3)     # 发生 ZeroDivisionError 错误时, 每 5s 重试一次, 最多重试 3 次.
 
    return result
# 当使用 bind=True 参数之后, 函数的参数发生变化, 多出了参数 self, 这这相当于把 div 编程了一个已绑定的方法, 通过 self 可以获得任务的上下文.

7.信号系统 :

信号可以帮助我们了解任务执行情况, 分析任务运行的瓶颈. Celery 支持 7 种信号类型.

  1. 任务信号
    • before_task_publish : 任务发布前
    • after_task_publish : 任务发布后
    • task_prerun : 任务执行前
    • task_postrun : 任务执行后
    • task_retry : 任务重试时
    • task_success : 任务成功时
    • task_failure : 任务失败时
    • task_revoked : 任务被撤销或终止时
  2. 应用信号
  3. Worker 信号
  4. Beat 信号
  5. Eventlet 信号
  6. 日志信号
  7. 命令信号

不同的信号参数格式不同, 具体格式参见官方文档

代码示例 :

from celery.signals import after_task_publish, task_success
@after_task_publish.connect(sender="crontab_celery.s1.add", )
def task_sent_2(sender=None, headers=None, body=None, **kwargs):
    # information about task are located in headers for task messages
    # using the task protocol version 2.
    info = headers if 'task' in headers else body
    print(1111111111)
    print('crontab_celery.s1.add for task id {info[id]}'.format(
        info=info,
    ))


@after_task_publish.connect(sender="crontab_celery.s1.tsum", )
def task_sent_1(sender=None, headers=None, body=None, **kwargs):
    # information about task are located in headers for task messages
    # using the task protocol version 2.
    info = headers if 'task' in headers else body
    print(22222222222)
    print('crontab_celery.s1.tsum for task id {info[id]}'.format(
        info=info,
    ))

重新启动 app时候会发现加任务打印

8.子任务与工作流:

1.子任务

from crontab_celery.s1 import add
from celery import signature


# 1.通过签名的方法传给其他任务, 成为一个子任务
task = add.subtask((2, 2), countdown=10)  # 快捷方式 add.s((2,2), countdown-10)
task.apply_async()

# 2.通过如下方式生成子任务
task = signature('crontab_celery.s1.mul', args=(2, 2), countdown=10)  # 快捷方式 add.s((2,2), countdown-10)
task.apply_async()

# 3.实现偏函数的方式非常有用, 这种方式可以让任务在传递过程中财传入参数.
partial = add.s(2)
partial.apply_async((4,))

2.工作流

子任务支持如下 5 种原语,实现工作流. 原语表示由若干指令组成的, 用于完成一定功能的过程
from crontab_celery.s1 import add, tsum
from celery import chain
from celery import group
from celery import chord

# 1.chain : 调用连, 前面的执行结果, 作为参数传给后面的任务, 直到全部完成, 类似管道.
res = chain(add.s(2, 3), add.s(5), add.s(10))()
res.get()
print(res.get(), "result")  # 20
print(res.parent.get(), "result.parent")  # 10
print(res.parent.parent.get(), "result.parent.parent")  # 5

# 2.管道式:
res = (add.s(2, 2) | add.s(4) | add.s(8))().get()

# 3.group 组任务
print(group(add.s(i, i) for i in range(10))().get())
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

# 4.chord 复合任务函数      任务全部完成时添加一个回调任务
res = chord((add.s(i, i) for i in range(10)), tsum.s())()
print(res.get())  # 90

# 5.map/starmap : 每个参数都作为任务的参数执行一遍, map 的参数只有一个, starmap 支持多个参数.
res = add.starmap(zip(range(10), range(10)))()
print(res)
# 相当于
# @app.task
# def temp():
#     return [add(i, i) for i in range(10)]


# 5.chunks : 将任务分块  按10个分块去处理
add_chunks = add.chunks(zip(range(100), range(100)), 10)
result = add_chunks.delay()
print(result.get())

res = add.chunks(zip(range(100), range(100)), 10)()
print(res.get())
# 使用rabbitmq队列没成功!

9. 其他

   

1.关闭不想要的功能

@app.task(ignore_result=True)   # 关闭任务执行结果.
def func():
    pass
 
CELERY_DISABLE_RATE_LIMITS=True     # 关闭限速

2.根据任务状态执行不同操作

# s1.py
class MyTask(Task):
 
    def on_success(self, retval, task_id, args, kwargs):
        print 'task done: {0}'.format(retval)
        return super(MyTask, self).on_success(retval, task_id, args, kwargs)
 
    def on_failure(self, exc, task_id, args, kwargs, einfo):
        print 'task fail, reason: {0}'.format(exc)
        return super(MyTask, self).on_failure(exc, task_id, args, kwargs, einfo)
 
# 正确函数, 执行 MyTask.on_success() :
@app.task(base=MyTask)
def add(x, y):
    return x + y
 
# 错误函数, 执行 MyTask.on_failure() : 
@app.task  #普通函数装饰为 celery task
def add(x, y):
    raise KeyError
    return x + y

10. 管理命令

任务状态回调 :

参数说明
PENDING 任务等待中
STARTED 任务已开始
SUCCESS 任务执行成功
FAILURE 任务执行失败
RETRY 任务将被重试
REVOKED 任务取消
PROGRESS 任务进行中

1.普通启动命令

 celery -A proj worker -l info

2.使用deamon方式 multi

$ celery multi start web -A proj -l info --pidfile=/path/to/celery_%n.pid --logfile=/path/to/celery_%n.log 
 
# web 是对项目启动的标识, 
# %n 是对节点的格式化用法.
    %n : 只包含主机名
    %h : 包含域名的主机
    %d : 只包含域名
    %i : Prefork 类型的进程索引,如果是主进程, 则为 0.
    %I : 带分隔符的 Prefork 类型的进程索引. 假设主进程为 worker1, 那么进程池的第一个进程则为 worker1-1

3.常用 multi 相关命令:

$ celery multi show web     # 查看 web 启动时的命令
$ celery multi names web    # 获取 web 的节点名字
$ celery multi stop web     # 停止 web 进程
$ celery multi restart web  # 重启 web
$ celery multi kill web     # 杀掉 web 进程

4.常用监控和管理命令

# shell : 交互时环境, 内置了 Celery 应用实例和全部已注册的任务, 支持 默认解释器,IPython,BPython
# celery shell -A proj

# result : 通过 task_id 在命令行获得任务执行结果
# celery -A proj result TASK_ID

# inspect active : 列出当前正在执行的任务
# celery -A proj inspect active

# inspect stats : 列出 worker 的统计数据, 常用来查看配置是否正确以及系统的使用情况.
# celery -A proj inspect stats

5.Flower web 监控工具

查看任务历史,任务具体参数,开始时间等信息;
提供图表和统计数据
实现全面的远程控制功能, 包括但不限于 撤销/终止任务, 关闭重启 worker, 查看正在运行任务
提供一个 HTTP API , 方便集成.

6.Flower 的 supervisor 管理配置文件:

[program:flower]
command=/opt/PyProjects/venv/bin/flower -A celery_worker:celery --broker="redis://localhost:6379/2" --address=0.0.0.0 --port=5555 
directory=/opt/PyProjects/app
autostart=true
autorestart=true
startretries=3 
user=derby
stdout_logfile=/var/logs/%(program_name)s.log
stdout_logfile_maxbytes=50MB
stdout_logfile_backups=30
stderr_logfile=/var/logs/%(program_name)s-error.log
stderr_logfile_maxbytes=50MB
stderr_logfile_backups=3

7.Celery 自带的事件监控工具显示任务历史等信息

celery -A proj event
** 需要把 CELERY_SEND_TASK_SEND_EVENT = True 设置, 才可以获取时间

8.使用自动扩展

celery -A proj worker -l info --autoscale=6,3     # 平时保持 3 个进程, 最大时可以达到 6 个.

9.celery命令汇总

celery --help


Usage: celery <command> [options]
用法:celery 命令 选项

Show help screen and exit.
显示帮助信息并退出。

Options(选项):
  -A APP, --app=APP     app instance to use (e.g. module.attr_name)-本次操作使用的App实例。
  -b BROKER, --broker=BROKER    -消息代理(服务器),用于传递数据的URL。
                        url to broker.  default is 'amqp://guest@localhost//'
  --loader=LOADER       name of custom loader class to use. - 自定义载入类的名称。
  --config=CONFIG       Name of the configuration module-配置模块的名称。
  --workdir=WORKING_DIRECTORY- 工作目录。
                        Optional directory to change to after detaching.
  -C, --no-color       -非彩色显示。
  -q, --quiet              -静默执行。
  --version             show program's version number and exit-显示版本号。
  -h, --help            show this help message and exit -显示本帮助。

---- -- - - ---- Commands-命令列表 -------------- --- ------------

+ Main-主要命令:
|    celery worker
|    celery events
|    celery beat
|    celery shell
|    celery multi
|    celery amqp

+ Remote Control-远程控制:
|    celery status
 
|    celery inspect --help
|    celery inspect active
|    celery inspect active_queues
|    celery inspect clock
|    celery inspect conf None
|    celery inspect memdump
|    celery inspect memsample
|    celery inspect objgraph None
|    celery inspect ping
|    celery inspect registered
|    celery inspect report
|    celery inspect reserved
|    celery inspect revoked
|    celery inspect scheduled
|    celery inspect stats
 
|    celery control --help
|    celery control add_consumer <queue> [exchange [type [routing_key]]]
|    celery control autoscale [max] [min]
|    celery control cancel_consumer <queue>
|    celery control disable_events
|    celery control enable_events
|    celery control pool_grow [N=1]
|    celery control pool_shrink [N=1]
|    celery control rate_limit <task_name> <rate_limit> (e.g. 5/s | 5/m | 5/h)>
|    celery control time_limit <task_name> <soft_secs> [hard_secs]

+ Utils-使用命令:
|    celery purge
|    celery list
|    celery migrate
|    celery call
|    celery result
|    celery report

+ Extensions:
|    celery flower
---- -- - - --------- -- - -------------- --- ------------

Type 'celery <command> --help' for help using a specific command.

键入'celery <command> --help'可以获得指定的命令的更详细的帮助信息。
View Code

11. 在falsk中使用

Flask 文档: 基于 Celery 的后台任务
在 Flask 中使用 Celery

 

posted @ 2020-05-12 18:17  洛丶丶丶  阅读(355)  评论(3编辑  收藏  举报