当前位置: 首页 > ops >正文

【Redis】笔记|第5节|Redisson实现高并发分布式锁核心源码

一、加锁流程

1. 核心方法调用链
RLock lock = redisson.getLock("resource");
lock.lock(); // 阻塞式加锁↳ lockInterruptibly()↳ tryAcquire(-1, leaseTime, unit) // leaseTime=-1表示启用看门狗↳ tryAcquireAsync()↳ tryLockInnerAsync() // 执行Lua脚本
2. Lua脚本实现(关键)
// RedissonLock.tryLockInnerAsync()
"if (redis.call('exists', KEYS[1]) == 0) then " +  // 锁不存在"redis.call('hset', KEYS[1], ARGV[2], 1); " +  // 创建锁(Hash结构)"redis.call('pexpire', KEYS[1], ARGV[1]); " +  // 设置过期时间"return nil; " +
"end; " +
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +  // 锁已存在,判断是否重入"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +  // 重入次数+1"redis.call('pexpire', KEYS[1], ARGV[1]); " +  // 重置过期时间"return nil; " +
"end; " +
"return redis.call('pttl', KEYS[1]);",  // 返回剩余时间,加锁失败
Collections.singletonList(getName()),  // KEYS[1]: 锁名称(如"resource")
internalLockLeaseTime, getLockName(threadId)  // ARGV[1]: 过期时间;ARGV[2]: 线程标识(UUID:threadId)
3. 关键点
  • 原子性:通过Lua脚本保证检查锁和创建锁的原子性。
  • 数据结构:使用Redis的Hash存储锁信息,field为线程标识,value为重入次数。
  • 过期时间:默认30秒(看门狗机制自动续期),防止死锁。

二、解锁流程

1. 核心方法调用链
lock.unlock();↳ unlockAsync()↳ unlockInnerAsync() // 执行Lua脚本
2. Lua脚本实现(关键)
// RedissonLock.unlockInnerAsync()
"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +  // 锁不存在或非当前线程持有"return nil; " +
"end; " +
"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +  // 重入次数-1
"if (counter > 0) then " +  // 重入次数>0,继续持有锁"redis.call('pexpire', KEYS[1], ARGV[2]); " +  // 重置过期时间"return 0; " +
"else " +  // 重入次数=0,释放锁"redis.call('del', KEYS[1]); " +  // 删除锁"redis.call('publish', KEYS[2], ARGV[1]); " +  // 发布锁释放消息(通知等待线程)"return 1; " +
"end; " +
"return nil;",
Arrays.asList(getName(), getChannelName()),  // KEYS[1]: 锁名称;KEYS[2]: 发布订阅通道
LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId)  // ARGV[3]: 线程标识
3. 关键点
  • 安全释放:仅锁持有者(UUID:threadId匹配)可释放锁。
  • 发布订阅:锁释放时通过Redis的PUBLISH通知等待线程。
  • 重入处理:通过hincrby -1递减重入次数,确保正确释放。

三、锁续时(看门狗机制)

1. 触发条件
  • 当使用无参lock()方法时(即未指定leaseTime),默认启用看门狗。
  • 看门狗默认每10秒(internalLockLeaseTime / 3)续期一次,将锁过期时间重置为30秒。
2. 核心源码
// RedissonLock.scheduleExpirationRenewal()
private void scheduleExpirationRenewal(long threadId) {ExpirationEntry entry = new ExpirationEntry();ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);entry.addThreadId(threadId);// 创建定时任务Timeout task = commandExecutor.getConnectionManager().newTimeout(timeout -> {RFuture<Boolean> future = renewExpirationAsync(threadId);future.whenComplete((res, e) -> {if (e != null) {log.error("Can't update lock " + getName() + " expiration", e);return;}if (res) {// 续期成功,递归调用scheduleExpirationRenewal(threadId);}});}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);entry.setTimeout(task);
}
3. 续期Lua脚本
// RedissonLock.renewExpirationAsync()
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +  // 锁存在且为当前线程持有"redis.call('pexpire', KEYS[1], ARGV[1]); " +  // 重置过期时间"return 1; " +
"end; " +
"return 0;",
Collections.singletonList(getName()),
internalLockLeaseTime, getLockName(threadId)
4. 关键点
  • 自动续期:通过Netty的Timeout实现定时任务。
  • 避免死锁:若业务执行时间超长,看门狗会持续续期,直到业务完成或线程崩溃。

四、重入锁实现

1. 数据结构

使用Redis的Hash存储锁信息:

  • Key:锁名称(如"resource")。
  • Field:线程标识(UUID:threadId)。
  • Value:重入次数(初始为1,每次重入+1)。
2. 加锁时的重入逻辑
// Lua脚本片段
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +  // 锁已存在,判断是否重入"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +  // 重入次数+1"redis.call('pexpire', KEYS[1], ARGV[1]); " +  // 重置过期时间"return nil; " +  // 返回nil表示加锁成功(重入)
"end; "
3. 解锁时的重入逻辑
// Lua脚本片段
"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +  // 重入次数-1
"if (counter > 0) then " +  // 重入次数>0,继续持有锁"redis.call('pexpire', KEYS[1], ARGV[2]); " +  // 重置过期时间"return 0; " +  // 返回0表示锁未释放
"else " +  // 重入次数=0,释放锁"redis.call('del', KEYS[1]); " +  // 删除锁"return 1; " +  // 返回1表示锁已释放
"end; "
4. 关键点
  • 线程安全:通过UUID:threadId确保同一线程可重入。
  • 原子计数:使用hincrby保证计数操作的原子性。

五、lock()与tryLock()的区别

1. 核心区别对比表

特性

lock()

tryLock()

阻塞行为

阻塞直到获取锁

立即返回或在指定时间内等待

超时机制

无超时,默认启用看门狗自动续期

可自定义等待时间和锁持有时间

异常处理

不响应中断(抛出 InterruptedException

可响应中断(通过重载方法)

返回值

void

boolean(是否获取锁成功)

看门狗默认启用

是(无参时)

否(需显式设置超时参数)

典型场景

必须获取锁才能执行的场景

可重试或放弃的场景

2. 源码差异分析
// lock() 源码片段
public void lock() {try {lock(-1, null, false); // leaseTime=-1表示启用看门狗} catch (InterruptedException e) {Thread.currentThread().interrupt();}
}// tryLock() 源码片段
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {// 1. 计算超时时间long time = unit.toMillis(waitTime);long current = System.currentTimeMillis();long threadId = Thread.currentThread().getId();// 2. 尝试获取锁Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId);if (ttl == null) {return true; // 获取成功}// 3. 超时处理逻辑(循环尝试或等待通知)// ...
}
3. 使用场景对比
// lock() 使用示例
RLock lock = redisson.getLock("order:123");
try {lock.lock(); // 阻塞直到获取锁// 执行关键业务逻辑
} finally {lock.unlock();
}// tryLock() 使用示例
RLock lock = redisson.getLock("inventory:apple");
try {// 尝试在5秒内获取锁,持有30秒if (lock.tryLock(5, 30, TimeUnit.SECONDS)) {// 获取锁成功,执行操作} else {// 获取锁失败,执行降级逻辑}
} catch (InterruptedException e) {Thread.currentThread().interrupt();
} finally {if (lock.isHeldByCurrentThread()) {lock.unlock();}
}

六、总结

Redisson分布式锁的核心优势:

  1. 原子性:通过Lua脚本确保操作的原子性。
  2. 可重入:基于Hash结构实现线程级别的重入计数。
  3. 高可用:通过看门狗机制避免锁过期导致的数据不一致。
  4. 高性能:基于Netty的异步通信模型。
  5. 安全释放:通过UUID:threadId确保锁只能被持有者释放。

最佳实践建议

  • 优先使用 tryLock():避免长时间阻塞,提高系统吞吐量。
  • 明确锁持有时间:根据业务场景合理设置leaseTime,避免过度依赖看门狗。
  • 异常处理:使用带超时参数的tryLock(),并处理中断异常。
http://www.xdnf.cn/news/10510.html

相关文章:

  • 个人总结八股文之-基础篇(持续更新)
  • 汽车软件 OTA 升级技术发展现状与趋势
  • 设计模式——中介者设计模式(行为型)
  • 【Qt开发】对话框
  • 深入理解 Linux 文件系统与日志文件分析
  • NodeJS全栈WEB3面试题——P8项目实战类问题(偏全栈)
  • 安全态势感知中的告警误报思考
  • 多群组部署
  • X浏览器APP:轻巧快捷,畅享极速浏览
  • TomSolver 库 | config详解及其测试
  • ANN与SNN的那些事
  • 动态规划(10):状态压缩
  • 力扣LeetBook数组和字符串--数组简介
  • Spring Security入门:创建第一个安全REST端点项目
  • [RoarCTF 2019]Easy Calc
  • SQL 逻辑处理顺序详解
  • 第二章支线五 ·CSS炼金续章:变量与暗黑主题术
  • 放弃 tsc+nodemon 使用 tsx 构建Node 环境下 TypeScript + ESM 开发环境搭建指南
  • SpringMVC的注解
  • StarRocks物化视图
  • 可视化大屏通用模板Axure原型设计案例
  • 代码随想录60期day54
  • [leetcode] 二分算法
  • 密码学:解析Feistel网络结构及实现代码
  • 传送文件利器wormhole的使用方法
  • 【iOS】ARC 与 Autorelease
  • 数据库系统概论(十五)详细讲解数据库视图
  • Linux运维笔记:服务器安全加固
  • HTML 中 class 属性介绍、用法
  • AlmaLinux OS 10 正式发布:兼容 RHEL 10 带来多项技术革新