Springboot--整合定时任务quartz--集群篇

文章目录

  • 前言
  • 一、quartz 的集群:
    • 1.1 服务集群带来的定时任务问题:
    • 1.2 服务集群定时任务解决思路:
  • 二、quartz 集群实现:
    • 2.1 引入jar
    • 2.2 配置文件:
    • 2.3 定义quartz 数据源:
    • 2.4 集群测试:
      • 2.4.1 定时任务:
      • 2.4.2 初始化quartz 表:
      • 2.4.3 服务启动:
      • 2.4.3 启动多个服务:
        • 2.4.3.1 initialize-schema 修改:
        • 2.4.3.2 从数据库加载任务:
        • 2.4.3.3 增加配置文件:
        • 2.4.3.4 修改配置项:
        • 2.4.3.5 故障转移:
  • 总结:
  • 参考:


前言

通常在生产环境中,不会存在单体的应用,如一个订单服务,可能同时部署多个相同的服务到不同的服务上,从而形成集群。此时定时任务就会面临重复执行的问题


一、quartz 的集群:

1.1 服务集群带来的定时任务问题:

在这里插入图片描述
业务系统是集群的 共用一个quartz 的数据库,存在的问题:

  • 任务被分配到多个系统中:而任务的数据是有状态的重复执行,导致业务数据重复;
  • 任务状态是无状态,多次执行对最终数据无影响:

显然对于有状态的数据并不能重复执行,比如扣款,转账等等;对于无状态的数据重复执行也没有意义,只是增加了资源的开销而已;

1.2 服务集群定时任务解决思路:

  • 只让集群中的一台服务器去跑定时任务(浪费其他机器的性能)
    zookeeper 对集群的相同服务选举出来一个master 节点进行任务的执行:

  • 集群中的每台服务器都会执行定时任务,但是一个任务只会分配到集群中的一台机器上:
    quartz 支持;将定时任务放在jdbc 中进行存储,从而实现 故障转移,和负载均衡:
    在这里插入图片描述
    故障转移:
    一个任务在一台机器上跑但是 这台机器下线;将任务分配到第二台机器执行(可以自行配置);
    负载均衡:
    将任务尽可能分配到各个服务中,但是每个任务只会被分配一次;

二、quartz 集群实现:

2.1 引入jar

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.1.1</version></dependency><!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.21</version></dependency>

tip :本文springboot 版本如下:

 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.16</version><relativePath/> <!-- lookup parent from repository --></parent>

对应自动引入的quartz 版本为: org.quartz-scheduler:quartz:2.3.2

2.2 配置文件:

application.yml

server:port: 8080
spring:datasource:type: com.zaxxer.hikari.HikariDataSource  #数据源类型hikari:pool-name: KevinHikariPool  #连接池名称,默认HikariPool-1maximum-pool-size: 20   #最大连接数,小于等于0会被重置为默认值10;大于零小于1会被重置为minimum-idle的值connection-timeout: 60000 #连接超时时间:毫秒,小于250毫秒,否则被重置为默认值30秒minimum-idle: 10  #最小空闲连接,默认值10,小于0或大于maximum-pool-size,都会重置为maximum-pool-sizeidle-timeout: 500000   # 只有空闲连接数大于最大连接数且空闲时间超过该值,才会被释放max-lifetime: 600000   #连接最大存活时间.不等于0且小于30秒,会被重置为默认值30分钟.设置应该比mysql设置的超时时间短connection-test-query: SELECT 1   #连接测试查询quartz:driver-class-name: com.mysql.cj.jdbc.Driverjdbc-url: jdbc:mysql://localhost:3406/quartz-oneself?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useAffectedRows=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2B8username: rootpassword: 123456quartz:# 配置使用jdbc 存储jobjob-store-type: jdbc#  随着容器启动,启动定时任务(默认值ture)auto-startup: true# 是否可以覆盖定时任务,true 是 (默认值false)overwrite-existing-jobs: false# 在容器关闭时,任务执行后关闭容 (默认值false)wait-for-jobs-to-complete-on-shutdown: true# 定时任务延时启动的时间 (默认值0s)startup-delay: 10sproperties:# 配置定时任务执行的线程池个数(默认10个)org.quartz.threadPool.threadCount: 10# 配置集群的名称,同一个集群内的多个服务需要保证名称一致org.quartz.scheduler.instanceName: OrderService# 集群中单个服务的实例id ,同一个集群中 实例id 需要不相同org.quartz.scheduler.instanceId: Order_0# 标识以集群的方式启动org.quartz.jobStore.isClustered: true# 存储job 时使用的事务管理类,注意改参数 不同版本设置的值 有差异org.quartz.jobStore.class: org.springframework.scheduling.quartz.LocalDataSourceJobStore# 数据库驱动,用来匹配不同数据的实现类org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate# quartz集群 定时任务集群表前缀org.quartz.jobStore.tablePrefix: QRTZ_# 容许的调度引擎设置触发器超时的"临界值"。任务的超时容忍度 默认为60秒(这里单位为毫秒)org.quartz.jobStore.misfireThreshold: 12000jdbc:
#      initialize-schema: alwaysinitialize-schema: never

注意:
(1)org.quartz.jobStore.class 低版本、高版本取值不同:
低版本:2.2.6.Release
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX

高版本:2.5.x- 2.7.18
org.quartz.jobStore.class=org.springframework.scheduling.quartz.LocalDataSourceJobStore
避免错误信息:org.quartz.SchedulerConfigException: DataSource name not set.

(2) org.quartz.jobStore.tablePrefix 定时任务集群表前缀,需要遵从一定规范:
在这里插入图片描述

qrtz_ 后的内容不能进行修改

2.3 定义quartz 数据源:

QuartzDataSourceConfig.java:

@Configuration
public class QuartzDataSourceConfig {@Value("${spring.datasource.quartz.jdbc-url}")private String url;@Value("${spring.datasource.quartz.driver-class-name}")private String driverClassName;@Value("${spring.datasource.quartz.username}")private String username;@Value("${spring.datasource.quartz.password}")private String password;@Autowiredprivate HikariBaseConfig hikariBaseConfig;@Bean// 标识quartz 数据源@QuartzDataSource@Qualifier("quartzDataSource")public DataSource quartzDataSource() {return hikariBaseConfig.getDataSource(driverClassName, url, username, password);}
}

2.4 集群测试:

2.4.1 定时任务:

业务类 HelloService:

@Service
public class HelloService {public String hello(){return "hello";}
}

业务类QuartzTest:

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.StringJoiner;public class QuartzTest extends QuartzJobBean {@Autowiredprivate HelloService helloService;@Overrideprotected void executeInternal(JobExecutionContext context) throws JobExecutionException {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//        System.out.println("\"job 执行\" = " + "job 执行" + sdf.format(new Date()));StringJoiner outStr = new StringJoiner("").add("QuartzTest 执行").add(sdf.format(new Date())).add(Thread.currentThread().getName()).add(context.getTrigger().getKey().getName()).add(helloService.toString()).add(helloService.hello());System.out.println(outStr);}
}

业务类 JobConfigure:


import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class JobConfigure {@Beanpublic JobDetail jobDetail1() {return JobBuilder.newJob(QuartzTest.class).usingJobData("job", "jobDetail").usingJobData("name", "jobDetail").usingJobData("count", 0).storeDurably().withIdentity("jobConfigure", "group1").build();}@Beanpublic Trigger trigger1() {return TriggerBuilder.newTrigger().withIdentity("triggerConfigure", "trigger1").forJob("jobConfigure","group1").withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever()).usingJobData("trigger", "trigger").usingJobData("name", "trigger").build();}@Beanpublic JobDetail jobDetail2() {return JobBuilder.newJob(QuartzTest.class).usingJobData("job", "jobDetail2").usingJobData("name", "jobDetail2").usingJobData("count", 0).storeDurably().withIdentity("jobConfigure2", "group1").build();}@Beanpublic Trigger trigger2() {return TriggerBuilder.newTrigger().withIdentity("triggerConfigure2", "trigger2").forJob("jobConfigure2","group1").withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever()).usingJobData("trigger", "trigger2").usingJobData("name", "trigger2").build();}
}

2.4.2 初始化quartz 表:

修改 application.yml 中 initialize-schema 为always ,让其可以帮忙在数据库中创建对应的表:

initialize-schema: always

2.4.3 服务启动:

在这里插入图片描述

可以看到服务是以集群方式启动的,并且对应的表已经完成了创建:
在这里插入图片描述
定时任务已经开始执行:
在这里插入图片描述

2.4.3 启动多个服务:

2.4.3.1 initialize-schema 修改:

修改 application.yml 中 initialize-schema 为never:

initialize-schema: never
2.4.3.2 从数据库加载任务:

JobConfigure 去除@Configuration 注解,后续让项目从数据库中加载任务;

2.4.3.3 增加配置文件:

application-1.yml:

spring:quartz:properties:org.quartz.scheduler.instanceName: OrderServiceorg.quartz.scheduler.instanceId: Order_1org.quartz.jobStore.isClustered: trueorg.quartz.threadPool.threadCount: 3org.quartz.jobStore.class: org.springframework.scheduling.quartz.LocalDataSourceJobStoreorg.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegateorg.quartz.jobStore.tablePrefix: QRTZ_org.quartz.jobStore.misfireThreshold: 12000
server:port: 8081

application-2.yml

spring:quartz:properties:org.quartz.scheduler.instanceName: OrderServiceorg.quartz.scheduler.instanceId: Order_2org.quartz.jobStore.isClustered: trueorg.quartz.threadPool.threadCount: 3org.quartz.jobStore.class: org.springframework.scheduling.quartz.LocalDataSourceJobStoreorg.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegateorg.quartz.jobStore.tablePrefix: QRTZ_org.quartz.jobStore.misfireThreshold: 12000
server:port: 8082
2.4.3.4 修改配置项:

在这里插入图片描述
点击 Edit Contigurations 配置选项:
在这里插入图片描述
选中需要启动多个实例的项目然后,点击 进行复制:
在这里插入图片描述
修改配置名称,然后在 Active: profiles 配置要生效的 配置文件,此处和 application-1.yml ,“-” 后面的内容保持一致;如果是 application-dev.yml ,那么此处填写的值为"dev";
在这里插入图片描述
选中配置文件后,启动项目; 注意项目启动时 ,也会去加载 application.yml 配置,如果配置相同 application-1.yml 会进行覆盖;

2.4.3.5 故障转移:

当正在执行任务的服务停掉后,后将任务转移至另外一个正常服务中:

在这里插入图片描述


总结:

quartz 的集群需要同一个服务的不同实例,都要连接到同一个 定时任务的数据源,并且通过 org.quartz.jobStore.isClustered: true 开启 集群,以实现定时任务的负载均衡和故障转移。

参考:

1 Quartz 配置参数详解;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/703858.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【管理咨询宝藏资料25】某能源集团五年发展战略报告

本报告首发于公号“管理咨询宝藏”&#xff0c;如需阅读完整版报告内容&#xff0c;请查阅公号“管理咨询宝藏”。 【管理咨询宝藏资料25】某能源集团五年发展战略报告 【关键词】战略规划、五年战略、管理咨询 【文件核心观点】 - LL应以快速做大做强为目标&#xff0c;专注…

百能正式加入星闪联盟,助力无线通信技术发展

星闪联盟于2020年9月22日正式成立&#xff0c;是一个由国家级标准研究机构、行业领军企业、产业链合作伙伴等组成的开放式合作平台。该联盟致力于推动新一代无线短距通信技术SparkLink的创新和产业生态发展&#xff0c;以满足智能汽车、智能家居、智能终端和智能制造等快速发展…

Escalate_Linux-环境变量劫持提权(5)

环境变量劫持提权 在Shll输入命令时&#xff0c;Shel会按PAH环境变量中的路径依次搜索命令&#xff0c;若是存在同名的命令&#xff0c;则执行最先找到的&#xff0c;若是PATH中加入了当前目录&#xff0c;也就是“”这个符号&#xff0c;则可能会被黑客利用&#xff0c;例如在…

linux操作系统期末练习题

背景&#xff1a; 一、远程登录 1&#xff0e;利用远程登录软件&#xff0c;以用户userManager(密码123456)&#xff0c;远程登录教师计算机&#xff08;考试现场给出IP地址&#xff09;&#xff0c;只有操作&#xff0c;没有命令。 2&#xff0e;以stu班级学生个人学号后3位…

Webserver解决segmentation fault(core dump)段错问问题

前言 在完成了整个项目后&#xff0c;我用make命令编译了server&#xff0c;当我运行./server文件时&#xff0c;出现了段错误 在大量的代码中找出错因并不是一件容易的事&#xff0c;尤其是对新手程序员来说。而寻找bug的过程就像是侦探调查线索追查凶手一样&#xff0c;我们…

【软件测试】--功能测试2--常用设计测试用例方法

一、解决穷举场景 重点&#xff1a;使用等价类划分法 1.1 等价类划分法 重点&#xff1a;有效等价和单个无效等价各取1个即可。 步骤&#xff1a;1、明确需求2、确定有效和无效等价3、根据有效和无效造数据编写用例 1.2 案例&#xff08;qq合法验证&#xff09; 需求&#xff…

vue中循环多个li(表格)并获取对应的ref

有种场景是这样的 <ul><li v-for"(item,index) in data" :key"index" ref"???">{{item}}</li> </ul> //key值在项目中别直接用index&#xff0c;最好用id或其它关键值const data [1,2,3,4,5,6]我想要获取每一个循环并…

外包干了3个月,技术倒退明显...

先说情况&#xff0c;大专毕业&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了接近6年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试&#xf…

嵌入式Qt 实现用户界面与业务逻辑分离

一.基本程序框架一般包含 二.框架的基本设计原则 三.用户界面与业务逻辑的交互 四.代码实现计算器用户界面与业务逻辑 ICalculator.h #ifndef _ICALCULATOR_H_ #define _ICALCULATOR_H_#include <QString>class ICalculator { public:virtual bool expression(const QSt…

人脸2D和3D道具SDK解决方案提供商

人脸识别和增强现实技术成为了许多企业和开发者关注的焦点&#xff0c;为了满足市场对高质量、易于集成的人脸识别SDK的需求&#xff0c;美摄科技推出了一系列领先的人脸2D/3D道具SDK解决方案。 一、产品特点 高精度识别&#xff1a;美摄科技的人脸识别技术采用深度学习算法&…

C++——二叉搜索树

二叉搜索树 二叉搜索树&#xff1a; 又为搜索二叉树&#xff0c;一般具有以下的性质 若它的左子树不为空&#xff0c;则左子树上所有的节点的值都小于父亲节点若它的右子树不为空&#xff0c;则右子树上所有的节点的值都大于父亲节点它的左右子树也都为二叉搜索树 二叉搜索树…

漏电保护继电器 导轨安装 零序电流互感器配套使用DJ-ZB1 DH-30L

系列型号&#xff1a; DJ-ZB1剩余&#xff08;漏电&#xff09;电流保护继电器 DJ-ZB2剩余&#xff08;漏电&#xff09;电流保护继电器 DJ-ZB3剩余&#xff08;漏电&#xff09;电流保护继电器 DJ-ZB4剩余&#xff08;漏电&#xff09;电流保护继电器 DJ-ZB5剩余&#xff08;漏…

pytorch保存张量为图片

这里用到的是torchvision中的save_image。 废话不多说&#xff0c;直接来代码&#xff1a; import torch from torchvision.utils import save_image B, C, H, W 64, 3, 32, 32 input_tensor torch.randn(B, C, H, W) save_image(input_tensor, "hh.png", nrow8)…

Shell脚本入门:从基础到实践,轻松掌握Shell编程

前言 在数字化和信息化的今天&#xff0c;计算机和操作系统成为了我们生活和工作中不可或缺的一部分。对于经常使用计算机的人来说&#xff0c;Shell&#xff08;命令行界面&#xff09;是一个非常重要的工具。而Shell脚本&#xff0c;则是对命令行操作的一种自动化和批量化处…

stm32:timer模块,如何计数,计数模块很简单,但是需要注意分频的设置,分频设置为7199,

首先看配置项 计数模块很简单&#xff0c;但是需要注意分频的设置&#xff0c;分频设置为7199&#xff0c; 然后计数寄存器里的值65535作为默认值&#xff0c;也可以在matlab里修改 下图为配置项目&#xff1a; 下图为matlab模型&#xff1a; 下图为运行结果&#xff1a; 计…

python(ch2)

可变长编码和不可变长编码 可变长编码是指不同字符使用不同数量的字节进行编码。例如&#xff0c;UTF-8 编码中&#xff0c;ASCII 字符使用 1 个字节编码&#xff0c;而其他语言的字符使用 2 个或更多字节编码。 不可变长编码是指所有字符都使用相同数量的字节进行编码。例如…

甲氧基 PEG4 二苯并环辛烯,mPEG4 DBCO,可以与多种基团发生反应

甲氧基四聚乙二醇二苯并环辛烯&#xff0c;甲氧基 PEG4 二苯并环辛烯&#xff0c;mPEG4 DBCO&#xff0c;DBCO mPEG4&#xff0c;m-PEG4-DBCO&#xff0c;mPEG4-DBCO&#xff0c;可以与多种基团发生反应 您好&#xff0c;欢迎来到新研之家 文章关键词&#xff1a;甲氧基四聚乙…

【Python_Zebra斑马打印机编程学习笔记(三)】解决ZPL指令无法显示中文的问题

解决ZPL指令无法显示中文的问题 解决ZPL指令无法显示中文的问题前言一、问题描述二、字符集、码表文件、字库文件1、字符集2、码表文件3、字库文件 三、两种设置中文字体的方式1、通过设置字符集、码表文件、字库文件改变默认字体2、通过^CF指令设置标准字体名称改变默认字体 解…

Linux命令行常用命令

初识shell shell是系统的用户界面&#xff0c;提供了用户与内核进行交互操作的一种接口。它接收用户输入的命令并把它送入内核去执行。实际上shell是一个命令解释器&#xff0c;它解释用户输入的命令并且把用户的意图传达给内核。&#xff08;可以理解为用户与内核之间的翻译官…

如何本地部署LightPicture结合cpolar内网穿透打造个人云图床

文章目录 1.前言2. Lightpicture网站搭建2.1. Lightpicture下载和安装2.2. Lightpicture网页测试2.3.cpolar的安装和注册 3.本地网页发布3.1.Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1.前言 现在的手机越来越先进&#xff0c;功能也越来越多&#xff0c;而手机…