如何用Django和Celery在指定时间执行任务?及eta参数问题排查
在Celery中设置特定日期时间执行异步任务及ETA问题排查
嘿,我来帮你搞定Celery定时任务的问题,刚好之前我也踩过类似的坑,下面分两部分给你讲清楚:
一、怎么设置特定日期执行任务
Celery的apply_async()方法自带的eta参数就是干这个的,完美匹配你“2019年8月30日11:35执行操作”的需求,具体用法很简单:
1. 基础用法
直接给eta传你要执行的时间就行,代码示例:
from datetime import datetime from your_celery_module import test # 把这里改成你要的时间:年、月、日、时、分 target_time = datetime(2019, 8, 30, 11, 35) test.apply_async(eta=target_time)
2. 一定要注意:时区问题!
这里有个超级容易踩的坑——Celery默认用UTC时间,如果你传的是不带时区信息的本地时间,它会直接把这个时间当成UTC来处理,结果就是任务执行时间和你预期的差好几个小时(比如东八区差8小时)。
给你两个解决办法,选哪个都行:
- 办法一:把本地时间转成UTC时间
先引入pytz库(没有的话先pip install pytz),然后转换:from datetime import datetime, timezone import pytz # 替换成你的本地时区,比如北京就是Asia/Shanghai local_tz = pytz.timezone('Asia/Shanghai') # 给本地时间加上时区信息 target_time_local = local_tz.localize(datetime(2019, 8, 30, 11, 35), is_dst=None) # 转成UTC时间 target_time_utc = target_time_local.astimezone(timezone.utc) test.apply_async(eta=target_time_utc) - 办法二:让Celery直接用本地时区
在你的celery.py里加两行配置,一劳永逸:app = Celery() # 设置成本地时区 app.conf.timezone = 'Asia/Shanghai' # 关掉UTC模式,这样直接传本地时间就ok了 app.conf.enable_utc = False
二、为啥你用eta的任务显示已接收但不执行?
根据你说的Flower显示已接收但没执行的情况,我90%确定是时区不匹配搞的鬼,具体分析下:
你传的本地时间在UTC里已经过期了
比如你传的是东八区的6:28,那对应的UTC时间是前一天的22:28,如果你调用任务的时候已经过了这个UTC时间,Celery会觉得任务已经过期,直接跳过不执行。Worker的时区和Celery配置不一致
就算你在celery.py里配置了时区,也要确保启动Worker的时候用的是同一个时区。启动Worker的时候可以加个参数指定:celery -A your_celery_module worker --loglevel=info --timezone=Asia/Shanghai再给你几个排查小技巧:
- 先检查你指定的ETA时间是不是已经过去了——如果调用任务的时候,那个时间已经比当前时间早,Celery肯定不会执行;
- 看看Worker日志,启动Worker的时候开
--loglevel=debug,里面会明确告诉你任务为啥没执行,比如会看到“Task expired”的提示; - 确认Worker状态正常,用
celery -A your_celery_module status命令就能看。
额外补充:如果是周期性任务怎么办?
要是你需要每天固定时间执行(比如每天11:35跑一次),那用Celery的Beat调度器配合crontab更合适:
在celery.py里加配置:
from celery.schedules import crontab app.conf.beat_schedule = { # 给任务起个名字 'daily-test-task': { # 指定要执行的任务路径 'task': 'your_celery_module.test', # 设置执行时间:每天11点35分 'schedule': crontab(hour=11, minute=35), }, }
然后启动Beat服务:
celery -A your_celery_module beat --loglevel=info
内容的提问来源于stack exchange,提问作者JuConte
相关产品推荐
相关产品推荐

