pip install django-apscheduler
INSTALLED_APPS = [
...
'django_apscheduler', # 导入django_apscheduler
'my_scheduler', # 新建app,用来启动apscheduler
...
]
执行python manage.py migrate django_apscheduler
,生成对应的表django_apscheduler_djangojob、django_apscheduler_djangojobexecution。
from apscheduler.schedulers.background import BackgroundScheduler
from django_apscheduler.jobstores import DjangoJobStore, register_events, register_job
from django.conf import settings
import logging
import os
# 日志配置
logging.basicConfig(level=logging.INFO,
format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
filename=os.path.join(settings.MEDIA_ROOT, 'log', 'scheduler.log'),
filemode='a')
# 实例化调度器
sched = BackgroundScheduler()
# 调度器使用默认的DjangoJobStore()
sched.add_jobstore(DjangoJobStore(), 'default')
sched._logger = logging
# 注册事件。启用时会报外键引用错误,原因待查。。。
# register_events(sched)
sched.start()
from my_scheduler.views import sched
from datetime import datetime
def send_mail(value):
print('{} {} send mail'.format(datetime.now(), value))
# date定時任務(执行一次)
sched.add_job(send_mail, 'date', run_date=datetime.strptime('2020-04-10 14:30', '%Y-%m-%d %H:%M'),
args=['date_job'], id='job_id', misfire_grace_time=60, coalesce=True)
# 间隔任务
sched.add_job(send_mail, 'interval', seconds=60, args=['interval_job'])
# cron定时调度(循环执行)
sched.add_job(send_mail, 'cron', month='1-12', day='3rd fri', hour='0-3', minute=30, end_date='2014-05-30', args=['cron_job'])
add_job特殊参数介绍:
coalesce:当由于某种原因导致某个job积攒了好几次没有实际运行(比如说系统挂了5分钟后恢复,有一个任务是每分钟跑一次的,按道理说这5分钟内本来是“计划”运行5次的,但实际没有执行),如果coalesce为True,下次这个job被submit给executor时,只会执行1次,也就是最后这次,如果为False,那么会执行5次(不一定,因为还有其他条件,看后面misfire_grace_time的解释)
max_instance: 就是说同一个job同一时间最多有几个实例再跑,比如一个耗时10分钟的job,被指定每分钟运行1次,如果我们max_instance值为5,那么在第6~10分钟上,新的运行实例不会被执行,因为已经有5个实例在跑了
misfire_grace_time:设想和上述coalesce类似的场景,如果一个job本来14:00有一次执行,但是由于某种原因没有被调度上,现在14:01了,这个14:00的运行实例被提交时,会检查它预订运行的时间和当下时间的差值(这里是1分钟),大于我们设置的30秒限制,那么这个运行实例不会被执行
此问题是uwsgi启动时开启多个进程造成的,修改uwsgi进程个数为1,workers=1
,问题得以解决。
在网上查询到有人通过锁的方式避免apscheduler重复执行(我没有成功…),如下:
# 方式一:利用端口不能复用的方式
import sys, socket
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(("127.0.0.1", 8888))
except socket.error:
print("!!!scheduler already started, DO NOTHING")
else:
sched = BackgroundScheduler()
sched.add_jobstore(DjangoJobStore(), 'default')
sched._logger = logging
sched.start()
print("scheduler started")
# 方式二:文件锁的方式, windows平台python无fcntl
import atexit
import fcntl
def init(app):
f = open("scheduler.lock", "wb")
try:
fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
sched = BackgroundScheduler()
sched.init_app(app)
sched.add_jobstore(DjangoJobStore(), 'default')
sched._logger = logging
sched.start()
except:
pass
def unlock():
fcntl.flock(f, fcntl.LOCK_UN)
f.close()
atexit.register(unlock) # 程序退出时回调函数unlock()
https://blog.csdn.net/weixin_38168918/article/details/101286885
https://www.jianshu.com/p/9d6b277c4a7d
https://zhuanlan.zhihu.com/p/46948464