简单介绍:
参考博客:
celery: https://www.cnblogs.com/cwp-bg/p/8759638.html
celery: https://www.cnblogs.com/yangjian319/p/9097171.html
flower GET/POST: https://flower.readthedocs.io/en/latest/api.html
flower官网: https://flower.readthedocs.io/en/latest/index.html
一、 Celery
1. 介绍
celery是一个分布式的任务调度模块/异步任务队列,可以支持多台不同计算机执行不同的任务或者相同的任务,一个Celery包含以下几个模块:
- 任务模块Task: 包含定时任务和异步任务。异步任务在业务逻辑中被触发并发往任务队列,而定时任务由celery beat进程周期性的将任务发往任务队列
- 消息中间件broker:接收任务生产者发来的任务,将任务存入队列,队列一般使用rabbitmq
- 任务执行单元worker:是执行任务的处理单元,它实时监控消息队列,获取队列中调度的任务,并执行它
- 任务结果存储backend:用于存储任务的执行结果,以供查询,一般用redis
可以有不同的消息队列(message queue),不同的消息可以指定发送给不同的message queue,而这是通过exchange实现的,发送消息到消息队列,可以指定routing_key,exchange通过routing_key把消息路由(routes)到不同的消息队列。exchange对应一个queue,每个queue对应每个worker。
2. 配置
保存到配置文件tasks/config.py中
from kombu import Exchange, Queue
import os
# broker:任务队列的中间人;
# backend:任务执行结果的存储;
rabbitmq_ip = os.environ.get('RABBITMQ_IP','localhost')
user = os.environ.get('USER','user')
password = os.environ.get('PASSWORD','password')
redis_ip = os.environ.get('REDIS_ID','localhost')
BROKER_URL = 'pyamqp://{user}:{password}@{IP}:5672//'.format(IP=rabbitmq_ip,user=user,password=password)
CELERY_RESULT_BACKEND = 'redis://{IP}:6379/0'.format(IP=redis_ip)
BROKER_TRANSPORT_OPTIONS= {'visibility_timeout': 86400}
CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24 #任务超时时间
CELERY_TIMEZONE = 'Asia/Shanghai'
CELERY_IMPORTS = ('tasks.operations') #使用celery的项目路径,在tasks路径下的operations.py
CELERY_QUEUES = (
Queue(
"for_task_add",
Exchange("for_task_add"),
routing_key="for_task_add"), ###这个逗号千万不能去掉!!!
)
CELERY_ROUTES = {
'tasks.add.add': {
"queue": "for_task_add",
"routing_key": "for_task_add"
}
}
tasks/__init__.py
from celery import Celery
app = Celery('tasks')
app.config_from_object('tasks.config') #引入配置文件
tasks/operations.py
from tasks import app
@app.task
def add(x,y):
return x + y
app.send_task("tasks.add",args=[3,4])
add.delay(3,4)
add.apply_async(args=[3,4],retry=True,retry_policy={'interval_max':0.2})
##retry: 如果任务失败,是否重试
##retry_policy:重试策略
##interval_max:重试间隔最大的秒数
-
app.task 装饰add函数成了一个Task实例,add.delay函数将task函数实例序列化之后,通过librabbitmq库的方法将任务发到rabbitmq
-
该过程创建一个名字为celery的exchange交换机,类型为direct(直连交换机);创建一个名为celery的queue,队列和交换机使用路由键celery绑定;
-
打开rabbitmq管理后台,可以看到有一条消息已经在celery队列中;
记住:当有多个装饰器的时候,app.task一定要在最外层;
#task方法参数
name:可以显式指定任务的名字;默认是模块的命名空间中本函数的名字。
serializer:指定本任务的序列化的方法;
bind:一个bool值,设置是否绑定一个task的实例,如果绑定,task实例会作为参数传递到任务方法中,可以访问task实例的所有的属性,即前面反序列化中那些属性
base:定义任务的基类,可以以此来定义回调函数,默认是Task类,我们也可以定义自己的Task类
default_retry_delay:设置该任务重试的延迟时间,当任务执行失败后,会自动重试,单位是秒,默认3分钟; autoretry_for:设置在特定异常时重试任务,默认False即不重试;
retry_backoff:默认False,设置重试时的延迟时间间隔策略;
retry_backoff_max:设置最大延迟重试时间,默认10分钟,如果失败则不再重试;
retry_jitter:默认True,即引入抖动,避免重试任务集中执行;
调用异步任务
task.delay():这是apply_async方法的别名,但接受的参数较为简单;
task.apply_async(args=[arg1, arg2], kwargs={key:value, key:value}):可以接受复杂的参数
send_task():可以发送未被注册的异步任务,即没有被celery.task装饰的任务;
二、 flower
可以通过界面化的方式进行管控任务的执行状态和查看任务执行结果
以上部分,用docker-compose配置
docker-compose.yml
version: '2'
services:
redis:
image: redis:5.0.5
ports:
- 6379:6379
rabbitmq:
image: 'rabbitmq:3-management'
environment:
- RABBITMQ_DEFAULT_USER=user
- RABBITMQ_DEFAULT_PASS=xxx
ports:
- 5672:5672
- 15672:15672
flower:
build: .
image: celery_test:0.0.2
working_dir: /app
command: flower --address=0.0.0.0 --port=5555 --broker_api=http://user:xxx@rabbitmq:15672/api/ -A tasks
environment:
- CELERY_BROKER_URL=amqp://user:xxx@rabbitmq:5672//
- RABBITMQ_IP=rabbitmq
- USER=user
- PASSWORD=xxx
- REDIS_ID=redis
ports:
- 10090:5555
links:
- rabbitmq
- redis
depends_on:
- rabbitmq
- add_test
- redis
#volumes:
# - ".:/app"
add_test:
build: .
image: add_test:0.0.2
command: celery -A tasks worker -c 10 --loglevel=info
working_dir: /app
environment:
- RABBITMQ_IP=rabbitmq
- USER=user
- PASSWORD=xxx
- REDIS_ID=redis
links:
- rabbitmq
- redis
depends_on:
- redis
- rabbitmq
volumes:
- "/test:/test"
可以在当前目录下,用dockerfile来部署项目
docker-compose build
docker-compose up
可以用以下测试任务,参考上面第二个链接
curl -X POST -d '{"args":[100,200]}' localhost:10090/api/task/apply/tasks.operation.add #会等待运行结束
curl -X POST -d '{"args":[100,200]}' localhost:10090/api/task/async-apply/tasks.operation.add #异步调用
可以用浏览器打开http://IP:10090/,通过可视化界面查看任务状态
进阶
1. 有类怎么办
import celery
class MyTask(celery.Task):
# 任务失败时执行
def on_failure(self, exc, task_id, args, kwargs, einfo):
print('{0!r} failed: {1!r}'.format(task_id, exc))
# 任务成功时执行
def on_success(self, retval, task_id, args, kwargs):
pass
# 任务重试时执行
def on_retry(self, exc, task_id, args, kwargs, einfo):
pass
@task(base=MyTask)
def add(x, y):
raise KeyError()
#方法相关的参数
exc:失败时的错误的类型;
task_id:任务的id;
args:任务函数的参数;
kwargs:键值对参数;
einfo:失败或重试时的异常详细信息;
retval:任务成功执行的返回值;
2. rabbitmq怎么实现同步
待补充
3. 定时操作怎么做?
只需要设置celery对象的CELERYBEAT_SCHEDULE属性就可以
from kombu import Exchange, Queue
import os
from datetime import timedelta
# broker:任务队列的中间人;
# backend:任务执行结果的存储;
rabbitmq_ip = os.environ.get('RABBITMQ_IP','localhost')
user = os.environ.get('USER','user')
password = os.environ.get('PASSWORD','password')
redis_ip = os.environ.get('REDIS_ID','localhost')
BROKER_URL = 'pyamqp://{user}:{password}@{IP}:5672//'.format(IP=rabbitmq_ip,user=user,password=password)
CELERY_RESULT_BACKEND = 'redis://{IP}:6379/0'.format(IP=redis_ip)
BROKER_TRANSPORT_OPTIONS= {'visibility_timeout': 86400}
CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24 #任务超时时间
CELERY_TIMEZONE = 'Asia/Shanghai'
CELERY_IMPORTS = ('tasks.operations') #使用celery的项目路径,在tasks路径下的operations.py
CELERYBEAT_SCHEDULE = {
'add-every-30-seconds':{
'task':'tasks.operations.add',
'schedule':timedalta(seconds=30), #或者'schedule':30
'args':(16,16)
}
}
一旦使用了scheduler,启动celery的时候需要加上-B celery -A tasks worker -B -c 10 --loglevel=info,设置多个定时任务,可以celery -A tasks beat
4. 为什么用rabbitmq, 不用redis做broker
可以参考网址rabbitmq与redist队列对比
可靠性:
- redis :没有相应的机制保证消息的可靠消费,如果发布者发布一条消息,而没有对应的订阅者的话,这条消息将丢失,不会存在内存中;
- rabbitMQ:具有消息消费确认机制,如果发布一条消息,还没有消费者消费该队列,那么这条消息将一直存放在队列中,直到有消费者消费了该条消息,以此可以保证消息的可靠消费
实时性:
- redis:实时性高,redis作为高效的缓存服务器,所有数据都存在在服务器中,所以它具有更高的实时性
消费者负载均衡:
- redis发布订阅模式,一个队列可以被多个消费者同时订阅,当有消息到达时,会将该消息依次发送给每个订阅者;
- rabbitMQ队列可以被多个消费者同时监控消费,但是每一条消息只能被消费一次,由于rabbitMQ的消费确认机制,因此它能够根据消费者的消费能力而调整它的负载;
持久性:
- redis:redis的持久化是针对于整个redis缓存的内容,它有RDB和AOF两种持久化方式,可以将整个redis实例持久化到磁盘,以此来做数据备份,防止异常情况下导致数据丢失。
- rabbitMQ:队列,消息都可以选择性持久化,持久化粒度更小,更灵活;
队列监控:
- rabbitMQ实现了后台监控平台,可以在该平台上看到所有创建的队列的详细情况,良好的后台管理平台可以方便我们更好的使用;
- redis没有所谓的监控平台。
总结:
- redis: 轻量级,低延迟,高并发,低可靠性;
- rabbitMQ:重量级,高可靠,异步,不保证实时;
- rabbitMQ是一个专门的AMQP协议队列,他的优势就在于提供可靠的队列服务,并且可做到异步,而redis主要是用于缓存的,redis的发布订阅模块,可用于实现及时性,且可靠性低的功能。
5. 错误重试
方法一:
@app.task(bind=True)
def add(self, x, y):
try:
return x+y
except Exception as e:
raise self.retry(exc=e,countdown=5,max_retries=5)
方法二:
@app.task(autoretry_for=(Exception,), retry_kwargs={'max_retries': 3, 'countdown': 5})
def test_func():
viewutils.test_func()
方法三:
重写task
6. 如果有多个任务怎么定义串行还是并行
单 worker 可以开 n 个进程进行工作,一个 worker 挂了往往所有进程都会挂掉
多 worker 假设为 m 个,可以理解为分布式,为了防止一个 worker 挂了(或者性能不足等原因),导致无法工作
那么能够并发处理的任务数量理论上为 m * n
celery 里面的-c 参数指定的是并发度,而-P 参数指定并发的实现方式,有 prefork (default)、eventlet、gevent 等,prefork 就是多进程的方式去实现并发。
你理解的多 worker 对应到多个进程,每个 worker (进程)自己内部还能并发是 gunicorn 的方式。gunicorn 的-w 参数指定有几个 worker (即几个进程),-k 参数指定每个 worker 的并发方式,可以是多线程或者多协程,也可以指定为 sync,表示 worker 是同步的,即不能并发。
比如 gunicorn 的-w 10 -k sync 和 celery 的-c 10 -P prefork 是等价的,都是创建 10 个进程去做并发,并发度最高就是 10。
再例如 celery 的-c 10 -P gevent 表示创建 10 个 gevent 协程去做并发,最高并发度也是 10。而 gunicorn 的-w 10 -k gevent,表示的是创建 10 个进程,且每个进程都是 gevent 异步的,这个并发度就很高了。