在上一篇《三次分布式锁事故的血泪教训:从SETNX死锁到Redisson自动续期的终极救赎》中,我们揭开了分布式锁的演进之路。本文将深入Redisson的核心武器库,聚焦99%实际工作场景中真正需要的API,揭示六大关键陷阱,并通过源码解析看门狗机制的实现原理。
一、Redisson分布式锁核心API全景图
基础锁(RLock) - 90%场景的首选
|
方法签名 |
作用 |
使用场景 |
注意事项 |
|
void lock() |
阻塞式加锁(默认30秒过期,看门狗自动续期) |
常规业务加锁 |
最常用,必须搭配finally解锁 |
|
boolean tryLock() |
尝试加锁(立即返回结果) |
非阻塞场景 |
需判断返回值处理失败情况 |
|
boolean tryLock(long waitTime, TimeUnit unit) |
在指定时间内尝试获取锁 |
允许短暂等待的场景 |
避免设置过长等待时间 |
|
void unlock() |
释放当前线程持有的锁 |
必须在finally块调用 |
需配合isHeldByCurrentThread()校验 |
|
boolean isLocked() |
检查锁是否被持有 |
锁状态监控 |
不能区分持有者 |
|
boolean isHeldByCurrentThread() |
检查当前线程是否持有锁 |
解锁前的关键校验 |
避免误删其他线程的锁 |
// 基础锁最佳实践
RLock lock = redisson.getLock("order:lock:" + orderId);
if (lock.tryLock(3, TimeUnit.SECONDS)) {
try {
// 核心业务逻辑(自动续期保障)
processOrder(orderId);
} finally {
// 双重校验防止误释放
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
} else {
// 获取锁失败处理
throw new BusyException("系统繁忙,请稍后重试");
}
// 公平锁最佳实践
RLock fairLock = redisson.getFairLock("order:lock:" + orderId);
if (fairLock.tryLock(8, TimeUnit.SECONDS)) {
try {
// 按照请求顺序处理支付(自动续期保障)
processSequentialPayment(orderId);
} finally {
// 双重校验确保安全释放
if (fairLock.isHeldByCurrentThread() && fairLock.isLocked()) {
fairLock.unlock();
}
}
} else {
// 获取锁失败处理
handleLockFailure(orderId);
}
读写锁(ReadWriteLock) - 高并发读优化
|
方法签名 |
作用 |
使用场景 |
特点 |
|
RLock readLock() |
获取读锁 |
并发读取共享资源 |
多个线程可同时持有 |
|
RLock writeLock() |
获取写锁 |
修改共享资源 |
互斥独占 |
RReadWriteLock rwLock = redisson.getReadWriteLock("product:inventory:" + productId);
// 读操作(并发优化)
public int getInventory() {
rwLock.readLock().lock();
try {
return inventoryService.get(productId);
} finally {
rwLock.readLock().unlock();
}
}
// 写操作(互斥保护)
public void updateInventory(int quantity) {
rwLock.writeLock().lock();
try {
inventoryService.update(productId, quantity);
} finally {
rwLock.writeLock().unlock();
}
}
红锁(RedLock) - 金融级安全方案
|
方法签名 |
作用 |
使用场景 |
代价 |
|
void lock() |
跨多个Redis实例加锁 |
金融交易等高安全要求场景 |
性能损耗较大 |
|
boolean tryLock() |
尝试获取红锁 |
需要强一致性的关键操作 |
需至少3个独立Redis集群 |
// 构建三个独立Redis实例的锁
RLock lock1 = redisson1.getLock("bank:transfer");
RLock lock2 = redisson2.getLock("bank:transfer");
RLock lock3 = redisson3.getLock("bank:transfer");
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
if (redLock.tryLock()) {
try {
// 执行资金转移(强一致性保障)
transferFunds(accountFrom, accountTo, amount);
} finally {
redLock.unlock();
}
}
二、六大关键陷阱与规避方案
🚨 陷阱1:锁释放的雷区
致命错误:
try {
lock.lock();
if (condition) {
lock.unlock(); // 提前解锁
return;
}
// 后续代码在无锁状态下运行!
} finally {
lock.unlock(); // 二次解锁抛出IllegalMonitorStateException
}
正确方案:
try {
lock.lock();
// 所有业务逻辑
} finally {
// 双重校验安全释放
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
🚨 陷阱2:看门狗休眠陷阱
错误配置:
// 指定leaseTime会禁用看门狗!
lock.lock(60, TimeUnit.SECONDS);
// 业务执行超过60秒 → 锁自动失效 → 数据不一致
解决方案:
// 1. 使用默认锁(启用看门狗)
lock.lock();
// 2. 需要自定义超时则全局配置
Config config = new Config();
config.setLockWatchdogTimeout(45000); // 看门狗续期时间(默认30秒)
RedissonClient redisson = Redisson.create(config);
🚨 陷阱3:锁粒度过大
反模式:
// 全局大锁(性能杀手)
RLock globalLock = redisson.getLock("GLOBAL_ORDER_LOCK");
优化方案:
// 细粒度锁(按订单ID分片)
String lockKey = "order:payment:" + (orderId % 100); // 100个分片
RLock segmentLock = redisson.getLock(lockKey);
🚨 陷阱4:锁命名冲突
危险操作:
// 不同业务使用相同锁名
RLock orderLock = redisson.getLock("system_lock");
RLock paymentLock = redisson.getLock("system_lock");
// 支付操作阻塞订单处理!
命名规范:
[系统]:[模块]:[资源类型]:[资源ID]
# 示例:
order:payment:lock:20230815
inventory:adjust:lock:PROD_1001
🚨 陷阱5:重入锁的递归陷阱
public void process() {
lock.lock();
try {
recursiveCall(3); // 递归调用
} finally {
lock.unlock();
}
}
private void recursiveCall(int depth) {
if (depth == 0) return;
lock.lock(); // 重入加锁
try {
recursiveCall(depth - 1);
} finally {
lock.unlock(); // 提前释放外层锁!
}
}
解决方案:
private void recursiveCall(int depth) {
if (depth == 0) return;
// 仅在最外层加锁
if (depth == 3) {
lock.lock();
}
try {
recursiveCall(depth - 1);
} finally {
if (depth == 3) {
lock.unlock();
}
}
}
🚨 陷阱6:网络分区风险
场景:
- 线程A在Redis主节点获得锁
- 主节点宕机,从节点升级为主
- 新主节点无锁记录 → 线程B获得锁
- 分布式锁失效!
解决方案:
// 使用RedLock(需3个独立Redis集群)
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
redLock.lock();
三、源码解析:看门狗如何续命
1. 加锁入口:lock()方法
public void lock() {
try {
lock(-1, null, false); // leaseTime=-1 触发看门狗
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
2. 看门狗启动:scheduleExpirationRenewal()
private void scheduleExpirationRenewal(long threadId) {
// 创建ExpirationEntry跟踪锁状态
ExpirationEntry entry = new ExpirationEntry();
ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);
// 启动续期任务
if (oldEntry == null) {
entry.setThreadId(threadId);
renewExpiration(); // 核心续期方法
}
}
3. 续期核心:renewExpiration()
protected void renewExpiration() {
// 获取当前线程的续期记录
ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
// 创建定时任务(每10秒执行一次)
Timeout task = commandExecutor.getConnectionManager()
.newTimeout(new TimerTask() {
public void run(Timeout timeout) {
// 检查锁是否仍被持有
if (ee.getLocks() > 0) {
// 异步续期操作
RFuture<Boolean> future = renewExpirationAsync(threadId);
future.onComplete((res, e) -> {
if (e != null) {
// 异常处理
return;
}
// 递归调用实现周期续期
if (res) {
renewExpiration();
}
});
}
}
},
internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); // 默认10秒
ee.setTimeout(task);
}
4. 续期Lua脚本
-- KEYS[1] = 锁key
-- ARGV[1] = 续期时间(毫秒)
-- ARGV[2] = 线程标识(UUID:threadId)
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
redis.call('pexpire', KEYS[1], ARGV[1]); -- 续期成功
return 1;
end;
return 0; -- 锁不存在或不属于当前线程
5. 解锁时清理资源
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
// 1. 取消看门狗任务
cancelExpirationRenewal(threadId);
// 2. 执行解锁脚本
return commandExecutor.evalWriteAsync(
getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
"return nil; " +
"end; " +
"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
"if (counter > 0) then " +
"redis.call('pexpire', KEYS[1], ARGV[2]); " +
"return 0; " +
"else " +
"redis.call('del', KEYS[1]); " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; " +
"end; ",
Arrays.asList(getRawName(), getChannelName()),
LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));
}
四、性能优化实战:高并发场景调优
1. 锁竞争优化策略
|
场景 |
问题 |
解决方案 |
效果 |
|
热点账户 |
单一资源高频竞争 |
分段锁 + 队列缓冲 |
并发提升8-10倍 |
|
批量操作 |
长时间持有锁 |
乐观锁 + 版本号 |
减少锁持有时间 |
|
读多写少 |
读写效率低 |
读写锁分离 |
读性能提升90% |
|
分布式事务 |
多资源锁定 |
联锁 + 超时回滚 |
避免死锁 |
2. 分段锁实战:账户余额更新
// 原始热点账户(性能瓶颈)
// RLock accountLock = redisson.getLock("account:" + accountId);
// 分段优化(128段)
int SEGMENTS = 128;
String segmentKey = "account:" + (accountId % SEGMENTS);
RLock segmentLock = redisson.getLock(segmentKey);
segmentLock.lock();
try {
// 更新账户余额
} finally {
segmentLock.unlock();
}
3. 监控指标:锁健康诊断
// 1. 等待队列长度(关键指标)
int queueSize = lock.getQueueSize();
// 2. 锁持有时间
long holdTime = System.currentTimeMillis() - lockStartTime;
// 3. 推送到监控系统
metrics.gauge("redisson.lock.queue.size", queueSize);
metrics.gauge("redisson.lock.hold.time", holdTime);
// 告警规则:queueSize > 100 || holdTime > 3000ms
结语:分布式锁的哲学思考
在分布式系统中,锁是必要的恶而非万能药。Redisson通过精妙设计,在安全与性能、一致性与可用性间找到平衡点。但请铭记:
"锁的终极目标不是阻止并发,而是在混沌中建立秩序。最优雅的锁,是那些最终被架构优化所取代的锁。"
终极建议:
- 优先考虑无锁设计(CAS、状态机)
- 其次选择乐观锁(版本号、时间戳)
- 最后才使用悲观锁(分布式锁)
当你在深夜被锁问题惊醒时,愿本文成为你的救生手册。你的系统稳定性,始于对每一行锁代码的敬畏!

被折叠的 条评论
为什么被折叠?



