Redisson简明教程—你家的锁芯该换了
时间:2025-11-05 07:25:28 出处:应用开发阅读(143)
1.简介
2.看门狗
2-1.为什么需要这个"狗子"?简明教程
2-2.工作原理
2-3.如何“撸狗子”
3.可重入锁:你的分布式"万能钥匙"
3-1.Redisson可重入锁的魔法
3-2.使用姿势大全
3-3.公平锁
3-4.非公平锁
3-5.可重入原理深扒
4.联锁:分布式锁中的"全家桶套餐"
4-1.联锁是什么?——"要么全有,要么全无"的锁芯霸道总裁
4-2.为什么需要联锁?
4-3.联锁使用三件套
4-4.联锁的硬核原理
4-5.联锁的三大禁忌
5.Redisson读写锁:分布式系统中的"读写分离"高手
5-1.使用姿势
5-2.原理深扒
6.信号量(Semaphore):分布式系统中的"限量入场券"
6-1.使用姿势大全
6-2.原理深扒
7.红锁(RedLock):分布式锁界的“联合国维和部队”
7-1.核心原理
7-2.注意事项
7-3.使用姿势
8.闭锁(CountDownLatch):分布式系统中的"集结号"
8-1.使用姿势
8-2.原理深扒
9.总结
1.简介
各位攻城狮们,你还在使用原生命令来上锁么?该换看来你还是不够懒,饺子都给你包好了,简明教程你非要吃大饼配炒韭菜,锁芯快点改善一下“伙食”吧,该换写代码也要来点幸福感。简明教程今天咱们就来聊聊Redisson提供的锁芯各种锁,Redisson就像是该换Redis给Java程序员的一把瑞士军刀,不仅能存数据,简明教程还能玩出各种分布式花样。锁芯
Redis版本:Redis 2.8+,该换理想版本5.0+(支持 Stream、简明教程模块化等高级特性,锁芯Redisson 能秀出全部技能)。该换架构模式:支持单机、哨兵和集群(集群模式可靠性更高)2.看门狗
想象你上厕所(获取锁)时带着一只忠心耿耿的阿黄(看门狗)。当你蹲坑时间快到时(锁快要过期),企商汇阿黄就会大叫:"主人你还没完事吗?我给你续时间啦!"(自动续期)
2-1.为什么需要这个"狗子"?
防止业务没执行完锁就过期:默认锁30秒过期,但万一你的业务要31秒呢?避免锁丢失:如果客户端崩溃,看门狗停止续期,锁最终会自动释放不用手动计算业务时间:再也不用战战兢兢估算业务执行时间了2-2.工作原理

2-3.如何“撸狗子”
复制Config config = new Config(); config.setLockWatchdogTimeout(30000L); // 单位毫秒,默认就是30秒 // ... RedissonClient redisson = Redisson.create(config);1.2.3.4. 复制// 你也可以在加锁时指定(会覆盖默认值) lock.lock(60, TimeUnit.SECONDS); // 这时看门狗会按60秒周期续期1.2.3.可重入锁:你的分布式"万能钥匙"
我们都知道Java中ReentrantLock和synchronized都是可重入锁,但都只能用于单机环境的,在分布式环境下,Redisson给我提供了类似体验的可重入锁。
3-1.Redisson可重入锁的魔法
线程安全:不同JVM的相同线程也可重入自动续期:看门狗机制保活(默认30秒)公平/非公平:两种模式可选超时机制:避免无限等待3-2.使用姿势大全
基础款(阻塞式): 复制RLock lock = redisson.getLock("orderLock"); lock.lock(); // 一直等到天荒地老 try { // 你的核心业务 } finally { lock.unlock(); // 一定要放在finally! }1.2.3.4.5.6.7. 高级款(尝试获取): 复制if (lock.tryLock(3, 30, TimeUnit.SECONDS)) { // 最多等3秒,锁30秒自动过期 try { // 业务处理 } finally { lock.unlock(); } } else { log.warn("获取锁失败,换个姿势再试一次"); }1.2.3.4.5.6.7.8.9. 骚操作款(异步获取): 复制RFuture<Void> lockFuture = lock.lockAsync(); lockFuture.whenComplete((res, ex) -> { if (ex == null) { try { // 异步业务处理 } finally { lock.unlock(); } } });1.2.3.4.5.6.7.8.9.10.3-3.公平锁
排队机制:使用Redis的List结构维护等待队列订阅发布:通过Redis的pub/sub通知下一个等待者双重检查:获取锁时检查自己是否在队列头部 复制RLock fairLock = redisson.getFairLock("myFairLock"); try { fairLock.lock(); // 业务逻辑 } finally { fairLock.unlock(); }1.2.3.4.5.6.7.3-4.非公平锁
直接竞争:所有线程同时尝试CAS操作效率优先:没有队列维护开销可能饥饿:运气差的线程可能长期得不到锁 复制RLock nonFairLock = redisson.getLock("hotItemLock"); if (nonFairLock.tryLock(50, TimeUnit.MILLISECONDS)) { // 拼手速! try { // 秒杀业务逻辑 } finally { nonFairLock.unlock(); } }1.2.3.4.5.6.7.8.3-5.可重入原理深扒
加锁Lua脚本伪代码:
复制-- 参数:锁key、锁超时时间、客户端ID+线程ID if (redis.call(exists, KEYS[1]) == 0) then -- 锁不存在,亿华云直接获取 redis.call(hset, KEYS[1], ARGV[2], 1); redis.call(pexpire, KEYS[1], ARGV[1]); returnnil; end; if (redis.call(hexists, KEYS[1], ARGV[2]) == 1) then -- 重入情况:计数器+1 redis.call(hincrby, KEYS[1], ARGV[2], 1); redis.call(pexpire, KEYS[1], ARGV[1]); returnnil; end; -- 锁被其他线程持有 return redis.call(pttl, KEYS[1]); -- 返回剩余过期时间1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.解锁Lua脚本伪代码:
复制-- 参数:锁key、客户端ID+线程ID if (redis.call(hexists, KEYS[1], ARGV[1]) == 0) then -- 压根没持有锁 returnnil; end; -- 重入次数-1 local counter = redis.call(hincrby, KEYS[1], ARGV[1], -1); if (counter > 0) then -- 还有重入次数,更新过期时间 redis.call(pexpire, KEYS[1], 30000); return0; else -- 最后一次解锁,删除key redis.call(del, KEYS[1]); -- 发布解锁消息 redis.call(publish, KEYS[2], ARGV[2]); return1; end;1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.4.联锁:分布式锁中的"全家桶套餐"
4-1.联锁是什么?——"要么全有,要么全无"的霸道总裁
想象你要同时约三个女神约会:
女神A:周末有空 ✅女神B:周末有空 ✅女神C:周末要加班 ❌ Redisson联锁的做法是:只要有一个拒绝,就取消所有约会!这就是联锁的"All or Nothing"哲学。4-2.为什么需要联锁?
典型场景:跨资源事务:需要同时锁定订单、库存、优惠券三个系统数据一致性:确保多个关联资源同时被保护避免死锁:防止交叉等待导致的死锁情况4-3.联锁使用三件套
基本用法: 复制// 准备三把锁(就像三个女神的联系方式) RLock lock1 = redisson.getLock("order_lock"); RLock lock2 = redisson.getLock("stock_lock"); RLock lock3 = redisson.getLock("coupon_lock"); // 创建联锁"约会套餐" RedissonMultiLock multiLock = new RedissonMultiLock(lock1, lock2, lock3); try { // 尝试同时锁定(约三位女神) if (multiLock.tryLock(3, 30, TimeUnit.SECONDS)) { // 三位都同意了!开始你的表演 processOrder(); updateStock(); useCoupon(); } else { log.warn("有个女神拒绝了你"); } } finally { multiLock.unlock(); // 记得送她们回家 }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21. 高阶技巧: 复制// 动态构造联锁(适合不确定数量的资源) List<RLock> locks = resourceIds.stream() .map(id -> redisson.getLock("resource_" + id)) .collect(Collectors.toList()); RedissonMultiLock dynamicLock = new RedissonMultiLock(locks.toArray(new RLock[0]));1.2.3.4.5.6.4-4.联锁的硬核原理
加锁流程:顺序加锁:按传入锁的顺序依次尝试获取失败回滚:任意一个锁获取失败时,释放已获得的所有锁统一过期时间:所有锁使用相同的过期时间底层Lua脚本(简化版): 复制-- 参数:多个锁的KEYS,统一过期时间,线程标识 local failed = false for i, key inipairs(KEYS) do if redis.call(setnx, key, ARGV[2]) == 0then failed = true break end redis.call(expire, key, ARGV[1]) end if failed then -- 释放已经获取的锁 for j = 1, i-1do redis.call(del, KEYS[j]) end return0 end return11.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.4-5.联锁的三大禁忌
乱序使用(导致死锁): 复制// 线程1: multiLock(lockA, lockB).lock(); // 线程2: multiLock(lockB, lockA).lock(); // 危险!可能死锁1.2.3.4.5.✅ 正确做法:全局统一加锁顺序
混合锁类型: 复制// 混合普通锁和公平锁 new MultiLock(lock1, fairLock2); // 不推荐1.2.✅ 正确做法:使用相同特性的源码下载锁组合
忽略部分锁失败: 复制if (!multiLock.tryLock()) { // 直接返回,不处理部分获取成功的情况 return; // 危险! }1.2.3.4.✅ 正确做法:确保完全获取或完全失败
5.Redisson读写锁:分布式系统中的"读写分离"高手
也许单机版的ReentrantReadWriteLock你听说过,但是分布式环境下的版本可能很少接触到。
典型场景:读多写少系统:比如商品详情页(每秒上万次读取,每分钟几次更新)数据一致性要求:保证读取时不会读到半成品数据系统性能优化:避免读操作被不必要的串行化5-1.使用姿势
复制RReadWriteLock rwLock = redisson.getReadWriteLock("libraryBook_123"); // 读操作(多个线程可同时进入) rwLock.readLock().lock(); try { // 查询数据(安全读取) Book book = getBookFromDB(123); } finally { rwLock.readLock().unlock(); } // 写操作(独占访问) rwLock.writeLock().lock(); try { // 修改数据(安全写入) updateBookInDB(123, newVersion); } finally { rwLock.writeLock().unlock(); }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.5-2.原理深扒
加读锁Lua脚本(简化): 复制-- 检查是否可以加读锁(没有写锁或当前线程持有写锁) if redis.call(hget, KEYS[1], mode) == write then -- 如果有写锁且不是当前线程持有,则失败 if redis.call(hexists, KEYS[1], ARGV[2]) == 0 then return 0; end; end; -- 增加读锁计数 redis.call(hincrby, KEYS[1], ARGV[1], 1); redis.call(pexpire, KEYS[1], ARGV[3]); return 1;1.2.3.4.5.6.7.8.9.10.11.12. 加写锁Lua脚本(简化): 复制-- 检查是否已有锁 if redis.call(exists, KEYS[1]) == 1 then -- 如果是读模式或有其他写锁 if redis.call(hget, KEYS[1], mode) == read or redis.call(hlen, KEYS[1]) > 1 then return0; end; -- 如果是当前线程持有的写锁(重入) if redis.call(hexists, KEYS[1], ARGV[2]) == 1 then redis.call(hincrby, KEYS[1], ARGV[2], 1); return1; end; end; -- 获取写锁 redis.call(hset, KEYS[1], mode, write); redis.call(hset, KEYS[1], ARGV[2], 1); redis.call(pexpire, KEYS[1], ARGV[3]); return1;1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.6.信号量(Semaphore):分布式系统中的"限量入场券"
同样的味道,Java中Semaphore的分布式版本。
6-1.使用姿势大全
基础用法: 复制// 获取信号量(初始10个许可) RSemaphore semaphore = redisson.getSemaphore("apiLimit"); semaphore.trySetPermits(10); // 设置许可数量 // 获取许可(阻塞直到可用) semaphore.acquire(); try { // 执行业务(保证最多10个并发) callLimitedAPI(); } finally { semaphore.release(); // 记得归还! } // 尝试获取(非阻塞) if (semaphore.tryAcquire()) { try { // 抢到许可了! } finally { semaphore.release(); } } else { log.warn("系统繁忙,请稍后再试"); }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23. 高级技巧: 复制// 带超时的尝试获取 if (semaphore.tryAcquire(3, 500, TimeUnit.MILLISECONDS)) { try { // 在500ms内获取到3个许可 batchProcess(); } finally { semaphore.release(3); // 归还多个许可 } } // 动态调整许可数量 semaphore.addPermits(5); // 增加5个许可(扩容) semaphore.reducePermits(3); // 减少3个许可(缩容)1.2.3.4.5.6.7.8.9.10.11.12.13.6-2.原理深扒
获取许可的Lua脚本(简化):
复制-- 参数:信号量key、请求许可数 local value = redis.call(get, KEYS[1]) if value >= ARGV[1] then return redis.call(decrby, KEYS[1], ARGV[1]) else return -1 end1.2.3.4.5.6.7.释放许可的Lua脚本(简化):
复制-- 参数:信号量key、释放许可数 return redis.call(incrby, KEYS[1], ARGV[1])1.2.7.红锁(RedLock):分布式锁界的“联合国维和部队”
RedLock 的诞生是为了对抗单点 Redis 挂掉后锁失效的问题。它的目标就是:“即使部分节点挂了,我也要稳如老狗”。
7-1.核心原理
Redisson 的 RedLock 实现来源于 Redis 作者 antirez 提出的 Redlock 算法。流程如下:
准备多个独立的 Redis 节点(注意:是互相独立的,不是主从复制结构)。客户端依次向这些节点尝试加锁(使用 SET NX PX 命令)。记录耗时:加锁操作总共不能超过锁过期时间的 1/2(比如设置锁有效期 10 秒,那就必须 5 秒内搞定加锁)。加锁成功节点超过半数(N/2 + 1)视为成功。若失败,立刻释放所有加锁成功的节点,以避免资源死锁。释放锁时,同样要向所有节点发送 unlock 操作。7-2.注意事项
RedLock 是为多主 Redis 实例准备的,不是给 Redis Cluster 用的。你得维护多个彼此独立的 Redis 实例,部署和运维成本更高。RedLock 的“强一致性”并非线性一致性,它只是通过多点确认提升“高可用性”。7-3.使用姿势
复制// 准备多个独立的RLock实例 RLock lock1 = redissonClient1.getLock("lock"); RLock lock2 = redissonClient2.getLock("lock"); RLock lock3 = redissonClient3.getLock("lock"); // 构造红锁(建议奇数个,通常3/5个) RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3); try { // 尝试获取锁(等待时间100s,锁持有时间30s) boolean locked = redLock.tryLock(100, 30, TimeUnit.SECONDS); if (locked) { // 执行业务逻辑 doCriticalWork(); } } finally { redLock.unlock(); }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.8.闭锁(CountDownLatch):分布式系统中的"集结号"
一样是Java的CountDownLatch的分布式版本,用法也是基本一样。
8-1.使用姿势
复制// 主协调节点(教官) RCountDownLatch latch = redisson.getCountDownLatch("batchTaskLatch"); latch.trySetCount(5); // 需要等待5个任务 // 工作节点(学员) RCountDownLatch workerLatch = redisson.getCountDownLatch("batchTaskLatch"); workerLatch.countDown(); // 完成任务时调用 // 主节点等待(在另一个线程/JVM) latch.await(); // 阻塞直到计数器归零 System.out.println("所有任务已完成!");1.2.3.4.5.6.7.8.9.10.11.8-2.原理深扒
关键操作伪代码:
复制-- countDown操作 local remaining = redis.call(decr, KEYS[1]) if remaining <= 0then redis.call(publish, KEYS[2], 0) -- 通知所有等待者 redis.call(del, KEYS[1]) -- 清理计数器 end return remaining -- await操作 local count = redis.call(get, KEYS[1]) if count == falseortonumber(count) <= 0then return1-- 已经完成 end return0-- 需要继续等待1.2.3.4.5.6.7.8.9.10.11.12.13.14.9.总结
Redisson 提供了丰富的分布式锁实现,适用于各种分布式场景,使用体验更好,选择锁类型时应根据具体业务场景和需求来决定,同时要注意锁的粒度和持有时间,避免分布式死锁和性能问题。
关于作者,高宏杰,转转门店技术部研发工程师。