SpringBoot Redis 分布式锁 Redisson
之前的项目中使用 附 1. SETNX 方式 中的方法来实现锁机制,但缺陷较大,并不能保证原子性。
Redisson 是一个实现了 RedLock 的框架,使用 redisson-spring-boot-starter 可以很容易的在 Spring Boot 中实现分布式锁。
关于 Redlock 可以参考这篇博客(by the way,这个 URL 地址比较有意思),讲的比较详细。
添加依赖
xml
<!-- Redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.16.7</version>
</dependency>
版本最好与使用 spring-boot 版本一致,本地开发时就由于版本不一致,导致在操作 zSet 时发生了 java.lang.StackOverflowError 异常。
版本对应见 官方仓库 上的说明,配置方式如下:
xml
<!-- Redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.16.7</version>
<exclusions>
<exclusion>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-data-25</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<!-- for Spring Data Redis v.2.2.x -->
<artifactId>redisson-spring-data-22</artifactId>
<version>3.16.7</version>
</dependency>
用法示例
java
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
java
@Autowired
private RedissonClient redissonClient;
java
// 添加缓存锁(锁失败时丢弃)
String lockKey = getLockKey(message);
RLock lock = redissonClient.getLock(lockKey);
try {
if (!lock.tryLock(1, TimeUnit.MINUTES)) {
log.error("锁失败(消息 Id:{})", message.getId());
return;
}
// do something
} finally {
lock.unlock();
}
附 1. SETNX 方式
这种方式如这篇博客中所说,无法保证其原子性。
仅供参考,不推荐使用。
java
public static boolean lock(RedisTemplate<String, Object> redisTemplate, String key, String value) {
if (redisTemplate.opsForValue().setIfAbsent(key, value, 10, TimeUnit.MINUTES)) {
return true;
}
String currentValue = (String) redisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()) {
String oldValue = (String) redisTemplate.opsForValue().getAndSet(key, value);
if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
return true;
}
}
log.info("key : {} value: {} currentValue : {}", key, value, currentValue);
return false;
}
public static void unlock(RedisTemplate<String, Object> redisTemplate, String key, String value) {
try {
String currentValue = (String) redisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
redisTemplate.opsForValue().getOperations().delete(key);
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
2022-04-27 追记
3.16.7 版本的 Redisson 版本使用 multi
操作时会导致程序卡住,解决办法是更新到 3.17.0 。
官方 Release 日志:https://github.com/redisson/redisson/releases/tag/redisson-3.17.0
Fixed - Spring Data Connection in multi mode causes thread stuck (regression since 3.16.7)
需要注意的是:在 pom.xml 中排除的不再是 redisson-spring-data-25 ,而是 redisson-spring-data-26 。
xml
<!-- Redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.0</version>
<exclusions>
<exclusion>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-data-26</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<!-- for Spring Data Redis v.2.2.x -->
<artifactId>redisson-spring-data-22</artifactId>
<version>3.17.0</version>
</dependency>