简介
ShedLock的作用,确保任务在同一时刻最多执行一次。如果一个任务正在一个节点上执行,则它将获得一个锁,该锁将阻止从另一个节点(或线程)执行同一任务。如果一个任务已经在一个节点上执行,则在其他节点上的执行不会等待,只需跳过它即可 。
用法
- 启用和配置计划
- 配置锁提供者
- 定时任务
- 测试结果
启用和配置计划
maven方式
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>4.23.0</version>
</dependency>
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-redis-spring</artifactId>
<version>2.5.0</version>
</dependency>
spring配置redis
redis:
#数据库索引
database: 0
host: 127.0.0.1
port: 6379
password:
jedis:
pool:
#最大连接数
max-active: 8
#最大阻塞等待时间(负数表示没限制)
max-wait: -1
#最大空闲
max-idle: 8
#最小空闲
min-idle: 0
#连接超时时间
timeout: 10000
Redis配置
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @ClassName RedisConfig
* @Description
* @Author Jiang
* @Time 2023/7/7 11:12
* @Version 1.0
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
//参照StringRedisTemplate内部实现指定序列化器
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(keySerializer());
redisTemplate.setHashKeySerializer(keySerializer());
redisTemplate.setValueSerializer(valueSerializer());
redisTemplate.setHashValueSerializer(valueSerializer());
return redisTemplate;
}
private RedisSerializer<String> keySerializer(){
return new StringRedisSerializer();
}
//使用Jackson序列化器
private RedisSerializer<Object> valueSerializer(){
return new GenericJackson2JsonRedisSerializer();
}
}
启用SchedulerLock
@EnableScheduling、@EnableSchedulerLock可以放在项目的启动类上,也可以放在项目配置类上,但是一定记得不能缺少,否则分布式锁会失效。(项目中遇到了这个问题排查了一天,最终发现是@EnableSchedulerLock没有配置,哭死)
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
// 开启定时任务注解
@EnableScheduling
// 开启定时任务锁,默认设置锁最大占用时间为30s
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
@SpringBootApplication
public class HelloSpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(HelloSpringbootApplication.class, args);
}
}
其中 @EnableSchedulerLock(defaultLockAtMostFor = "PT30S") 源码中有这样一段介绍如下,指定在执行节点结束时应保留锁的默认时间使用ISO8601 Duration格式,作用就是在被加锁的节点挂了时,无法释放锁,造成其他节点无法进行下一任务,我们使用注解时候需要给定一个值,还有最后一句,可以在每个ScheduledLock注解中被重写,也就是说我们每个定时任务都可以重新定义时间,来控制每个定时任务。
/**
* Default value how long the lock should be kept in case the machine which obtained the lock died before releasing it.
* Can be either time with suffix like 10s or ISO8601 duration as described in {@link java.time.Duration#parse(CharSequence)}, for example PT30S.
* This is just a fallback, under normal circumstances the lock is released as soon the tasks finishes.
* Set this to some value much higher than normal task duration. Can be overridden in each ScheduledLock annotation.
*/
String defaultLockAtMostFor();
配置锁提供者
ShedLock使用Mongo,JDBC数据库,Redis,Hazelcast,ZooKeeper或其他外部存储进行协调,即通过外部存储来实现锁机制。
本文主要介绍通过Redis实现分布式锁的方式。
import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import net.javacrumbs.shedlock.provider.redis.spring.RedisLockProvider;
/**
* @ClassName ScheduledLockConfig
* @Description
* @Author Jiang
* @Time 2023/7/7 13:28
* @Version 1.0
*/
@Configuration
public class ScheduledLockConfig {
@Autowired
RedisTemplate redisTemplate;
@Bean
public LockProvider lockProvider() {
return new RedisLockProvider(redisTemplate.getConnectionFactory());
}
}
定时任务
可根据自己的实际业务编写定时任务
import lombok.extern.slf4j.Slf4j;
import net.javacrumbs.shedlock.core.SchedulerLock;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* @ClassName TestScheduled
* @Description
* @Author Jiang
* @Time 2023/7/7 11:26
* @Version 1.0
*/
@Slf4j
@Component
public class TestScheduled {
@Autowired
RedisTemplate redisTemplate;
@Scheduled(fixedDelay = 30 * 1000)
@SchedulerLock(name = "evaluateUnsubmit", lockAtLeastFor = 5*60*1000 ,lockAtMostFor = 20*60*1000 )
public void testMethod(){
log.info("开始执行 {}", DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
try {
Thread.sleep(100);
redisTemplate.opsForValue().set("test" + System.currentTimeMillis(),"goodJob",100, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info("执行完成 {}", DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
}
}
参数说明 @Scheduled
- fixedDelay:非常好理解,它的间隔时间是根据上次的任务结束的时候开始计时的。方法上设置了fixedDelay=30 * 1000,那么当该方法某一次执行结束后,开始计算时间,当时间达到30秒,就开始再次执行该方法
@SchedulerLock注解主要参数:
- name:锁的名称,必须保证唯一;
- lockAtMostFor:成功执行任务的节点所能拥有的独占锁的最长时间,设置的值要保证比定时任务正常执行完成的时间大一些,此属性保证了如果task节点突然宕机,也能在超过设定值时释放任务锁;
- lockAtLeastFor:成功执行任务的节点所能拥有的独占锁的最短时间,其主要目的是任务执行时间可能很短,执行后如果发上释放锁,可能会造成多个节点执行。
测试结果
项目启动后执行一次任务,在redis里新增了一条job-lock:default:evaluateUnsubmit用来解决多点部署重复执行任务的问题。
工作中遇到了就顺手总结一下,如有什么问题欢迎指正
Stay hungry,Stay foolish