使用django-crontab创建定时任务

正文

在Django开发中经常会遇到一些定时任务,那么提到定时任务肯定就会第一时间想到crontab。大家现在都是用容器去部署,那么在dockerfile里写crontab是一个很痛苦的事儿,所以自然就会想到用一些更加优雅的方法去让django跟crontab链接起来。

其实Django里用定时任务有很多方法,很多人用的是apscheduler或者是django-apscheduler。但是这个方案在我公司并不可行,原因是django-apscheduler会创建两张MySQL的表,而其中有一个表的id字段必须是varchar,是用来记录定时任务名称的。而我公司的开发规定要求id字段必须是int,所以直接使用它这个表是不行的,需要修改源码,这样就很麻烦了,对后期的维护和代码的传承也不利,而django-celery的方法比较重,于是我改用了django-crontab

先说明环境:
django == 3.2.12
python == 3.7.5
django的project叫naxx,app叫 naxxramas

安装django-crontab的方法很简单,pip install django-crontab即可,目前的版本是0.7.1,这个版本还支持,不过它已经很久不更新了,它的源码地址是:https://github.com/kraiz/django-crontab

使用的方法也很简单。

首先,在django项目的settings.pyINSTALLED_APPS添加django-crontab:

然后再去我们创建的app–naxxramas里创建一个文件叫crontabs.py,里面的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# !/usr/bin/python3
# 用途:定时任务相关,每天23点半执行这个任务,可见settings.py的配置
from naxxramas.models import MoneyLost, Change_appinfo, Dcchain_coreapp # 引入数据库

def crontab_change_app_info():
try:
fo = open("/tmp/test1.txt", "w")
fo.write("文件名: ", fo.name)
fo.write("写入成功\n 写入成功!\n")
# 关闭打开的文件
fo.close()
except Exception as e:
fo = open("/tmp/test2.txt", "w")
fo.write("文件名: ", fo.name)
fo.write("写入失败\n 写入失败!\n")
fo.write(e)
fo.close()

这个文件很简单,就是成功的话写入test1.txt对应的内容,失败的话就写入test2.txt对应的内容。

保存这个文件之后,就返回settings.py,在添加一个定时函数,如下:

上面这个任务代表每天9点27分执行一个django自带的命令,目标就是export相关的信息到/tmp/export.log。下面那个任务代表每小时50分的时候,执行一下我们上面那个crontabs.pycrontab_change_app_info函数, 并将结果输出到/tmp/chenx.log里。

保存了settings.py之后,不用重启Django。在命令行执行python manage.py crontab add:

1
2
3
4
$python manage.py crontab add

adding cronjob: (5e1f9075ca8672d43e82009a2e1f3402) -> ('27 9 * * *', 'django.core.management.call_command', ['export'], {}, '>> /tmp/export.log')
adding cronjob: (17e25872cfb38fa6ce1b8ab61075e5dc) -> ('50 * * * *', 'naxxramas.crontabs.crontab_change_app_info', '>> /tmp/chenx.log 2>&1')

然后crontab -l就能看到效果了:

其他相关的命令如下,把这些命令添加到对应的dockerfile或者启动文件里,保证Django启动的时候,也要启动django-crontab,同理退出Django的时候,也要退出django-crontab

1
2
3
python manage.py crontab add  添加定时任务
python manage.py crontab remove 清除定时任务,这里注意,先不要修改settings.py的任务,不然它发现原来的任务编号不存在了,就会remove报错,建议remove后再修改settings.py里的定时函数
python manage.py crontab show 显示定时任务

有时候设置django定时任务定时时间为每分钟执行,有时候可能出现上一次任务还没执行完下一次任务又会开始这样的BUG。解决办法就是在settings.py中加入CRONTAB_LOCK_JOBS = True

1
2
3
4
5
6
7
8
9
# setting.py

# 定时任务
CRONJOBS = [
('* * * * *', 'app.xxx.xxx.main', '>>/var/log/xxx.log')
]

CRONTAB_LOCK_JOBS = True # 定时任务如果同一作业的旧实例仍在运行,则阻止启动作业
CRONTAB_COMMAND_PREFIX = 'PYTHONUNBUFFERED=1' # 不加这句话,main方法里使用print打印到xxx.log的日志不是实时的。通过这个使设置定时任务print打印不缓存(类似python -u xxx.py),不过推荐还是用logging模块记录日志

这里要注意3点:
第一,因为django-crontab依赖crontab进程,所以要确认crontab是启动的;
第二,如果你的输出文件仅仅是一个print XXX 到某某文件的话,那估计不会看到效果,只能看到这个文件被创建了而已;
第三,如果定时任务没有效果,可以在命令行去执行crontab -l里的命令,看看是什么错误,根据对应的错误排查即可;
第四,无法做到Django退出后,django-crontab一起退出,所以建议在django退出的脚本或者uwsgi退出的脚本里补充上python manage.py crontab remove

参考资料

http://2016519.com/article/2020/5/15/35.html
https://www.xkblogs.com/index.php/archives/26/
https://www.jianshu.com/p/dd22e8bd29c1
https://www.cnblogs.com/weidaijie/p/12747685.html
https://www.youtube.com/watch?v=6eoS9CFOmFw 这里的视频讲解更全面

感谢您请我喝咖啡~O(∩_∩)O,如果要联系请直接发我邮箱chenx1242@163.com,我会回复你的
-------------本文结束感谢您的阅读-------------