
在实际开发中,经常会遇到多实例同时操作同一份数据的情况,如果没有加锁,很容易造成数据不一致。本文以一个试卷解析业务为例,讲解如何使用 Redisson 分布式锁 保证多实例安全写库。
一、业务背景
在我们的试卷解析系统中,解析结果会先写入 Redis 缓存,然后批量写入数据库。
如果多个节点同时处理同一张卷子,可能会出现以下问题:
-
数据库被重复覆盖,导致题目解析丢失
-
错题本写入出现覆盖问题
解决办法:在批量写库前加 分布式锁,保证同一时间只有一个节点处理某张卷子。
二、什么是分布式锁
在单机环境中,我们可以用 synchronized
或 ReentrantLock
来保证线程安全。
在分布式环境中(多台应用同时操作同一份数据),本地锁无法保证安全。
分布式锁的目标:同一份数据,在同一时间只能被一个实例修改。
三、为什么选择 Redisson
Redisson 是基于 Redis 的分布式锁实现,优势:
-
支持可重入锁,类似 Java 的
ReentrantLock
-
看门狗机制:自动续租,避免任务超时导致锁被提前释放
-
提供公平锁、读写锁等多种类型
-
封装简单易用,减少手动实现 SETNX + EXPIRE 的复杂度
四、Redisson 分布式锁基本使用
1. 添加依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.5</version>
</dependency>
2. 配置 Redis
spring: redis: host: 127.0.0.1 port: 6379
3. 简单示例
@Autowired
private RedissonClient redissonClient;
public void doSomething() {
RLock lock = redissonClient.getLock("test:lock");
try {
// 最多等待5秒获取锁
if (lock.tryLock(5, TimeUnit.SECONDS)) {
System.out.println("加锁成功,执行任务");
Thread.sleep(10000); // 模拟耗时操作
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
System.out.println("任务完成,释放锁");
}
}
}
五、结合业务场景实战
在试卷解析系统中,我们实现了如下方法,将 Redis 缓存的解析结果刷回数据库:
@Transactional(rollbackFor = Exception.class)
public void flushToDatabase(Long correctId) {
String lockKey = "correct:lock:" + correctId;
RLock lock = redissonClient.getLock(lockKey);
boolean locked = false;
try {
locked = lock.tryLock(5, TimeUnit.SECONDS);
if (!locked) {
System.out.println("任务正在被处理,跳过 correctId=" + correctId);
return;
}
// 读取 Redis 缓存并更新数据库
Map<Object, Object> cacheMap = RedisUtils.getHashEntries("correct:cache:" + correctId);
if (cacheMap != null) {
updateDatabase(correctId, cacheMap);
RedisUtils.del("correct:cache:" + correctId);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (locked && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
亮点
-
看门狗机制 :即使
updateDatabase
处理时间超过 30 秒,Redisson 会自动续期锁,防止被其他实例抢占 -
安全释放锁 :
isHeldByCurrentThread()
避免误释放他人锁 -
原子性 :保证同一
correctId
的刷库操作不会被多个节点同时执行
六、注意事项
-
tryLock
的参数:-
waitTime
:最多等待多久获取锁 -
leaseTime
:锁自动释放时间(不传则用看门狗机制)
-
-
务必在
finally
里释放锁,并判断isHeldByCurrentThread()
-
避免使用 Redis
KEYS *
扫描全量缓存,生产环境可用SCAN
或维护一个Set
存放待刷的correctId
七、总结
-
分布式锁是解决多实例并发问题的利器
-
Redisson 封装了自动续租机制,大幅降低开发复杂度
-
在刷库、库存扣减、任务幂等等场景下都非常适用
通过本文案例,你可以在自己的 SpringBoot 项目中安全使用 Redisson 分布式锁,实现多实例安全写库。