当项目部署在多个tomcat上进行集群后,定时任务就会有问题. 多个tomcat都会执行相同的定时任务.最简单的解决办法是把定时器只放到一个tomcat上运行,但是这样就不是集群了.
我们期望的是一台服务挂掉,不影响定时器执行. 这时候就用到了quartz的集群.
其实单机运行的时候,使用spring task 最方便, 2行代码就搞定了. 但是spring task不支持集群. 所以集群的话还是用quartz.
1.集群配置
2.测试
3.删除定时任务
1.集群配置
1)数据库脚本
我们可以想象,一个定时任务,只会执行一次,肯定要有一个全局的人来监控到底是哪个quartz去执行了这个任务,然后让别的quartz不再执行这个任务.
那谁来当这个全局的人呢? 当然是数据库啦. quartz官方也提供了基于内存的Terracotta来管理. 我们这里就用数据库了.
去下载quartz的压缩包,里面自带sql脚本文件.点击打开链接
我们这里用的数据库是 mysql,innodb引擎的. 所以执行标红的sql脚本.
2) 配置文件
首先在spring-quartz中,增加个执行器配置,用于任务注册
<bean id="executor"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="10" />
<property name="maxPoolSize" value="100" />
<property name="queueCapacity" value="500" />
</bean>
然后在总管理类中增加一些属性配置
<bean id="startQuertz" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="testTrigger" />
</list>
</property>
<property name="taskExecutor" ref="executor" />
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:quartz.properties" />
</bean>
其中dataSource 是连接数据库的bean,configLocation是 quartz自身的一些配置
quartz.properties 我们使用官方的 传送门 ,但是把数据库配置的地方改下
#============================================================================
# Configure Main Scheduler Properties
#============================================================================
org.quartz.scheduler.instanceName = MyClusteredScheduler
org.quartz.scheduler.instanceId = AUTO
#============================================================================
# Configure ThreadPool
#============================================================================
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 25
org.quartz.threadPool.threadPriority = 5
#============================================================================
# Configure JobStore
#============================================================================
org.quartz.jobStore.misfireThreshold = 60000
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 20000
如果这时候你运行项目,会报错
java.io.NotSerializableException: Unable to serialize JobDataMap
for insertion into database because the value of property 'methodInvoker'
is not serializable: org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean]
这是因为Quartz集群只支持JDBCJobStore存储方式,而MethodInvokingJobDetailFactoryBean不能序列化存储job数据到数据库.
没办法,只好我们要自己编写个类,继承QuartzJobBean
public class MyDetailQuartzJobBean extends QuartzJobBean {
private Logger logger = Logger.getLogger(MyDetailQuartzJobBean.class);
private String targetObject;
private String targetMethod;
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
try {
logger.error("开始执行>>>>>> [" + targetObject+":"+ targetMethod + "] ");
Object otargetObject = SpringContextUtil.getBean(targetObject);
Method m = null;
try {
m = otargetObject.getClass().getMethod(targetMethod, new Class[] {});
m.invoke(otargetObject, new Object[] {});
} catch (SecurityException e) {
logger.error(e);
} catch (NoSuchMethodException e) {
logger.error(e);
}
} catch (Exception e) {
logger.error(e);
throw new JobExecutionException(e);
}
}
public void setTargetObject(String targetObject) {
this.targetObject = targetObject;
}
public void setTargetMethod(String targetMethod) {
this.targetMethod = targetMethod;
}
}
然后将 MethodInvokingJobDetailFactoryBean 这个类 要换成 JobDetailFactoryBean ,并且引用上面自定义的类,
targetObject和targetMethod 也要换个写法.
整个spring-quartz.xml配置如下
<?xml version="1.0" encoding="UTF-8"?>
<beans default-init-method="init" default-destroy-method="destroy"
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.xsd ">
<!-- 要调用的工作类 -->
<bean id="quartzService" class="com.dingcheng.common.quartz.QuartzService"></bean>
<!-- 定义调用对象和调用对象的方法 -->
<bean id="testJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="jobClass" value = "com.dingcheng.common.quartz.MyDetailQuartzJobBean" />
<property name="jobDataAsMap">
<map>
<entry key="targetObject" value="quartzService" />
<entry key="targetMethod" value="test" />
</map>
</property>
</bean>
<!-- 定义触发时间 -->
<bean id="testTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="jobDetail" ref="testJob" />
<property name="cronExpression" value="*/20 * * * * ?" />
</bean>
<!-- 线程执行器配置,用于任务注册 -->
<bean id="executor"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="10" />
<property name="maxPoolSize" value="100" />
<property name="queueCapacity" value="500" />
</bean>
<!-- 总管理类 -->
<bean id="startQuertz" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="testTrigger" />
</list>
</property>
<property name="taskExecutor" ref="executor" />
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:quartz.properties" />
</bean>
</beans>
2.测试
1)在定时方法中,打印一下路径
public void test(){
logger.error("定时器执行了!"+QuartzService.class.getResource("").getFile());
}
2)把项目放到2个tomcat下,直接启动2个tomcat,2个中有一个会执行,把执行的这个tomcat关掉,另外一个tomcat就自动开始执行.即实现了集群(在linux上面2个tomcat是轮流执行的,windows上是单个执行,挂掉才切换,不知道是偶然还是什么情况).
3)如果你是按照上面的配置文件进行测试, 会发现刚启动的时候,定时器连续执行了好几次.那应该是启动的过程中累计的执行次数一起执行了.为了避免这种情况,需要在总管理类中增加配置
<property name="startupDelay" value="10"/><!-- 延迟加载10秒,即启动后10秒再执行 -->
3.删除任务
因为这个任务是保存到数据库中了, 如果我们想取消某个定时任务怎么办呢?
目前没找到删除的办法,只能从数据库删除.
delete from qrtz_cron_triggers where TRIGGER_NAME = 'test1Trigger' ;
delete from qrtz_triggers where TRIGGER_NAME = 'test1Trigger';
delete from qrtz_job_details where JOB_NAME = 'test1Detail';
org.quartz.plugin.jobInitializer.scanInterval = 10
org.quartz.plugin.jobInitializer.overWriteExistingJobs = true
貌似都是老皇历了, 现在2.2.x已经找不到scanInterval这个属性了. 等我找到再来更新文章.
删除需要手动,更新还是可以配置的, 例如更新执行时间.
需要在总管理类中增加
<property name="overwriteExistingJobs" value="true" />
源码地址:https://code.csdn.net/qq315737546/ssmq-cluster/tree/master