在开发后台与任务相关的功能中,遇到一个需求:用户需要能够为任务配置定时策略,使任务定时执行某个操作。
需求分析
根据需求,我们可以拆解成如下几个步骤:
- 「某个操作」的实现
- 配置为定时任务
- 定时策略可配置
- 用户体验好
其中步骤 1 与本文无关不提;对于定时任务的实现,在上节Celery异步任务队列 有简单提到 celery 也支持定时任务。
Celery 的定时任务策略配置于代码中,在启动 celery 时写入本地shelve 文件,不利于管理。
因此在 celery 的文档中也提到一个扩展模块 django-celery-beat
,该模块将定时任务的配置写入 Django 配置的数据库中,当程序启动后可以通过 admin
后台进行管理,并且可以直接通过 ORM 对定时任务配置进行修改,无需修改代码然后重启 celery,符合我们预期。
当然还有很多其他库也能实现,因为我们已经使用 celery 执行异步任务,所以本文还是用 django-celery-beat
解决问题。
Celery 的定时任务使用的是类似 crontab
的语法,因此在用户体验上,要考虑普通用户的学习成本,可以提供一些常用的配置,例如每周的工作日每天 1 点执行任务;也要考虑后期的扩展性,可以提供输入框方便配置。
设计与实现
基本用法
定时策略(CrontabSchedule)
CrontabSchedule
支持类 crontab 语法,同样是 5 个配置域,分别为:
- 分
- 时
- 每周中的天
- 每月中的天
- 每年中的月
每个配置域使用空格隔开。
对每个配置域常用语法:
*
: 范围内的所有值M-N
: M到N之间的值M-N/X
或*/X
: 每X分钟、每X天等等A,B,...,Z
: 枚举的值
举个例子: 每个工作日1点执行: 0 1 1-5 * *
创建定时策略代码如下:
from django_celery_beat.models import CrontabSchedule, PeriodicTask > schedule, _ = CrontabSchedule.objects.get_or_create( ... minute='30', ... hour='*', ... day_of_week='*', ... day_of_month='*', ... month_of_year='*', ... )
定时任务
定时任务可以依赖不同的定时策略,例如 crontab, interval 等,创建时指定 schedule
即可。以 crontab 定时任务为例:
> import json > from datetime import datetime, timedelta > PeriodicTask.objects.create( ... crontab=schedule, # we created this above. ... name='Importing contacts', # simply describes this periodic task. ... task='proj.tasks.import_contacts', # name of task. ... args=json.dumps(['arg1', 'arg2']), ... kwargs=json.dumps({ ... 'be_careful': True, ... }), ... expires=datetime.utcnow() + timedelta(seconds=30) ... )
其中 name
为定时任务的名称,每个任务名必须唯一; task
为需要执行的 celery 任务。加上定时策略调度器,这三个是一个定时任务所必须的属性。
定时任务还有其他配置,如 args
/ kwargs
对应一个 celery 任务的入参; expires
设置了该定时任务的过期时间。
Django配置
最基础的配置只需要在 INSTALLED_APPS
中添加引用,并设置定时任务调度器即可:
settings.py
INSTALLED_APPS = [ ... 'django_celery_beat' ] # 配置 celery 定时任务使用的调度器 CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler'
时区问题
在使用 django-celery-beat
过程中遇到两个关于时区的问题:
创建的定时任务,实际触发时间与配置的时间存在8小时时间差
解决方案:
8小时明显是因为时区不同导致,而 django-celery-beat
对时区的处理似乎总有问题(若不对请指教)。
修改 settings.py
中的时区配置:
settings.py
# 设置 Django 大部分应用通用的时区 TIME_ZONE = 'Asia/Shanghai' # 关闭 UTC USE_TZ = False CELERY_ENABLE_UTC = False # 设置 django-celery-beat 真正使用的时区 CELERY_TIMEZONE = TIME_ZONE # 使用 timezone naive 模式 DJANGO_CELERY_BEAT_TZ_AWARE = False
关于 timezone naive 与 timezone aware 模式的区别可以参考文章:Django时区详解
简单来说就是,naive 模式不存储时区信息,只存储经过时区转换后的时间;反之 aware 模式则存储了 UTC 时间和 UTC 时区信息。
根据文档,在修改了时区后,需要将已执行过的定时任务的 last_run_at
重置为 None
:
python manage.py shell > from django_celery_beat.models import PeriodicTask > PeriodicTask.objects.all().update(last_run_at=None)
修改完成后,重启 celery beat
。
PS: 就算是经过这样配置,我也仍然遇到了任务不断执行的问题,并且在我多次重启 celery 后不再复现,因此本配置可能还有问题。
数据库中, CrontabSchedule
的 timezone
配置始终是 UTC
解决方案:
查看 CrontabSchedule
模型的源码,找到数据库中 timezone
字段的属性:
class CrontabSchedule(models.Model): ... timezone = timezone_field.TimeZoneField( default='UTC', verbose_name=_('Cron Timezone'), help_text=_( 'Timezone to Run the Cron Schedule on. Default is UTC.'), )
由于我们在创建 CrontabSchedule
实例时并未指定 timezone
,因此在创建任务时,添加该字段的配置即可:
from django_celery_beat.models import CrontabSchedule > schedule, _ = CrontabSchedule.objects.get_or_create( ... minute='30', ... hour='*', ... day_of_week='*', ... day_of_month='*', ... month_of_year='*', ... timezone='Asia/Shanghai' ... )
*业务前后端设计
本节内容仅供参考,不一定适用其他场景。
前端
设计前端定时任务配置项,包含一个开关,一个三选一单选组件,以及一个输入框:
为了方便非技术人员设置定时任务,优化用户体验,定时任务除了「自定义」的输入模式,还有一个「每天」与「每周」的选项:
- 每天:0 1 1-5 * *
- 每周:0 1 1 * *
单选框与字符串双向绑定,在后端返回上面两个字符串之一时选中每天或每周,否则选中自定义选项。
后端
假设对于我的业务来说,前端需要的任务数据字段为:
{ "task_id": 1, "is_periodic_task": true, "periodic_task_id": 1, "crontab": "* * * * *" }
ER 模型如图:
返回给前端的数据中,若 periodic_task
不为空,则 is_periodic_task
为 True
,并通过 periodic_task.crontab_id
获取到 CrontabSchedule
实例,转化为字符串返回。
要注意, CrontabSchedule
的 __str__
方法除了返回 crontab
配置,还会返回时区等信息,而这些信息前端展示时并不需要。
因此可以新建一个方法:
def get_crontab_str(contab) -> str: """ 获取前端配置需要的 5 项值 :param contab: CrontabSchedule对象 :return: """ return '{0} {1} {2} {3} {4}'.format( cronexp(contab.minute), cronexp(contab.hour), cronexp(contab.day_of_week), cronexp(contab.day_of_month), cronexp(contab.month_of_year) )
序列化时调用该方法返回给前端即可。
修改任务
修改任务包括以下三种情况
- 从定时任务改为非定时任务
- 从非定时任务改为定时任务
- 在定时任务基础上修改定时策略
对应流程图如下:
1:
2, 3:
图中「修改配置中的」指前端传来的修改请求中的新配置信息
具体代码就不赘述,只提一下暂停定时任务的方法:
修改 PeriodicTask.objects.enabled
为 False/0
即可
> periodic_task.enabled = False > periodic_task.save()
版本说明
参考
http://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html
https://django-celery-beat.readthedocs.io/en/latest/
https://docs.djangoproject.com/en/2.2/topics/i18n/timezones/
https://www.jb51.net/article/166085.htm
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 【雨果唱片】中国管弦乐《鹿回头》WAV
- APM亚流新世代《一起冒险》[FLAC/分轨][106.77MB]
- 崔健《飞狗》律冻文化[WAV+CUE][1.1G]
- 罗志祥《舞状元 (Explicit)》[320K/MP3][66.77MB]
- 尤雅.1997-幽雅精粹2CD【南方】【WAV+CUE】
- 张惠妹.2007-STAR(引进版)【EMI百代】【WAV+CUE】
- 群星.2008-LOVE情歌集VOL.8【正东】【WAV+CUE】
- 罗志祥《舞状元 (Explicit)》[FLAC/分轨][360.76MB]
- Tank《我不伟大,至少我能改变我。》[320K/MP3][160.41MB]
- Tank《我不伟大,至少我能改变我。》[FLAC/分轨][236.89MB]
- CD圣经推荐-夏韶声《谙2》SACD-ISO
- 钟镇涛-《百分百钟镇涛》首批限量版SACD-ISO
- 群星《继续微笑致敬许冠杰》[低速原抓WAV+CUE]
- 潘秀琼.2003-国语难忘金曲珍藏集【皇星全音】【WAV+CUE】
- 林东松.1997-2039玫瑰事件【宝丽金】【WAV+CUE】