Skip to content

SpringBoot Redis 分布式锁 Redisson

🏷️ 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>