介绍:
Python项目,需要异步调度,包含定时任务,可引入Celery及
celery-redbeat作为调度工具。
其中,celery[Celery - Distributed Task Queue — Celery 5.2.7 documentation]是python的任务调度框架,celery-redbeat是clelery beat[Welcome to Celery Redbeat’s documentation! — Celery Redbeat documentation]的一种,用于监听任务,需依靠redis
目标:
通过实例讲解,了解如何使用celery-redbeat监听任务,实现实时任务
Demo:
环境:Python==3.7.5 celery==5.2.7 redis==4.3.4 eventlet==0.33.1 celery-redbeat==2.0.0
目录:
(1)创建celery配置, celery_config.py
# celery worker 配置
broker_url = 'redis://:$password@$host:$port/db' # broker url, db=0
result_backend = 'redis://:$password@$host:$port/db' # 结果存储, db=1
task_serializer = 'json'
result_serializer = 'json'
accept_content = ['application/json']
timezone = 'Asia/Shanghai'
enable_utc = False
task_compression = "gzip"
worker_max_tasks_per_child = 100 # 一个worker进程最多执行任务数
worker_cancel_long_running_tasks_on_connection_loss = True # 连接中断重连后,无需重复发送任务
worker_log_format = "[%(asctime)s][%(levelname)s][%(processName)s] %(message)s" # 日志
# celery beat 配置
beat_scheduler = 'redbeat.RedBeatScheduler'
redbeat_redis_url = broker_url
redbeat_key_prefix = 'redbeat' # 任务前缀
redbeat_redis_options = {'max_retries': 3}
beat_max_loop_interval = 1 # 监听频率,秒
redbeat_lock_timeout = beat_max_loop_interval * 5 # 锁超时,秒
# 配置路由,可以不配置,默认是celery队列
task_routes = {
'demo.tasks.add': {'queue': 'add'},
'demo.tasks.div': {'queue': 'div'},
}
(2) 创建celery 任务,tasks.py
from abc import ABC
from celery.utils.log import get_task_logger
from celery import Task
from demo import app
logger = get_task_logger(__name__)
class AsyncTask(Task, ABC):
""" 重写Task类 """
def before_start(self, task_id, args, kwargs):
""" 任务开始前 """
logger.info('=1111==before_start=task_id={}==args={}==kwargs={}==task=={}'.
format(task_id, args, kwargs, self.request.task))
def on_failure(self, exc, task_id, args, kwargs, einfo):
""" 任务执行失败 """
logger.error('=222==on_failure=task_id={}==args={}==kwargs={}=exc={}=einfo={}==task=={}'.
format(task_id, args, kwargs, exc, einfo, self.request.task))
def on_success(self, retval, task_id, args, kwargs):
""" 任务执行成功 """
logger.info('=333==on_success=task_id={}==args={}==kwargs={}==retval={}=task=={}'.
format(task_id, args, kwargs, retval, self.request.task))
def on_retry(self, exc, task_id, args, kwargs, einfo):
""" 任务重试 """
logger.warning('=444==on_retry=task_id={}==args={}==kwargs={}=exc={}=einfo={}==task=={}'.
format(task_id, args, kwargs, exc, einfo, self.request.task))
# 任务一
@app.task(base=AsyncTask)
def add(x, y):
return x + y
# 任务二
@app.task(base=AsyncTask)
def div(x, y):
return x / y
(3) 创建celery app, demo.py
from celery import Celery
app = Celery('demo', include=['tasks']) # demo是celery应用名,include加载创建好的任务
app.config_from_object('celery_config') # 加载celery 配置
if __name__ == '__main__':
app.start() # 启动
print(app.conf.humanize(with_defaults=False, censored=True))
(4) 创建调度任务,scheduler.py
from redbeat import RedBeatSchedulerEntry
from celery.schedules import schedule, crontab
from celery import Celery
app = Celery(include=['tasks'])
app.config_from_object("celery_config")
task_name = ":task-add" # ":"是分割符,建议加上
entry = RedBeatSchedulerEntry(app=app, name=task_name, # 任务名
task='tasks.add', # 任务,需要与tasks.py任务名一致
#schedule=schedule(run_every=11), # 调度规则,每多少秒循环执行
schedule=crontab(minute='*/10'), # 调度规则,定时任务
args=[3, 11], # 任务参数
enabled=True, # 开启,False时关闭任务
options={"queue": 'add'} # 指定队列, celery worker启动时需要指定队列
)
entry.save()
# 通过key,删除任务
key = 'redbeat:task-add' # 配置中的redbeat_key_prefix + task_name
entry = RedBeatSchedulerEntry.from_key(key, app=app)
entry.delete()
# 终止执行中的任务
from demo import app
celery_id = "b67bd725-6dc1-4c63-a184-b0faae736d17"
app.control.revoke(celery_id, terminate=True)
注意事项:同一任务暂停后,再启动,可能到调度时刻执行两次,可用RedBeatSchedulerEntry.from_key(key, app=app).reschedule()解决
调度规则参考:
Periodic Tasks — Celery 5.2.7 documentation
启动:
(1) celery worker:
celery -A demo worker -c 2 -P eventlet -l INFO -Q celery,add -f /tmp/worker.log
-A 应用名
-c worker数
-P 执行池[windows 只能是solo]
-l 日志类型
-f 日志文件
-Q 调度队列[只有在配置了task_routes时使用]
(2) celery beat:
celery -A demo beat -l INFO -f /tmp/beat .log