在分布式服务中,经常有例如定时任务这样的场景。
在定时任务中,如果不使用 quartz
这样的分布式定时工具,只是简单使用 @Schedule
注解来实现定时任务,在服务分布式部署中,就有可能存在定时任务并发重复执行问题。
对于解决以上场景中的问题,我们引入了分布式锁。
RedisUtils 工具类:
@Component
public class RedisUtils {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 原子性操作 加锁
*/
public boolean setIfAbsent(String key, String value, long timeout, TimeUnit unit) {
return Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit));
}
/**
* 原子性操作 解锁
*/
public void delete(String key) {
redisTemplate.delete(key);
}
}
使用示例:
@Resource
private RedisUtils redisUtils;
public void updateUserWithRedisLock(SysUser sysUser) throws InterruptedException {
// 1.获取分布式锁
boolean lockSuccess = RedisUtils.setIfAbsent("SysUserLock" + sysUser.getId(), "value", 30, TimeUnit.SECONDS);
if (lockSeccess) {
// 加锁成功...
// TODO: 业务代码
// 释放锁
redisUtils.delete("SysUserLock" + sysUser.getId());
} else {
// 如果需要阻塞的话,就睡一段时间再重试
Thread.sleep(100);
updateUserWithRedisLock(sysUser);
}
}
setIfAbsent()
方法的作用就是在 lock key 不存在的时候,才会设置值并返回 true;如果这个 key 已经存在了就返回 false,即获取锁失败。slefAbsen
RedisLockRegistry 是 spring-integration-redis
中提供的 Redis 分布式锁实现类。
集成 spring-integration-redis:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-interationartifactId>
dependency>
<dependency>
<groupId>org.springframework.integrationgroupId>
<artifactId>spring-integration-redisartifactId>
dependency>
注册RedisLockRegistry:
@Configuration
public class RedisLockConfig {
@Bean
public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactor) {
// 第一个参数 redisConnectionFactory
// 第二个参数 registryKey,分布式锁前缀,建议设置项目名称
// 该构造方法对应的分布式锁,默认有效期是60秒,可以自定义。
return new RedisLockRegistry(redisConnectionFactory, "boot-launch");
// return new RedisLockRegistry(redisConnectionFactory, "boot-launch", 60);
}
}
使用RedisLockRegistry:
代码实现:
@Resource
private RedisLockRegisty redisLockRegistry;
public void updateUser(String userId) {
String lockKey = "config" + userId;
Lock lock = redisLockRegistry.obtain(lockKey); // 获取锁资源
try {
lock.lock(); // 加锁
// TODO: 业务代码
} finally {
lock.unlock(); // 释放锁
}
}
注解实现:
@RedisLock("lock-key")
public void save() {
}
Redission 是一个独立的 Redis 客户端,是与 Jedis、Lettuce 同级别的存在。
集成 Redisson:
<dependency>
<groupId>org.redissongroupId>
<artifactId>redisson-spring-boot-starterartifactId>
<version>3.15.0version>
<exclusions>
<exclusion>
<groupId>org.redissongroupId>
<artifactId>redisson-spring-data-23artifactId>
exclusion>
exclusions>
dependency>
配置:
1)在配置文件中添加如下内容:
spring:
redis:
redisson:
file: classpath:redisson.yaml
2)然后新建一个 redisson.yaml
文件,也放在 resources
目录下:
singleServerConfig:
idleConnectionTimeout: 10000
connectTimeout: 10000
timeout: 3000
retryAttempts: 3
retryInterval: 1500
password: 123456
subscriptionsPerConnection: 5
clientName: null
address: "redis://192.168.161.3:6379"
subscriptionConnectionMinimumIdleSize: 1
subscriptionConnectionPoolSize: 50
connectionMinimumIdleSize: 32
connectionPoolSize: 64
database: 0
dnsMonitoringInterval: 5000
threads: 0
nettyThreads: 0
codec: ! {}
transportMode: "NIO"
代码实现:
@Resource
private RedissonClient redissonClient;
public void updateUser(String userId) {
String lockKey = "config" + userId;
RLock lock = redissonClient.getLock(lockKey); // 获取锁资源
try {
lock.lock(10, TimeUnit.SECONDS); // 加锁,可以指定锁定时间
// TODO: 业务代码
} finally {
lock.unlock(); // 释放锁
}
}
对比 RedisLockRegistry 和 Redisson 实现:
注意:如果业务执行超时之后,再去 unlock
会抛出 java.lang.IllegalMonitorStateException
。
setIfAbsent()
是 Redis 专门为分布式锁实现的原子操作,其中封装了获取 key、设置 key、设置过期时间等操作。setnx()
主要用于分布式 ID 的生成,如果要用来实现分布式锁的话,虽然可以通过返回结果为0表示获取锁失败,1表示获取锁成功,但是对于设置过期时间的操作需要手动实现,无法保证原子性。整理完毕,完结撒花~
参考地址:
1.Java三种方式实现redis分布式锁,https://blog.csdn.net/w_monster/article/details/124472493