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-1
      maximum-pool-size: 20   #最大连接数,小于等于0会被重置为默认值10;大于零小于1会被重置为minimum-idle的值
      connection-timeout: 60000 #连接超时时间:毫秒,小于250毫秒,否则被重置为默认值30秒
      minimum-idle: 10  #最小空闲连接,默认值10,小于0或大于maximum-pool-size,都会重置为maximum-pool-size
      idle-timeout: 500000   # 只有空闲连接数大于最大连接数且空闲时间超过该值,才会被释放
      max-lifetime: 600000   #连接最大存活时间.不等于0且小于30秒,会被重置为默认值30分钟.设置应该比mysql设置的超时时间短
      connection-test-query: SELECT 1   #连接测试查询
    quartz:
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://localhost:3406/quartz-oneself?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useAffectedRows=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2B8
      username: root
      password: 123456
  quartz:
  	# 配置使用jdbc 存储job
    job-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: 10s
    properties:
      # 配置定时任务执行的线程池个数(默认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: 12000

    jdbc:
#      initialize-schema: always
      initialize-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;

    @Autowired
    private 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 {
    @Autowired
    private HelloService helloService;
    @Override
    protected 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 {

    @Bean
    public JobDetail jobDetail1() {
        return JobBuilder.newJob(QuartzTest.class)
                .usingJobData("job", "jobDetail")
                .usingJobData("name", "jobDetail")
                .usingJobData("count", 0)
                .storeDurably()
                .withIdentity("jobConfigure", "group1").build();
    }

    @Bean
    public Trigger trigger1() {
        return TriggerBuilder.newTrigger()
                .withIdentity("triggerConfigure", "trigger1")
                .forJob("jobConfigure","group1")
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1)
                        .repeatForever())
                .usingJobData("trigger", "trigger")
                .usingJobData("name", "trigger")
                .build();
    }

    @Bean
    public JobDetail jobDetail2() {
        return JobBuilder.newJob(QuartzTest.class)
                .usingJobData("job", "jobDetail2")
                .usingJobData("name", "jobDetail2")
                .usingJobData("count", 0)
                .storeDurably()
                .withIdentity("jobConfigure2", "group1").build();
    }

    @Bean
    public 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: OrderService
      org.quartz.scheduler.instanceId: Order_1
      org.quartz.jobStore.isClustered: true
      org.quartz.threadPool.threadCount: 3
      org.quartz.jobStore.class: org.springframework.scheduling.quartz.LocalDataSourceJobStore
      org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
      org.quartz.jobStore.tablePrefix: QRTZ_
      org.quartz.jobStore.misfireThreshold: 12000
server:
  port: 8081

application-2.yml

spring:
  quartz:
    properties:
      org.quartz.scheduler.instanceName: OrderService
      org.quartz.scheduler.instanceId: Order_2
      org.quartz.jobStore.isClustered: true
      org.quartz.threadPool.threadCount: 3
      org.quartz.jobStore.class: org.springframework.scheduling.quartz.LocalDataSourceJobStore
      org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate
      org.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 配置参数详解;

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

介绍 CI / CD

目录 一、介绍 CI / CD 1、为什么要 CI / CD 方法简介 1、持续集成 2、持续交付 3、持续部署 2、GitLab CI / CD简介 3、GitLab CI / CD 的工作原理 4、基本CI / CD工作流程 5、首次设置 GitLab CI / CD 6、GitLab CI / CD功能集 一、介绍 CI / CD 在本文档中&#x…

【Pytorch深度学习开发实践学习】B站刘二大人课程笔记整理lecture07多维输入

lecture07多维输入 课程网址 Pytorch深度学习实践 部分课件内容&#xff1a; import torch import numpy as npxy np.loadtxt(diabetes.csv.gz, delimiter,, dtypenp.float32) x_data torch.from_numpy(xy[:,:-1]) #第一列开始最后一列不要 y_data torch.from_numpy(…

【Python_Zebra斑马打印机编程学习笔记(一)】实现标贴预览的两种方式

实现标贴预览的两种方式 实现标贴预览的两种方式前言一、调用 Labelary Online ZPL Viewer API 方法实现标贴预览功能1、Labelary Online ZPL Viewer API 案例介绍2、生成 PNG 格式3、Parameters 二、通过 zpl 的 label.preview() 方法实现标贴预览功能1、实现步骤2、代码示例 …

gitlab,从A仓库迁移某个工程到B仓库,保留提交记录

从A仓库&#xff0c;拷贝 git clone --bare ssh://git192.168.30.88:22/framework/platform.git 在B仓库新建工程&#xff0c;注意&#xff1a;一定要去掉默认的生成README文件进入platform.git 文件夹下&#xff0c;推送到B仓库 git push --mirror ssh://git192.168.30.100…

怎么用sora赚第一桶金?

&#x1f31f;解锁文字变视频的强大功能&#xff01;&#x1f31f; ✨欢迎来到 Sora Cand&#xff0c;一个革命性的网站&#xff0c;利用 OpenAI 的 Sora 模型帮你把文字变成酷炫的视频&#xff01;✨ 想象一下&#xff0c;你的文字从纸上跳出来&#xff0c;变成引人入胜的视觉…

全志T527国产核心板及米尔配套开发板批量上市!

2023年12月&#xff0c;米尔电子联合战略合作伙伴全志科技&#xff0c;率先业内发布了国产第一款T527核心板及开发板。这款高性能、高性价比、八核A55的国产核心板吸引了广大客户关注&#xff0c;为积极响应客户需求&#xff0c;米尔基于全志T527核心板现已批量上市&#xff0c…

RabbitMQ 部署方式选择

部署模式 RabbitMQ支持多种部署模式&#xff0c;可以根据应用的需求和规模选择适合的模式。以下是一些常见的RabbitMQ部署模式&#xff1a; 单节点模式&#xff1a; 最简单的部署方式&#xff0c;所有的RabbitMQ组件&#xff08;消息存储、交换机、队列等&#xff09;都运行在…

Java项目:21 基于SSM实现的图书借阅管理系统

作者主页&#xff1a;舒克日记 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 基于SSM实现的图书借阅管理系统设计了两个角色&#xff0c;分别是管理员、用户&#xff0c;在数据表user中以ident字段区分&#xff0c;为1表示管理员…

Math方法,以及三角函数计算

abs(x) 返回参数的绝对值 var xMath.abs(-5) //5floor(x) 向下舍入为最接近的整数。 var xMath.floor(2.1) //2ceil(x) 向上舍入为最接近的整数。 var xMath.ceil(2.1) //3fround(x) 最接近的&#xff08;32 位单精度&#xff09;浮点表示。 var xMath.fround(2.60) //2.59…

企业动态|上海航空工业集团殷舜晖部长一行到访同创永益

1月24日上午&#xff0c;中国商飞上海航空工业集团采购中心殷舜晖部长一行4人到访同创永益北京总部。同创永益COO马青山、营销副总经理刘翔、总经办主任田东陪同参观&#xff0c;并介绍了公司的发展历程与近年来的突出成绩。 在随后的会议中&#xff0c;马青山向殷舜晖部长一行…

AppBox快速开发框架(开源)开发流程介绍

目前很多低代码平台都是基于Web用拖拽方式生成界面&#xff0c;确实可以极大的提高开发效率&#xff0c;但也存在一些问题&#xff1a; 大部分平台灵活性不够&#xff0c;特殊需求需要较大的自定义开发&#xff1b; 解析json配置的执行效率不是太高&#xff1b; 大部分平台缺…

统计图雷达图绘制方法

统计图雷达图绘制方法 常用的统计图有条形图、柱形图、折线图、曲线图、饼图、环形图、扇形图。 前几类图比较容易绘制&#xff0c;饼图环形图绘制较难。 还有一种雷达图的绘制也较难&#xff0c;今提供雷达图的绘制方法供参考。 本方法采用C语言的最基本功能&#xff1a; &am…

k8s(2)

目录 一.二进制部署k8s 常见的K8S安装部署方式&#xff1a; k8s部署 二进制与高可用的区别 二.部署k8s 初始化操作&#xff1a; 每台node安装docker&#xff1a; 在 master01 节点上操作; 准备cfssl证书生成工具:&#xff1a; 执行脚本文件&#xff1a; 拉入etcd压缩包…

【目标检测新SOTA!v7 v4作者新作!】YOLO v9 思路复现 + 全流程优化

YOLO v9 思路复现 全流程优化 提出背景&#xff1a;深层网络的 信息丢失、梯度流偏差YOLO v9 设计逻辑可编程梯度信息&#xff08;PGI&#xff09;&#xff1a;使用PGI改善训练过程广义高效层聚合网络&#xff08;GELAN&#xff09;&#xff1a;使用GELAN改进架构 对比其他解法…

Airtest-Selenium实操小课③:下载可爱猫猫图片

1. 前言 那么这周我们看看如何实现使用Airtest-Selenium实现自动搜索下载可爱的猫猫图片吧~ 2. 需求分析和准备 整体的需求大致可以分为以下步骤&#xff1a; 打开chrome浏览器 打开百度网页 搜索“可爱猫猫图片” 定位图片元素 创建存储图片的文件夹 下载可爱猫猫图片…

SpringBoot中Redis缓存的使用

目录 1 前言 2 实现方法 2.1 查询数据时 2.2 修改数据 1 前言 对于一些不常改变&#xff0c;但又经常查询的数据&#xff0c;我们可以使用Redis缓存&#xff0c;来缓解数据库的压力&#xff0c;其中的逻辑如下&#xff1a; 2 实现方法 2.1 查询数据时 一般在控制类查询方…

普中51单片机(DS18B20温度传感器)

DS18B20温度传感器原理 内部结构 64位(激)光刻只读存储器 光刻ROM中的64位序列号是出厂前被光刻好的&#xff0c;它可以看作是该DS18B20的地址序列号。64位光刻ROM的排列是&#xff1a;开始8位&#xff08;28H&#xff09;是产品类型标号&#xff0c;接着的48位是该DS18B20自身…

推荐系统经典模型YouTubeDNN

文章目录 YouTubeDNN概念YouTubeDNN模型架构图YouTubeDNN召回阶段YouTubeDNN层级介绍 YouTubeDNN排序阶段YoutubeDNN模型中的一些Trick负采样问题特征构造上下文选择 总结 YouTubeDNN概念 YouTubeDNN是YouTube用于做视频推荐的落地模型&#xff0c;其大体思路就是召回阶段使用…

Kubernetes 二进制部署 《easzlab / kubeasz项目部署》(一)

Kubernetes 二进制部署 - easzlab / kubeasz项目部署 1. 准备工作1.1 设置防火墙1.2 设置SeLinux1.3 设置时区及时间同步1.4 配置域名解析1.5 确认SSH开启1.6 IP转发1.7 安装docker1.8 关闭swap 2. 服务器规划2.1 基本架构图2.2 官方建议2.3 实践服务器规划 3. 服务器配置3.1 配…

Airtest遇到模拟器无法输入中文的情况该如何处理?

1. 前言 最近有收到同学们的一些提问&#xff0c;使用Airtest的 text 接口&#xff0c;发现在部分模拟器上&#xff0c; text 无法输入中文&#xff0c;不知道该怎么处理。 今天我们就输入这个小问题&#xff0c;来详细聊一下。 2. Airtest的输入法简介 对于Android设备来说…