一、背景
在项目服务是集群部署的时候,代码在每个人都会有定时任务,但是如果让每个节点都去跑定时任务是不大合适的。SpringBoot 中的 ShedLock 可以很好解决这个问题,下面我将为大家详细介绍 SpringBoot 如何集成 ShedLock,而 ShedLock 又是如何实现分布式定时的。
二、ShedLock是什么
以下是ShedLock锁提供者,通过外部存储实现锁,由下图可知外部存储集成的库还是很丰富:

本篇教程我们基于JdbcTemplate存储为例来使用ShedLock锁。
三、落地实现
3.1 引入依赖包
shedlock所需依赖包:
org.springframework.bootspring-boot-starter-webnet.javacrumbs.shedlockshedlock-spring4.2.0net.javacrumbs.shedlockshedlock-provider-jdbc-template4.2.0org.springframework.bootspring-boot-starter-jdbcmysqlmysql-connector-javaruntime
依赖包树形图:

3.2 配置数据库连接信息
server:
port: 8105
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/testjdbc?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.mysql.cj.jdbc.MysqlDataSource
3.3 创建Mysql数据表
CREATE TABLE `shedlock` ( `name` varchar(64) NOT NULL COMMENT 'name' , `lock_until` timestamp(3) NULL DEFAULT NULL , `locked_at` timestamp(3) NULL DEFAULT NULL , `locked_by` varchar(255) NULL DEFAULT NULL , PRIMARY KEY (`name`) ) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC ;
3.4 配置LockProvider
ShedLockConfig.java:
import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.sql.DataSource;
/**
* @description: Shedlock集成Jdbc配置类
*/
@Component
public class ShedLockConfig {
@Resource
private DataSource dataSource;
@Bean
private LockProvider lockProvider() {
return new JdbcTemplateLockProvider(dataSource);
}
}
springboot主启动类MerakQuartzApplication:
import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* @version 1.0
* @ClassName: MerakQuartzApplication
* @description: 工单任务调度
*/
// 开启定时器
@EnableScheduling
// 开启定时任务锁,指定一个默认的锁的时间30秒
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
@EnableAsync
@MapperScan(basePackages = {"com.merak.hyper.automation.persist.**.mapper"})
@SpringBootApplication(scanBasePackages = {"com.merak.hyper.automation.**"}, exclude = {SecurityAutoConfiguration.class})
public class MerakQuartzApplication {
public static final Logger log = LoggerFactory.getLogger(MerakQuartzApplication.class);
public static void main(String[] args) {
SpringApplication.run(MerakQuartzApplication.class, args);
}
private int taskSchedulerCorePoolSize = 15;
private int awaitTerminationSeconds = 60;
private String threadNamePrefix = "taskExecutor-";
/**
* @description: 实例化ThreadPoolTaskScheduler对象,用于创建ScheduledFuture> scheduledFuture
*/
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(taskSchedulerCorePoolSize);
taskScheduler.setThreadNamePrefix(threadNamePrefix);
taskScheduler.setWaitForTasksToCompleteOnShutdown(false);
taskScheduler.setAwaitTerminationSeconds(awaitTerminationSeconds);
/**需要实例化线程*/
taskScheduler.initialize();
// isinitialized = true;
log.info("初始化ThreadPoolTaskScheduler ThreadNamePrefix=" + threadNamePrefix + ",PoolSize=" + taskSchedulerCorePoolSize
+ ",awaitTerminationSeconds=" + awaitTerminationSeconds);
return taskScheduler;
}
/**
* @description: 实例化ThreadPoolTaskExecutor对象,管理线程
*/
@Bean("asyncTaskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(5);
taskExecutor.setMaxPoolSize(50);
taskExecutor.setQueueCapacity(200);
taskExecutor.setKeepAliveSeconds(60);
taskExecutor.setThreadNamePrefix("asyncTaskExecutor-");
taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
taskExecutor.setAwaitTerminationSeconds(60);
//修改拒绝策略为使用当前线程执行
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//初始化线程池
taskExecutor.initialize();
return taskExecutor;
}
}
3.5 创建定时Job
DigitalEmpTask:
package com.merak.hyper.automation.quartz.task;
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @version 1.0
* @ClassName: BizOrderTask
* @description: 任务队列服务调度
*/
@Component
public class DigitalEmpTask {
public static final Logger log = LoggerFactory.getLogger(DigitalEmpTask.class);
@Scheduled(cron = "0/30 * * * * ?")
@SchedulerLock(name = "digitalEmpTaskScheduler", lockAtMostFor = "PT25S", lockAtLeastFor = "PT25S")
protected void digitalEmpTaskScheduler() {
log.info("云执行调度中心1:任务开始执行,时间:" + DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD_HH_MM_SS));
try {
} catch (Exception e) {
log.error("云执行调度中心1调度失败,原因:" + e.getMessage());
}
}
}
四、结果分析
1.分别启动两个服务节点,配置如下:
server:
port: 12105
servlet:
context-path: /automation-quartz-oneserver:
port: 12106
servlet:
context-path: /automation-quartz-two
2.运行日志(片断)
节点automation-quartz-one 运行日志:
2023-02-22 12:01:00.143 [taskExecutor-1] INFO46>46>46>46>
46>46>46>46>46>46>46>46>46>46>46>46>46>46>46>46>46>46>46>46>46>46>46>46>46>46>46>46>46>46>46>46>46>46>

