需求
最近有一个项目需要从原来的单机部署修改为 集群方式部署,因为银行要求所有的的服务不能有单点的情况,所以我们需要 对该项目就行改造,别的需求改造还是比较容易的,但是项目中采用了quartz执行定时任务,所以我们需要改造这个地方,在本文中采用quartz自身支持的基于DB的集群部署方案。
版本
2.2.3
创建DB
sql文件地址:https://download.csdn.net/download/u013045437/15534461
数据字段说明:https://blog.csdn.net/hao134838/article/details/114291197
pom文件引入包
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.2.3</version>
</dependency>
定时任务xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!-- 调度器lazy-init='false'那么容器启动就会执行调度程序 -->
<bean id="myJobFactory" class="com.jack.platformweb.task.service.MyJobFactory"></bean>
<bean id="startQuertz " lazy-init="false" autowire="no"
class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="jobFactory" ref="myJobFactory"/>
<property name="dataSource" ref="urgeRobotDataSource"/>
<property name="configLocation" value="classpath:properties/quartz.properties"/>
<!--这个是必须的,QuartzScheduler延时启动,应用启动完后 QuartzScheduler再启动-->
<property name="startupDelay" value="30"/>
<!--这个是可选,QuartzScheduler启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了-->
<property name="overwriteExistingJobs" value="true"/>
<!-- 允许在Quartz上下文中使用Spring实例工厂 -->
<property name="applicationContextSchedulerContextKey" value="applicationContext"/>
<property name="triggers">
<list>
<ref bean="sendCaseTrigger"/>
</list>
</property>
</bean>
<!-- 推送任务 -->
<bean id="sendCaseTaskClass" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass">
<value>com.jack.platformweb.quartz.service.SendCaseTaskService</value>
</property>
<property name="durability" value="true"/>
<property name="requestsRecovery" value="true"/>
</bean>
<bean id="sendCaseTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="sendCaseTaskClass"/>
<property name="cronExpression" value="0/20 * * * * ?"/> <!-- //每5分钟执行一次 -->
</bean>
</beans>
上面配置文件有几个点需要说明一下:
1、<bean id="myJobFactory" class="com.jack.platformweb.task.service.MyJobFactory"></bean>
这是我们自己定义的一个Factory,主要是解决我们在定时任务中可以自动注入我们的业务service
package com.jack.platformweb.task.service;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
public class MyJobFactory extends AdaptableJobFactory {
//这个对象Spring会帮我们自动注入进来,也属于Spring技术范畴.
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
//调用父类的方法
Object jobInstance = super.createJobInstance(bundle);
//进行注入,这属于Spring的技术,不清楚的可以查看Spring的API.
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
2、<property name="dataSource" ref="urgeRobotDataSource"/> urgeRobotDataSource 就是我们自己项目中配置的数据源,可以与mybatis使用一个
其余的一些配置就和我们xml配置quartz 的方式差不多了,只是这种 部署方式控制粒度到类上,目前还没有找到控制到方法上的解决方案。
属性配置
#============================================================================
# Configure Main Scheduler Properties
#============================================================================
org.quartz.scheduler.instanceName = Mscheduler
org.quartz.scheduler.instanceId = AUTO
org.quartz.jobStore.clusterCheckinInterval=20000
#============================================================================
# Configure ThreadPool
#============================================================================
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 3
org.quartz.threadPool.threadPriority = 5
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.useProperties = true
org.quartz.jobStore.dataSource = myDS
#数据库表开头表示
org.quartz.jobStore.tablePrefix = qrtz_
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.maxMisfiresToHandleAtATime=1
任务类代码
package com.jack.platformweb.quartz.service;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.PersistJobDataAfterExecution;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @author zhenghao
* @description: 推送案件任务
* @date 2020/3/2011:10
*/
@Service
@Transactional
@PersistJobDataAfterExecution
@DisallowConcurrentExecution// 不允许并发执行
public class SendCaseTaskService extends QuartzJobBean {
private static Logger log = LoggerFactory.getLogger(SendCaseTaskService.class);
// @Autowired
// private FsCallBaseService fsCallBaseService;
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) {
//业务逻辑
// fsCallBaseService.sendCaseAll();
log.info("task_task_tak");
}
}
总结
通过上面的方式我们就完成了基于DB的quartz的集群部署方式,关于这种方案的的原理网上有很多的文章就行介绍,其实非常的简单,就是讲执行任务写到数据库中,多个部署节点都基于同一个DB就行并发控制。
这种解决方案有几个弊端:

1、每一个定时任务的方法都需要一个类,这里需要改的工作量比较大,暂时还没有找到基于注解的,粒度到方法上的集群部署方式。
2、多个部署节点的集机器需要进行时间同步。
这里主要是考虑到改造工作量大,并且xml配置方式复杂,所以这几天也在考虑其他的方案,基于现有的部署要求,可以使用AOP+分布式锁(基于rediss)来实现,在一篇博客中分享这种解决方案。