为什么要使用Quzrtz集群

  在项目进行集群部署时,如果业务在执行中存在互斥关系,没有对定时任务进行统一管理,就会引起业务的多次执行,不能满足业务要求。这时就需要对任务进行管理,要保证一笔业务在所有的集群环境中,有且只有一台机器能执行该任务。

  如果不适用Quartz集群,要如何实现这种业务逻辑?

  在这里只列出两种简单的思路:

    1. 利用单线程机制。可以在redis中设置一个属性为空,每次任务执行时去设置这个全局变量,进入任务中需要对值进行校验,值不为空则跳过本次执行任务,值为空时进行设置,方法执行完毕后再将该值置空。
    2. 指定该定时任务在某台固定机器IP上执行,其他Ip则跳过该任务。

  这两种方法都有其弊端,第一种是对代码侵入较大,第二种则是无法做到高可用。

准备工作

    首先在http://www.quartz-scheduler.org/downloads/ 上下载quzrtz包,本文以quartz-2.3.0-distribution.tar.gz 版本为例。下载后解压,选择你需要的数据库进行表创建,本文以oracle为例,顾使用tables_oracle.sql即可。tips:可以直接在解压后 的文件夹里搜索table,就能找到支持的所有类型的数据库脚本。

核心代码实现

  quartz.properties 基本配置属性

 ###############内存版配置####################################
#org.quartz.scheduler.instanceName=spring-boot-quartz-demo
#org.quartz.scheduler.instanceId=AUTO
#org.quartz.threadPool.threadCount=5
#org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
###############集群版配置####################################
org.quartz.scheduler.instanceName=spring-boot-quartz-demo
org.quartz.scheduler.instanceId=AUTO
org.quartz.scheduler.skipUpdateCheck=true
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=5
org.quartz.threadPool.threadPriority=5
org.quartz.jobStore.misfireThreshold=60000
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
#org.quartz.jobStore.dataSource=dataSource
org.quartz.jobStore.tablePrefix=QRTZ_
org.quartz.jobStore.isClustered=true
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
org.quartz.jobStore.useProperties=false
  QuartzJobFactory.java  注入到Spring 容器
package top.enjoyitlife.config;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;
import org.springframework.stereotype.Component; @Component
public class QuartzJobFactory extends SpringBeanJobFactory{
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
Object jobInstance = super.createJobInstance(bundle);
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}

 QuartzConfig.java 配置类  加载配置文件 对quartz进行实例化和属性设置 注意 使用集群方案 需要连接数据库,而内存版则不用配置数据库

package top.enjoyitlife.config;

import java.io.IOException;
import java.util.Properties;
import javax.sql.DataSource;
import org.quartz.spi.JobFactory;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
@Configuration
public class QuartzConfig { public static final String QUARTZ_PROPERTIES_PATH = "/quartz.properties"; @Bean
public JobFactory jobFactory(ApplicationContext applicationContext) {
QuartzJobFactory jobFactory = new QuartzJobFactory();
jobFactory.setApplicationContext(applicationContext);
return jobFactory;
} @Bean
public SchedulerFactoryBean schedulerFactoryBean(JobFactory jobFactory,DataSource dataSource, PlatformTransactionManager transactionManager) throws IOException {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setAutoStartup(true);
factory.setJobFactory(jobFactory);
factory.setQuartzProperties(quartzProperties());
//集群版配置
factory.setDataSource(dataSource);
factory.setTransactionManager(transactionManager);
return factory;
} @Bean
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource(QUARTZ_PROPERTIES_PATH));
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
} }

    QuartzBindOperationConfig.java 定时任务的绑定 包括 触发器  CRON表达式 备注 任务名 任务所在组

package top.enjoyitlife.config;

import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import top.enjoyitlife.schedule.TestSchedule; @Configuration
public class QuartzBindOperationConfig {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private QuartzJobFactory quartzJobFactory;
@Autowired
private Scheduler scheduler;
public void scheduleBind() {
try {
scheduler.setJobFactory(quartzJobFactory);
JobDetail tesJobDetail = JobBuilder.newJob(TestSchedule.class)
.withIdentity("tesJob", "tesJob").withDescription("定时任务demo")
.build();
CronScheduleBuilder tesJobCronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/10 * * * * ?");
CronTrigger tesJobCronTrigger = TriggerBuilder.newTrigger()
.withIdentity("tesJobTrigger", "tesJob")
.withSchedule(tesJobCronScheduleBuilder).build();
scheduler.scheduleJob(tesJobDetail, tesJobCronTrigger);
scheduler.start();
} catch (SchedulerException e) {
// e.printStackTrace();
} } }

  ApplicationListenerConfig.java 项目启动初始化即加载任务

package top.enjoyitlife.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent; @Configuration
public class ApplicationListenerConfig implements ApplicationListener<ContextRefreshedEvent> { private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private QuartzBindOperationConfig quartzBindOperationConfig;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
quartzBindOperationConfig.scheduleBind();
} }

  TestSchedule.java 测试任务演示类

package top.enjoyitlife.schedule;
import java.util.Date;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; @DisallowConcurrentExecution
public class TestSchedule implements Job{ private Logger logger = LoggerFactory.getLogger(getClass()); @Override
public void execute(JobExecutionContext context) throws JobExecutionException {
logger.info("*****"+new Date());
}
}

   POM.xml 引用如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>top.enjoyitlife</groupId>
<artifactId>enjoyitlife</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>quartzDistribute</name>
<description>quartz集群demo</description> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties> <repositories>
<repository>
<id>maven-ali</id>
<url>http://maven.aliyun.com/nexus/content/groups/public//</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
<checksumPolicy>fail</checksumPolicy>
</snapshots>
</repository>
</repositories> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency> <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency> <dependency>
<groupId>org.codehaus.janino</groupId>
<artifactId>janino</artifactId>
</dependency> <!-- 根据本地情况 进行对应调整-->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.4.0</version>
</dependency> <dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency> <dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.5</version>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency> </dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> </project>

运行日志:

2019-05-11 16:11:20.076  INFO 9624 --- [Quartz_Worker-1] top.enjoyitlife.schedule.TestSchedule    : *****Sat May 11 16:11:20 CST 2019
2019-05-11 16:11:30.037 INFO 9624 --- [Quartz_Worker-2] top.enjoyitlife.schedule.TestSchedule : *****Sat May 11 16:11:30 CST 2019
2019-05-11 16:11:40.033 INFO 9624 --- [Quartz_Worker-3] top.enjoyitlife.schedule.TestSchedule : *****Sat May 11 16:11:40 CST 2019

  任务执行后 数据库中就可以看到我对应的任务记录

集群效果验证

修改application.properties 中的server.port端口号,然后重新运行QuartzDistributeApplication的main 方法。以下截图为双服务同时启动时的任务,目前是只有任务1有日志记录,服务2没有任务记录。

停止任务1应用,然后我们会看到任务2 打印以下日志:

【小技巧】

  如果要手动修改集群中的任务CRON 表达式,需要首先修改表QRTZ_CRON_TRIGGERS中的CRON_EXPRESSION表达式,然后在修改表QRTZ_TRIGGERS中的NEXT_FIRE_TIME和PREV_FIRE_TIME的值为0,在下次任务执行后,该任务的执行间隔就会生效。

  如果要手动删除集群中的定时任务,删除表记录顺序如下:QRTZ_CRON_TRIGGERS----->QRTZ_TRIGGERS---->QRTZ_JOB_DETAILS,主要由于存在外键的原因,所以要按顺序删除。

Springboot2.X集成Quartz集群的更多相关文章

  1. Springboot2.x集成Redis集群模式

    Springboot2.x集成Redis集群模式 说明 Redis集群模式是Redis高可用方案的一种实现方式,通过集群模式可以实现Redis数据多处存储,以及自动的故障转移.如果想了解更多集群模式的 ...

  2. Spring集成quartz集群配置总结

    1.spring-quartz.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE be ...

  3. Quartz集群

    为什么选择Quartz: 1)资历够老,创立于1998年,比struts1还早,但是一直在更新(27 April 2012: Quartz 2.1.5 Released),文档齐全. 2)完全由Jav ...

  4. 项目中使用Quartz集群分享--转载

    项目中使用Quartz集群分享--转载 在公司分享了Quartz,发布出来,希望大家讨论补充. CRM使用Quartz集群分享  一:CRM对定时任务的依赖与问题  二:什么是quartz,如何使用, ...

  5. quartz集群报错but has failed to stop it. This is very likely to create a memory leak.

    quartz集群报错but has failed to stop it. This is very likely to create a memory leak. 在一台配置1核2G内存的阿里云服务器 ...

  6. Quartz集群配置

    先看看quartz的持久化基本介绍: 引用 1 大家都清楚quartz最基本的概念就是job,在job内调用具体service完成具体功能,quartz需要把每个job存储起来,方便调度,quartz ...

  7. Quartz集群原理及配置应用

    1.Quartz任务调度的基本实现原理 Quartz是OpenSymphony开源组织在任务调度领域的一个开源项目,完全基于Java实现.作为一个优秀的开源调度框架,Quartz具有以下特点: (1) ...

  8. quartz集群调度机制调研及源码分析---转载

    quartz2.2.1集群调度机制调研及源码分析引言quartz集群架构调度器实例化调度过程触发器的获取触发trigger:Job执行过程:总结:附: 引言 quratz是目前最为成熟,使用最广泛的j ...

  9. (4) Spring中定时任务Quartz集群配置学习

    原 来配置的Quartz是通过spring配置文件生效的,发现在非集群式的服务器上运行良好,但是将工程部署到水平集群服务器上去后改定时功能不能正常运 行,没有任何错误日志,于是从jar包.JDK版本. ...

随机推荐

  1. JavaScript实现按键记录,并在关掉网页之前把记录的内容post出去

    最近陈老师让我给新架构加一个按键记录的业务.去学习了JavaScript,网上找了一些代码,最后写出来了: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTM ...

  2. 截图工具,更改系统默认快捷键,系统配置实用程序,以管理员身份运行cmd(win7)

    截图工具: 开始--附件--右键发送到桌面快捷方式---桌面截图工具右键属性--快捷方式 更改系统默认快捷键: 控制面板--外观和个性化--调整屏幕分辨率--高级设置---英特尔图形和媒体控制面板-- ...

  3. 前端开发 --- CSS参考手册

    目录 1 选择器 1.1 通用选择器 1.2 层次选择器 1.3 伪类选择器 1.3.1 动态伪类选择器 1.3.2 目标伪类选择器 1.3.3 UI元素状态伪类选择器 1.3.4 结构伪类选择器 1 ...

  4. python下载各大主流视频网站电影

    You-Get 是一个命令行工具, 用来下载各大视频网站的视频, 是我目前知道的命令行下载工具中最好的一个, 之前使用过 youtube-dl, 但是 youtube-dl 吧, 下载好的视频是分段的 ...

  5. mac上Firefox安装firebug和firepath

    Firefox安装Selenium ide插件时提示:该附加组件无法安装 因为它似乎已损坏,如下图: 此时千万不要去想着这个插件有问题,可能是Firefox的版本问题. 在Firefox45之后的新版 ...

  6. github新手使用

    1.首先要先在github的官网注册一个属于自己的账号.https://github.com/ 2.注册完成后需要一些简单的设置,先创建一个属于自己的仓库,repository 3.创建仓库 4.如何 ...

  7. Unity(1) 编辑器结构

    5个主要视图视图,未完,持续补充... 1 Project(项目) 用于显示资源 Favorites:按资源分类显示 Assets:按目录结构显示 特殊说明 Prefabs(预设),用于场景中游戏对象 ...

  8. euler证明

    我们用g(x)表示x的欧拉函数值,即1~x与x互质的数的个数 欧拉函数公式为: g(x)= y*((x1-1)/x1)*((x2-1)/x2)*((x3-1)/x3)....(其中x1, x2, x3 ...

  9. 进击python第4篇:初探模块

    模块,用一砣代码实现了某个功能的代码集合,任何python程序都可以作为模块导入,n个 .py 文件组成的代码集合就称为模块. but 为什么要引入模块概念?主要原因是代码重用(code reuse) ...

  10. TyvjP1863 [Poetize I]黑魔法师之门(2014-8-27)

    P1863 [Poetize I]黑魔法师之门 时间: 1000ms / 空间: 131072KiB / Java类名: Main 背景 经过了16个工作日的紧张忙碌,未来的人类终于收集到了足够的能源 ...