Springboot整合Quartz集群部署以及配置Druid数据源

一句话概括Quartz:

Quart分布式调度任务是通过数据库实现的,抢占式调度,一个任务只能在一个节点上执行,他的集群也仅仅是解决了单点故障(任务级别),实现了高可用,多个任务在集群中负载均衡调度,并没有解决任务分片的问题,不能实现水平扩展,如果执行大量的短任务,各个节点频繁的竞争数据库锁,节点越多这种情况越严重,性能会很低下。

版本为:SpringBoot 2.x,Quartz 2.3.0,Durid 1.1.9

这里不再使用c3p0数据源,在boot2.x中spring-boot-starter-quartz依赖默认是不依赖c3p0数据源的,如果要使用需要自己单独引用c3p0数据源,在quartz.properties配置下就可以了,这里使用的Druid数据源。

首先引入依赖:

        <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-quartz</artifactId>
        </dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.43</version>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
			<version>1.1.9</version>
		</dependency>

Druid-spring-boot-starterhttps://github.com/alibaba/druid/tree/master/druid-spring-boot-starter

application.yml文件配置,这里面配置的Druid数据源相关的,没有配置其他的,很单纯的

server:
  port: 8080
spring:
    application:
        name: quartz
    datasource:
        url: jdbc:mysql://192.168.184.135:3306/quartzdb?characterEncoding=utf-8&useUnicode=true&useSSL=false
        driver-class-name: com.mysql.jdbc.Driver  # mysql8.0以前使用com.mysql.jdbc.Driver
        username: root
        password: 123456
        platform: mysql
        #通过这句配置将druid连接池引入到我们的配置中,spring会尽可能判断类型是什么,然后根据情况去匹配驱动类。
        type: com.alibaba.druid.pool.DruidDataSource
        druid:
            initial-size: 5  # 初始化大小
            min-idle: 5  # 最小
            max-active: 100  # 最大
            max-wait: 60000  # 配置获取连接等待超时的时间
            time-between-eviction-runs-millis: 60000  # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
            min-evictable-idle-time-millis: 300000  # 指定一个空闲连接最少空闲多久后可被清除,单位是毫秒
            validationQuery: select 'x'
            test-while-idle: true  # 当连接空闲时,是否执行连接测试
            test-on-borrow: false  # 当从连接池借用连接时,是否测试该连接
            test-on-return: false  # 在连接归还到连接池时是否测试该连接
            filters: config,wall,stat  # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
            poolPreparedStatements: true # 打开PSCache,并且指定每个连接上PSCache的大小
            maxPoolPreparedStatementPerConnectionSize: 20
            maxOpenPreparedStatements: 20
            # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
            connectionProperties: druid.stat.slowSqlMillis=200;druid.stat.logSlowSql=true;config.decrypt=false
             # 合并多个DruidDataSource的监控数据
            #use-global-data-source-stat: true
            #WebStatFilter配置,说明请参考Druid Wiki,配置_配置WebStatFilter
            web-stat-filter:
                enabled: true #是否启用StatFilter默认值true
                url-pattern: /*
                exclusions: /druid/*,*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico
                session-stat-enable: true
                session-stat-max-count: 10
            #StatViewServlet配置,说明请参考Druid Wiki,配置_StatViewServlet配置
            stat-view-servlet:
                enabled: true #是否启用StatViewServlet默认值true
                url-pattern: /druid/*
                reset-enable: true
                login-username: admin
                login-password: admin

quartz.properties配置文件

#调度标识名 集群中每一个实例都必须使用相同的名称
org.quartz.scheduler.instanceName = quartzScheduler
#调度器实例编号自动生成,每个实例不能不能相同
org.quartz.scheduler.instanceId = AUTO
#开启分布式部署,集群
org.quartz.jobStore.isClustered = true
#分布式节点有效性检查时间间隔,单位:毫秒,默认值是15000
org.quartz.jobStore.clusterCheckinInterval = 2000
#远程管理相关的配置,全部关闭
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false
#实例化ThreadPool时,使用的线程类为SimpleThreadPool(一般使用SimpleThreadPool即可满足几乎所有用户的需求)
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
##并发个数,指定线程数,至少为1(无默认值)(一般设置为1-100之间的的整数合适)
org.quartz.threadPool.threadCount = 10
##设置线程的优先级(最大为java.lang.Thread.MAX_PRIORITY 10,最小为Thread.MIN_PRIORITY 1,默认为5)
org.quartz.threadPool.threadPriority = 5
#org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
#容许的最大作业延长时间,最大能忍受的触发超时时间,如果超过则认为“失误”,不敢再内存中还是数据中都要配置
org.quartz.jobStore.misfireThreshold = 6000
#持久化方式配置
# 默认存储在内存中,保存job和Trigger的状态信息到内存中的类
#org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
#数据库方式
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
#持久化方式配置数据驱动,MySQL数据库
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#quartz相关数据表前缀名
org.quartz.jobStore.tablePrefix = QRTZ_

#数据库别名 随便取
#org.quartz.jobStore.dataSource = qzDS
#org.quartz.dataSource.qzDS.driver = com.mysql.jdbc.Driver
#org.quartz.dataSource.qzDS.URL = jdbc:mysql://192.168.184.135:3306/quartzdb?useSSL=false&useUnicode=true&characterEncoding=UTF-8
#org.quartz.dataSource.qzDS.user = root
#org.quartz.dataSource.qzDS.password = 123456
#org.quartz.dataSource.qzDS.maxConnections = 10
#org.quartz.dataSource.qzDS.acquireIncrement=1

//////

我这里使用的是MySql数据库,想要使用Oracle数据库,导入表(@"/路径/tables_oracle.sql"),不管你怎么导,只要能用就行,在数据源的地方修改下连接信息,修改quartz.properties文件修改为如下,直接启动就行了。

org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.oracle.OracleDelegate

//////

案例结构

模拟任务调度service层的业务逻辑

UserService.java

package com.quartz.service;

import java.io.Serializable;


public interface UserService extends Serializable {
    void getUserInfo();
    void getUserAddr();
}

UserServiceImpl.java

package com.quartz.service.impl;

import com.quartz.service.UserService;
import org.springframework.stereotype.Service;


@Service
public class UserServiceImpl implements UserService {
    @Override
    public void getUserInfo() {
        System.err.println("调度getUserInof成功");
    }

    @Override
    public void getUserAddr() {
        System.err.println("调度getUserAddr成功");
    }
}

任务

JobOne.java

package com.quartz.job;

import com.quartz.service.UserService;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;

@Component
@DisallowConcurrentExecution //保证上一次任务执行完毕再执行下一任务
//@PersistJobDataAfterExecution //上一个任务完成前写入需要被下一个任务获取的变量以及对应的属性值,类似求和累加
public class JobOne extends QuartzJobBean {
    private UserService userService;

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        userService.getUserInfo();
    }

    public void setUserService(UserService userService) {
        this.userService = userService;
    }
}

JobTwo.java

package com.quartz.job;

import com.quartz.service.UserService;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Component;

@Component
@DisallowConcurrentExecution
public class JobTwo extends QuartzJobBean {
    //不能使用注入的方式,只能使用DateMap方式传入参数
    private UserService userService;

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        userService.getUserAddr();
    }

    public void setUserService(UserService userService) {
        this.userService = userService;
    }
}

JobConfig.java

package com.quartz.jobconfig;

import com.quartz.job.JobOne;
import com.quartz.job.JobTwo;
import com.quartz.service.UserService;
import org.quartz.JobDataMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;


@Configuration
public class JobConfig {

    @Autowired
    private UserService userService;

    @Bean("jobOneDetail")
    public JobDetailFactoryBean jobOneDetailFactoryBean(JobOne jobOne) {
        JobDetailFactoryBean jobDetailFactoryBean =new JobDetailFactoryBean();
        jobDetailFactoryBean.setJobClass(jobOne.getClass());
        //没有绑定触发器仍然保留在Quartz的JobStore中
        jobDetailFactoryBean.setDurability(true);
        jobDetailFactoryBean.setName("jobOneDetailName");
        jobDetailFactoryBean.setGroup("jobOneDetailGroup");

        JobDataMap jobDataMap = new JobDataMap ();
        jobDataMap.put ("userService",userService);
        jobDetailFactoryBean.setJobDataMap(jobDataMap) ;
        return jobDetailFactoryBean;
    }

    @Bean("jobOneTrigger")
    public CronTriggerFactoryBean cronTriggerOneFactoryBean(@Qualifier("jobOneDetail") JobDetailFactoryBean jobDetailFactoryBean){
        CronTriggerFactoryBean cronTriggerFactoryBean=new CronTriggerFactoryBean();
        cronTriggerFactoryBean.setJobDetail(jobDetailFactoryBean.getObject());
        cronTriggerFactoryBean.setCronExpression("*/1 * * * * ?");
        cronTriggerFactoryBean.setName("jobOneTriggerName");
        cronTriggerFactoryBean.setGroup("jobOneTriggerGroup");
        return cronTriggerFactoryBean;
    }


    @Bean("jobTwoDetail")
    public JobDetailFactoryBean jobTwoDetailFactoryBean(JobTwo jobTwo) {
        JobDetailFactoryBean jobDetailFactoryBean =new JobDetailFactoryBean();
        jobDetailFactoryBean.setJobClass(jobTwo.getClass());
        jobDetailFactoryBean.setDurability(true);
        jobDetailFactoryBean.setName("jobTwoDetailName");
        jobDetailFactoryBean.setGroup("jobTwoDetailGroup");
        JobDataMap jobDataMap = new JobDataMap ();
        jobDataMap.put ("userService",userService);
        jobDetailFactoryBean.setJobDataMap(jobDataMap) ;
        return jobDetailFactoryBean;
    }

    @Bean("jobTwoTrigger")
    public CronTriggerFactoryBean cronTriggerTwoFactoryBean(@Qualifier("jobTwoDetail") JobDetailFactoryBean jobDetailFactoryBean){
        CronTriggerFactoryBean cronTriggerFactoryBean=new CronTriggerFactoryBean();
        cronTriggerFactoryBean.setJobDetail(jobDetailFactoryBean.getObject());
        cronTriggerFactoryBean.setCronExpression("*/1 * * * * ?");
        cronTriggerFactoryBean.setName("jobTwoTriggerName");
        cronTriggerFactoryBean.setGroup("jobTwoTriggerGroup");
        return cronTriggerFactoryBean;
    }

}

SchedulerConfig.java

package com.quartz.jobconfig;

import com.alibaba.druid.pool.DruidDataSource;
import org.quartz.Trigger;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;


@Configuration
public class SchedulerConfig {

    //执行任务。有了触发器,我们就可以执行任务了。注册一个SchedulerFactroyBean,然后将触发器一list的方式传入
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(DruidDataSource druidDataSource, @Qualifier("jobOneTrigger") Trigger jobOneTrigger, @Qualifier("jobTwoTrigger") Trigger jobTwoTrigger){
        SchedulerFactoryBean schedulerFactoryBean=new SchedulerFactoryBean();
        //调度器名称
        schedulerFactoryBean.setSchedulerName("TestScheduler");
        //数据源
        schedulerFactoryBean.setDataSource(druidDataSource);
        //覆盖已存在的任务,用于Quartz集群,QuartzScheduler启动会更新已存在的Job
        schedulerFactoryBean.setOverwriteExistingJobs(true);
        //延时1s启动定时任务,避免系统未完全启动却开始执行定时任务的情况
        schedulerFactoryBean.setStartupDelay(1);
        //设置加载的quartz.properties配置文件
        schedulerFactoryBean.setConfigLocation(new ClassPathResource("/quartz.properties"));
        //自动启动
        schedulerFactoryBean.setAutoStartup(true);
        //注册触发器
        schedulerFactoryBean.setTriggers(jobOneTrigger,jobTwoTrigger);
        return schedulerFactoryBean;
    }
}

启动两个改下端口8080,8081就行了,看到效负载调度。

节点争抢Job问题

因为Quartz使用了一个随机的负载均衡算法, Job以随机的方式由不同的实例执行。Quartz官网上提到当前,还不存在一个方法来指派(钉住) 一个 Job 到集群中特定的节点。

 

 

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是整合步骤: 1. 在 pom.xml 中添加依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.21</version> </dependency> ``` 2. 在 application.yml 或 application.properties 文件中配置数据源Quartz: ```yaml spring: datasource: url: jdbc:mysql://localhost:3306/db_name?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver quartz: job-store-type: jdbc jdbc: initialize-schema: never # 默认值为 always,表示每次启动都会重新初始化表结构,这里设为 never,只在第一次启动时初始化 ``` 3. 创建一个 Quartz 配置类,用于配置 Quartz 的属性: ```java @Configuration public class QuartzConfig { @Autowired private DataSource dataSource; @Bean public SchedulerFactoryBean schedulerFactoryBean() { SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); schedulerFactoryBean.setDataSource(dataSource); schedulerFactoryBean.setQuartzProperties(quartzProperties()); schedulerFactoryBean.setSchedulerName("MyScheduler"); schedulerFactoryBean.setApplicationContextSchedulerContextKey("applicationContext"); schedulerFactoryBean.setAutoStartup(true); schedulerFactoryBean.setStartupDelay(5); return schedulerFactoryBean; } @Bean public Properties quartzProperties() { Properties properties = new Properties(); properties.setProperty("org.quartz.scheduler.instanceName", "MyScheduler"); properties.setProperty("org.quartz.scheduler.instanceId", "AUTO"); properties.setProperty("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX"); properties.setProperty("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.StdJDBCDelegate"); properties.setProperty("org.quartz.jobStore.useProperties", "false"); properties.setProperty("org.quartz.jobStore.dataSource", "myDS"); properties.setProperty("org.quartz.jobStore.tablePrefix", "QRTZ_"); properties.setProperty("org.quartz.jobStore.isClustered", "true"); properties.setProperty("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool"); properties.setProperty("org.quartz.threadPool.threadCount", "10"); properties.setProperty("org.quartz.dataSource.myDS.driver", "com.mysql.jdbc.Driver"); properties.setProperty("org.quartz.dataSource.myDS.URL", "jdbc:mysql://localhost:3306/db_name?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai"); properties.setProperty("org.quartz.dataSource.myDS.user", "root"); properties.setProperty("org.quartz.dataSource.myDS.password", "123456"); properties.setProperty("org.quartz.dataSource.myDS.maxConnections", "10"); return properties; } } ``` 4. 创建一个 Job 类,用于定义具体的任务: ```java public class MyJob implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { // 任务执行逻辑 } } ``` 5. 创建一个 Trigger 类,用于定义任务执行的时间规则: ```java public class MyTrigger { public static Trigger getTrigger() { SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(10) // 每 10 秒执行一次 .repeatForever(); return TriggerBuilder.newTrigger() .withIdentity("MyTrigger", "MyTriggerGroup") .withSchedule(scheduleBuilder) .startAt(new Date()) // 从当前时间开始执行 .build(); } } ``` 6. 在启动类中添加注解 @EnableScheduling,用于开启定时任务: ```java @SpringBootApplication @EnableScheduling public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` 7. 在需要调度任务的地方,注入 Scheduler 对象,然后调用其 scheduleJob 方法即可: ```java @Autowired private Scheduler scheduler; public void scheduleJob() throws SchedulerException { JobDetail jobDetail = JobBuilder.newJob(MyJob.class) .withIdentity("MyJob", "MyJobGroup") .build(); Trigger trigger = MyTrigger.getTrigger(); scheduler.scheduleJob(jobDetail, trigger); } ``` 以上就是 Spring Boot 整合 QuartzDruid 的步骤。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值