Redisson分布式锁:从入门到实战
一、为什么需要分布式锁?
在单体应用中,我们使用Java的synchronized或ReentrantLock就能解决并发问题。但在微服务架构下,多个实例同时运行,单机的锁机制就失效了。这时就需要分布式锁来保证跨JVM的互斥访问。
分布式锁的核心需求
- 互斥性:同一时刻只能有一个客户端持有锁
- 防止死锁:客户端崩溃后能自动释放锁
- 容错性:Redis节点故障时仍能正常工作
- 可重入性:同一个线程可以多次获取同一个锁
二、Redis实现分布式锁的基础方案
2.1 最简单的实现:SET NX
最早期的实现方式是使用SET命令的NX选项:
bash
SET key value NX PX 30000
- NX:只在key不存在时设置
- PX:设置过期时间(毫秒)
这种方式的缺点:
- 无法实现可重入锁
- 无法获取锁的持有人信息
- 无法实现锁的自动续期
2.2 完整实现方案
一个相对完善的Redis分布式锁实现需要包含以下逻辑:

- 加锁:SET key value NX PX timeout
- 解锁:Lua脚本保证原子性
- 看门狗:定时续期防止业务执行时间超过锁过期时间
三、Redisson分布式锁
Redisson是Redis的Java客户端,它提供了完善的分布式锁实现,解决了原生Redis锁的各种问题。
3.1 核心特性
- 可重入锁:同一个线程可多次获取锁
- 公平锁:按请求顺序获取锁
- 读写锁:允许多个读操作或单个写操作
- 红锁:跨多个Redis节点的分布式锁
- 自动续期:看门狗机制自动延长锁时间
- 锁释放:Lua脚本保证原子性操作
3.2 基本使用
java
// 获取锁对象
RLock lock = redisson.getLock("myLock");
try {
// 加锁(支持等待时间和自动续期)
boolean locked = lock.tryLock(100, 30000, TimeUnit.MILLISECONDS);
if (locked) {
// 执行业务逻辑
doBusiness();
}
} finally {
// 释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
四、Redis实现 vs Redisson对比

| 特性 | Redis原生实现 | Redisson |
|---|---|---|
| 实现复杂度 | 高,需要处理多种边界情况 | 低,开箱即用 |
| 可重入锁 | 需要自己实现 | 原生支持 |
| 自动续期 | 需要后台线程续期 | 看门狗自动续期 |
| 公平锁 | 难以实现 | 开箱即用 |
| 读写锁 | 难以实现 | 开箱即用 |
| 红锁 | 需要自己协调 | 开箱即用 |
| 异常处理 | 需要仔细处理 | 完善的异常体系 |
结论:生产环境推荐使用Redisson,它已经处理了各种边界情况和异常场景。
五、Redisson核心原理解析
5.1 加锁机制
Redisson使用Lua脚本保证加锁的原子性:

lua
-- 检查key是否存在
if redis.call('exists', KEYS[1]) == 0 then
-- 不存在则设置,使用hash结构存储
redis.call('hset', KEYS[1], ARGV[2], 1)
redis.call('pexpire', KEYS[1], ARGV[1])
return nil
end
-- key已存在,检查是否是当前线程
if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then
-- 是当前线程,增加重入次数
redis.call('hincrby', KEYS[1], ARGV[2], 1)
redis.call('pexpire', KEYS[1], ARGV[1])
return nil
end
return redis.call('pttl', KEYS[1])
锁的数据结构使用Hash类型:
- Key:锁名称
- Field:线程标识(UUID:threadId)
- Value:重入次数
5.2 看门狗机制
看门狗(Watchdog)是Redisson的核心特性,用于解决锁超时问题:

- 业务获得锁时,启动后台定时任务
- 每隔lockWatchdogTimeout/3时间检查锁是否还持有
- 如果还持有则重置过期时间
- 业务释放锁时停止看门狗
默认情况下,看门狗每10秒续期一次,锁的过期时间为30秒。
5.3 解锁机制
解锁同样使用Lua脚本保证原子性:
lua
-- 检查锁是否是当前线程持有
if redis.call('hexists', KEYS[1], ARGV[2]) <= 0 then
return nil
end
-- 减少重入次数
local counter = redis.call('hincrby', KEYS[1], ARGV[2], -1)
-- 如果重入次数大于0,重置过期时间
if counter > 0 then
redis.call('pexpire', KEYS[1], ARGV[1])
return 0
end
-- 重入次数为0,删除锁
redis.call('del', KEYS[1])
-- 发布解锁消息
redis.call('publish', KEYS[2], ARGV[3])
return 1
六、生产环境实战案例

6.1 库存扣减
java
@Service
public class InventoryService {
@Autowired
private RedissonClient redisson;
public boolean deductStock(String productId, int quantity) {
RLock lock = redisson.getLock("stock:" + productId);
try {
// 等待5秒,锁过期30秒
if (lock.tryLock(5, 30, TimeUnit.SECONDS)) {
// 查询库存
int stock = getStock(productId);
if (stock >= quantity) {
// 扣减库存
updateStock(productId, stock - quantity);
return true;
}
return false;
}
throw new BusinessException("系统繁忙,请稍后重试");
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
6.2 订单幂等性控制
java
@Service
public class OrderService {
@Autowired
private RedissonClient redisson;
public Order createOrder(OrderRequest request) {
String lockKey = "order:create:" + request.getUserId();
RLock lock = redisson.getLock(lockKey);
try {
if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {
// 检查是否已有未完成订单
Order existOrder = getUnfinishedOrder(request.getUserId());
if (existOrder != null) {
return existOrder;
}
// 创建新订单
Order order = buildOrder(request);
saveOrder(order);
return order;
}
throw new BusinessException("操作频繁,请稍后重试");
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
6.3 定时任务防止重复执行
java
@Component
public class DataSyncJob {
@Autowired
private RedissonClient redisson;
@Scheduled(cron = "0 */5 * * * *")
public void syncData() {
String lockKey = "job:data:sync";
RLock lock = redisson.getLock(lockKey);
try {
// 尝试获取锁,不等待
if (lock.tryLock(0, TimeUnit.SECONDS)) {
// 执行数据同步
doSync();
} else {
log.info("上次同步任务尚未完成,跳过本次执行");
}
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
七、高级特性:读写锁
在读多写少的场景下,使用读写锁可以大幅提升并发性能:

java
@Service
public class ConfigService {
@Autowired
private RedissonClient redisson;
public String getConfig(String key) {
RReadWriteLock rwLock = redisson.getReadWriteLock("config:" + key);
RLock readLock = rwLock.readLock();
try {
readLock.lock();
// 多个线程可以同时读取
return queryFromDB(key);
} finally {
readLock.unlock();
}
}
public void updateConfig(String key, String value) {
RReadWriteLock rwLock = redisson.getReadWriteLock("config:" + key);
RLock writeLock = rwLock.writeLock();
try {
// 写操作独占锁
writeLock.lock();
updateDB(key, value);
// 清除缓存
clearCache(key);
} finally {
writeLock.unlock();
}
}
}
八、最佳实践
8.1 锁的粒度选择
- 粗粒度:锁整个表或模块,简单但并发度低
- 细粒度:锁单个记录,并发度高但实现复杂
推荐:根据业务场景选择合适的粒度,库存扣减用商品维度,订单创建用用户维度。
8.2 锁的过期时间设置
- 不要设置过短,避免业务未执行完就释放
- 不要设置过长,避免故障时长时间阻塞
- Redisson的看门狗会自动续期,但要设置合理的初始时间
推荐:根据业务执行时间的P99值设置,预留50%余量。
8.3 异常处理
- 捕获中断异常,确保锁能被释放
- 使用tryLock而不是lock,避免无限等待
- 检查锁的持有状态,避免误释放
九、常见问题
Q1:Redisson锁会丢失吗?
不会。Redisson使用看门狗机制,即使业务执行时间超过锁的过期时间,也会自动续期,直到业务主动释放锁。
Q2:Redis主从切换会影响锁吗?
会。单节点Redis在主从切换时可能丢失锁。需要使用Redisson的红锁(RedLock)或Redis Cluster。
Q3:如何实现公平锁?
Redisson提供了公平锁实现:
java
RLock fairLock = redisson.getFairLock("myFairLock");
fairLock.lock();
Q4:性能如何?
Redisson的分布式锁性能很好,单机QPS可达5万+,足以应对大多数场景。如有更高性能需求,可以考虑分段锁或本地锁+分布式锁的组合方案。
十、总结
Redisson分布式锁是Java领域最成熟的Redis分布式锁解决方案:
- 使用简单:API友好,学习成本低
- 功能完善:支持多种锁类型和场景
- 稳定可靠:经过大量生产环境验证
- 性能优异:看门狗机制和Lua脚本保证性能
在实际项目中,建议优先使用Redisson,它已经帮你处理了各种复杂的边界情况,让你专注于业务逻辑的实现。