最近在做一个项目,项目中有多个模块,总体构架为SpringBoot +Quartz+其他(其他的不是重点啦)。
问题提出:
项目中会有Quartz做的定时任务模块,至于怎么使用Quartz网上一搜一大把,在这里不再细讲,
我要说的是,在开发和实际部署环境中如何避免定时任务重复执行?
例如:项目开发完成后已经部署到服务器,但是后期肯定是需要维护的呀,
那么问题来了,你在本地开发环境启动项目的时候,如果不做处理,呵呵。。。
结果可想而知,同一个定时任务,必然会执行两遍,如果是N个人维护这个程序,任务必将执行N+1次(服务器 一次)。
当然,前提是你们用的同一个数据库,大多数情况下为了偷懒(其实也是为了数据一致性啦),
程序猿一般会使用真实的服务器来运行程序。
那么如何在修改其他(非定时任务)模块时,在开发环境中关闭Quartz的定时任务呢?当然还不能影响服务器的任务执行。
在网上漂了大半天,也没有实际有效的解决办法,下面说说我的解决办法吧,如果你有更好的办法,也很希望你能告诉我,让我们共同进步。
首先,不同的环境我是有不同的配置文件的,主配置文件内容如下:
spring:
profiles:
# 生产环境
# active: prd
# 开发环境
active: dev
备注:我用的YML文件格式,其他格式类似,遵循其道即可。
在实际配置文件中加入开关(自定义的名字,可以改为你喜欢的名字)选项:
开发环境:
# 开启定时任务 true为启动 false为关闭
scheduling:
enabled: false
生产环境:
# 开启定时任务 true为启动 false为关闭
scheduling:
enabled: true
配置文件到此为止,简单吧,以下代码是QuartzSchedule中控制Schedule的配置和执行。
------------ 分割线 --------------------------------------------------
以下内容可能根据不同的人来说并不相同,请不要说你找不到这些代码,
你找一下你的Schedule是在哪里控制的,加入开关控制即可。
我在网上搜了好久也没有好的控制办法,经过对Quartz代码的解读,我发现Quartz的初始化是SpringBoot在内部自动加载的,
很难去不让它加载,不然你要修改Quartz的内部代码,不是不可以,但这必然不是我们想要的。
于是我想了个曲线救国的方式:Schedule可以初始化和加载,去控制它的执行,这个Schedule是程序运行时初始化的,
必然是单独运行的,所以修改它不会影响服务器的定时任务。
SchedulerFactoryBean factory = new SchedulerFactoryBean()
这个是我程序中的调度工厂类,你的可能会不同,但大同小异,这个会生成具体的Schedule实体,此处初始化时会设置Schedule的相关属性:
@Configuration
public class ScheduleConfig
{
//Jerry add for schedule control at 2020-2-22
@Value("${jmbj.scheduling.enabled}")
private Boolean scheduleEnableFlg;
@Bean
public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource)
{
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setDataSource(dataSource);
// quartz参数
Properties prop = new Properties();
prop.put("org.quartz.scheduler.instanceName", "JmbjScheduler");
prop.put("org.quartz.scheduler.instanceId", "AUTO");
// 线程池配置
prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
prop.put("org.quartz.threadPool.threadCount", "20");
prop.put("org.quartz.threadPool.threadPriority", "5");
// JobStore配置
prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
// 集群配置
prop.put("org.quartz.jobStore.isClustered", "true");
prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");
prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true");
// sqlserver 启用
// prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");
prop.put("org.quartz.jobStore.misfireThreshold", "12000");
prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
factory.setQuartzProperties(prop);
factory.setSchedulerName("JmbjScheduler");
// 延时启动
factory.setStartupDelay(1);
factory.setApplicationContextSchedulerContextKey("applicationContextKey");
// 可选,QuartzScheduler
// 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
factory.setOverwriteExistingJobs(true);
//Jerry edit at 2020-2-22
if (!scheduleEnableFlg) {
factory.setAutoStartup(false);
}else
{
// 设置自动启动,默认为true
factory.setAutoStartup(true);
}
return factory;
}
}
上面的属性中,如果要关闭schedule必须将自动启动的选项设置为false,其他属性按你的需要进行设置即可。
以上配置加载后会去实例化schedule对象,如下图所示:
@Autowired
private Scheduler scheduler;
@Autowired
private SysJobMapper jobMapper;
//Jerry add for schedule control at 2020-2-22
@Value("${jmbj.scheduling.enabled}")
private Boolean scheduleEnableFlg;
/**
* 项目启动时,初始化定时器
* 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据)
*/
@PostConstruct
public void init() throws SchedulerException, TaskException {
//Jerry edit at 2020-2-22
if (scheduleEnableFlg) {
List<SysJob> jobList = jobMapper.selectJobAll();
for (SysJob job : jobList) {
updateSchedulerJob(job, job.getJobGroup());
}
log.info("定时任务已启动。");
} else {
// scheduler.shutdown();
log.info("定时任务未启动。");
}
}
这个是从配置文件中读取控制开关。
@Value("${jmbj.scheduling.enabled}")
private Boolean scheduleEnableFlg;
以上是初始化Schedule后会去数据库中查找设置的定时任务:
如果是开发环境必然也是没必要去查询数据库了,这样必然也不会去执行定时任务啦!
需要注意的地方:开始还将schedule设置了shutdown();可以设置,但是没有必要,因为schedule中没有任何任务,所以不关闭也无所谓,性能嘛,我觉得可以忽略,但未测试。切记设置自动启动未false!!
转载请注明出处,如有其它办法,欢迎一起讨论。