Redisson 实战:Spring Boot 项目中如何优雅实现分布式锁
在并发场景下,为了保证多个线程、多个服务节点间对共享资源的互斥访问,我们常常需要用到“分布式锁”。而 Redisson 正是 Redis 官方推荐的 Java 客户端中,对分布式锁封装最优雅、最稳定的实现之一。
本篇文章将基于 Spring Boot + Redisson 实现一个通用的分布式锁组件封装,并提供实际接口测试验证使用效果。
完整示例项目地址:
一、使用背景
常见的并发场景如下:
-
限制用户同时只能执行一次下单请求
-
避免重复扣减库存
-
防止定时任务并发执行重复逻辑
这些场景都要求我们实现可靠的“互斥执行”机制,Redisson 提供的 RLock 就能很好地满足这些需求。
二、项目结构概览
redisson-lock-demo/
├── config/ # Redisson 配置类
├── service/ # 分布式锁抽象 + 实现 + 回调接口
├── controller/ # 接口演示
└── Application.java
三、Redisson 配置
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer().setAddress("redis://localhost:6379");
return Redisson.create(config);
}
}
四、通用锁服务设计
我们封装了一个统一的 DistributedLockService 接口:
public interface DistributedLockService {
/**
* 获取分布式锁(看门狗机制)
*
* @param key 锁的key
* @param callback 业务逻辑
* @return 业务逻辑执行结果
*/
<T> T lock(String key, LockCallback<T> callback);
/**
* 尝试获取分布式锁
*
* @param key 锁的key
* @param waitTime 等待获取锁的时间
* @param leaseTime 锁的持有时间
* @param unit 时间单位
* @param callback 业务逻辑
* @return 业务逻辑执行结果
*/
<T> T tryLock(String key, long waitTime, long leaseTime, TimeUnit unit, LockCallback<T> callback);
/**
* 解锁
*
* @param key 锁的key
*/
void unlock(String key);
}
回调接口定义:
@FunctionalInterface
public interface LockCallback<T> {
T doInLock() throws Exception;
}
实现类:RedissonDistributedLockService
@Slf4j
@Service
@RequiredArgsConstructor
public class RedissonDistributedLockService implements DistributedLockService {
private final RedissonClient redissonClient;
@Override
public <T> T lock(String key, LockCallback<T> callback) {
RLock lock = redissonClient.getLock(key);
try {
lock.lock();
return callback.doInLock();
} catch (Exception e) {
throw new RuntimeException("执行加锁逻辑时失败", e);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
@Override
public <T> T tryLock(String key, long waitTime, long leaseTime, TimeUnit unit, LockCallback<T> callback) {
RLock lock = redissonClient.getLock(key);
boolean locked = false;
try {
locked = lock.tryLock(waitTime, leaseTime, unit);
if (!locked) {
throw new RuntimeException("未能成功获取分布式锁:" + key);
}
return callback.doInLock();
} catch (Exception e) {
throw new RuntimeException("执行 tryLock 逻辑时失败", e);
} finally {
if (locked && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
@Override
public void unlock(String key) {
RLock lock = redissonClient.getLock(key);
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
五、接口演示
@RestController
@RequiredArgsConstructor
public class TestController {
private final DistributedLockService lockService;
@GetMapping("/test/lock")
public String testLock() {
return lockService.lock("test-lock", () -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "加锁执行成功";
});
}
}
可以使用浏览器或并发工具(如 ApacheBench、JMeter)测试多线程访问 /test/lock,观察是否串行执行。
六、使用建议与常见坑
-
建议设置锁的超时时间(leaseTime),避免异常情况下造成死锁
-
注意:Redisson 分布式锁默认不是可重入的,避免多线程内重复加锁
-
切勿将 lock() 用于无法保证释放的逻辑中,推荐使用 tryLock() 尝试型加锁
七、总结
本文实现了基于 Redisson 的通用分布式锁服务,具备:
-
封装清晰(接口 + 回调)
-
使用方便(只需一行 lockService.lock(...))
-
支持 tryLock、自动释放等能力
适用于绝大多数中后台业务并发控制场景,欢迎收藏使用。
八、补充说明:Redisson 的锁续期机制与看门狗
当我们使用 lock() 方法加锁时,如果没有显式设置 leaseTime,Redisson 会自动使用一个默认的“看门狗”机制来续期锁,避免因业务执行时间过长而提前释放。
🚨 自动续期机制说明
-
默认锁有效期为 30 秒
-
若业务执行时间超过 30 秒,Redisson 会每隔 10 秒自动续期
-
前提是锁还在被线程持有,且客户端未宕机
这一机制被称为 Redisson WatchDog,源码位于 org.redisson.RedissonLock.renewExpiration。
✅ 推荐做法
-
如果能预估业务时间,建议使用 tryLock(..., leaseTime) 显式指定锁时长,(比如常在 3 秒内完成),建议设置 leaseTime = 5 ~ 10 秒,避免依赖 WatchDog 续期
-
如果业务不确定耗时,且服务器稳定可靠,则可使用默认机制简化使用