Django+Apscheduler 开发定时任务模块
目录
本文章要实现的是Django+Apscheduler 开发定时任务模块,并使用uwsgi+nginx模式部署,
且能避免多进程下任务重复执行,且能够页面动态操作任务;
一、回顾
前两节我们已经创建好了django项目,并创建好了定时任务的model,请前往查看
【Django+Apscheduler 开发定时任务模块】【一】
【Django+Apscheduler 开发定时任务模块】【二】
二、开发apscheduler工具类
1、先贴上apscheduler官网链接,这里不做具体介绍:
2、选择调度器,apscheduler的调度器有多种,还是简单介绍吧:
这里借来的介绍
BlockingScheduler: 如果调度器是你程序中唯一要运行的东西,请选择它
BackgroundScheduler: 如果你想你的调度器可以在你的应用程序后台静默运行,同时也不打算使用以下任何 Python 框架,请选择它
AsyncIOScheduler: 如果你的程序使用了 asyncio 库,请使用这个调度器
GeventScheduler: 如果你的程序使用了 gevent 库,请使用这个调度器
TornadoScheduler: 如果你打算构建一个 Tornado 程序,请使用这个调度器
TwistedScheduler: 如果你打算构建一个 Twisted 程序,请使用这个调度器
QtScheduler: 如果你打算构建一个 Qt 程序,请使用这个调度器
由于我们是基于Django框架开发,所以这里使用支持后台静默运行的调度器:BackgroundScheduler
3、apscheduler的主要操作函数:
1、添加任务:add_job
2、修改任务:modify_job
3、暂停任务:pause_job
4、重启任务:resume_job
5、删除任务:remove_job
# 我们的项目中,主要使用了添加、修改和删除任务,主要围绕这三个函数来写
4、开始编写apscheduler工具类,注意看下面代码块中的注释:job_new.py:
# 首先定义一个函数和一个全局的apscheduler对象,便于在类中和外部函数调用
def start_scheduler():
global scheduler
scheduler = BackgroundScheduler()
scheduler.start()
# 编写类 JobAction,这个class有点长,不知道能不能行
class JobAction:
# 这是类的一个初始化函数,初始化scheduler和监听函数
def __init__(self):
self.scheduler = scheduler
self.scheduler.add_listener(self.my_listener, EVENT_JOB_ERROR | EVENT_JOB_MISSED | EVENT_JOB_EXECUTED)
# 上一节的截图中出现了两条记录,调度类型分别为date和cron,这里分为两个启动函数来写,实际还有第三个类型为:interval,本项目并没有使用;
# 大家应该看到了 eval(trigger),用这个是因为我们传过来的参数是字符串,但add_job()需要的是一个对象,所以eval的作用就体现出来了。
# 重要的是我们首先要在这个文件中将任务import进来,比如:
# from app.test_job import test
@staticmethod
def start_date_job(trigger, job_rate, id):
trigger_id = trigger + '-' + id
scheduler.add_job(eval(trigger), 'date', run_date=job_rate, id=trigger_id, args=[trigger_id, id, 'date'], coalesce=False)
logger.info("%s start successfully" % trigger)
logger.info('任务池:' + str(scheduler.get_jobs))
@staticmethod
def start_cron_job(trigger, job_rate, id):
rate = job_rate.split()
trigger_id = trigger + '-' + id
# 秒 分 时 日 月 星期 年
scheduler.add_job(eval(trigger), 'cron', second=rate[0], minute=rate[1], hour=rate[2], day=rate[3],
month=rate[4], day_of_week=rate[5], year=rate[6], id=trigger_id,
args=[trigger_id, id, 'cron'], coalesce=False)
logger.info("%s start successfully" % trigger)
logger.info('任务池:' + str(scheduler.get_jobs))
# 停止任务
@staticmethod
def stop_job(trigger_id):
if scheduler.get_job(trigger_id):
scheduler.remove_job(trigger_id)
logger.info('已启动的任务:' + str(scheduler.get_jobs()))
# 暂停任务
@staticmethod
def pause_job(trigger_id):
logger.info(trigger_id)
scheduler.pause_job(trigger_id)
# 重启任务
@staticmethod
def resume_job(trigger_id):
scheduler.resume_job(trigger_id)
# 修改任务
@staticmethod
def modify_job(trigger_id, job_value):
if job_value[0] == 'cron':
rate = job_value[1].split()
scheduler.reschedule_job(trigger_id, trigger='cron', second=rate[0], minute=rate[1],hour=rate[2], day=rate[3], month=rate[4], day_of_week=rate[5], year=rate[6])
elif job_value[0] == 'date':
scheduler.reschedule_job(trigger_id, trigger='date', run_date=job_value[1])
# 这里是监听函数
@staticmethod
def my_listener(event): # 添加监听器
job = scheduler.get_job(event.job_id)
if not event.exception:
pass
else:
logger.error("jobname=%s|jobtrigger=%s|errcode=%s|exception=[%s]|traceback=[%s]|scheduled_time=%s", job.name, job.trigger, event.code, event.exception, event.traceback, event.scheduled_run_time)
# scheduler.shutdown(wait=False)
以上就是对apscheduler操作的一个整合,方便后续的调用,下面就使用这个类来执行定时任务
三、页面添加按钮
由上一节的截图可知,咱们的定时任务页面除了添加、删除,一共是三个按钮:启动任务、停止任务和保存,注意看代码块中的注释;
下面分别来介绍一下相应的实现的功能:
1、先说简单的 保存按钮,这个django admin自带的按钮,通过配置可实现,直接看代码吧
# admin中有一行配置,加上这一行就会出现保存按钮,可以将展示页面的编辑信息通过后台保存到数据库
list_editable = ('action_type', 'job_rate',)
# 我们的项目中在点击保存按钮之后对数据就行了一定的处理,重写了save_model函数
# 这里是在保存数据的时候,先判断任务状态是否是运行状态
# 是的话,以任务触发器+‘_’+任务ID 为key,任务类型和新的策略存储在redis中;反之不进行操作。最后将修改后的数据存入数据库;
def save_model(self, request, obj, form, change):
from django.core.cache import cache
if obj.job_state == 1:
cache.set(obj.trigger_name + '-' + str(obj.id), (obj.action_type, obj.job_rate))
obj.save()
2、接下来是 启动任务,这个按钮的功能就是将任务状态修改为启动,并且将该记录对应的触发器、执行频率和调度类型添加到apscheduler的任务池中
# 这个函数就不全贴出来了,只写关键部分吧
def start_job(self, request, queryset):
# 这里省略一部分循环选择的页面数据的部分,根据下面代码可以还原出来
# 就是将选中的任务循环添加到任务池中,并修改状态;若有错,返回错误信息
trigger_name = trigger.get('trigger_name')
job_rate = rate.get('job_rate')
job_id = id.get('id')
job_name = name.get('job_name')
job_type = type.get('action_type')
if hasattr(JobAction(), 'start_%s_job' % job_type):
func = getattr(JobAction(), 'start_%s_job' % job_type)
func(trigger_name, job_rate, str(job_id))
queryset.update(job_state=1)
else:
return messages.error(request, '【启动错误】 ' + job_name + ' 任务不存在!')
3、最后就是 停止任务 了,还是看代码吧
# 这里也省略一部分循环选择的页面数据的部分,根据下面代码可以还原出来
# 就是根据选中的任务,从任务池中删除
def stop_job(self, request, queryset):
trigger_name = trigger.get('trigger_name')
job_id = id.get('id')
job_name = name.get('job_name')
JobAction.stop_job(trigger_name + '-' + str(job_id))
queryset.update(job_state=0)
OK,这里就完成了对页面按钮和定时任务的操作相关功能的开发工作。
写太多了,上一节说这是最后了,食言了,明天再写一节吧!真的最后一节了!
2022-05-02