【大白话说Java面试题 第173题】【07_Redis篇】第9题:使用过 Redisson 嘛?说说它的原理?

发布时间:2026/7/15 8:54:44
【大白话说Java面试题 第173题】【07_Redis篇】第9题:使用过 Redisson 嘛?说说它的原理? 第9题使用过 Redisson 嘛说说它的原理回答核心考点Redisson是 Java 生态中最成熟的 Redis 分布式锁实现大厂面试不会只问用过没有而是深入考察底层数据结构Hash 结构、Pub/Sub 频道、看门狗续期的 Netty 定时任务实现、可重入锁的 Lua 脚本原子逻辑、公平锁与非公平锁的队列差异以及MultiLock 与 Redlock 的架构区别。面试官真正想判断的是你是否阅读过 Redisson 源码能否从使用层面深入到原理层面并准确区分 Redisson 各组件的适用边界。1. Redisson 整体架构1.1 核心组件组件职责关键类RedissonClient客户端入口管理连接池RedissonConnectionManager管理 Redis 连接NettyMasterSlaveConnectionManagerCommandExecutor执行 Redis 命令同步/异步CommandAsyncExecutorRLock分布式锁接口RedissonLockRFuture异步结果封装RedissonPromise1.2 与 Redis 的交互方式Redisson 基于Netty实现 NIO 网络通信所有 Redis 命令通过异步管道发送支持连接池复用和自动重连。Java 业务线程 → RLock API → CommandExecutor → Netty Channel → Redis Server ↓ 异步回调RFuture← 结果解析 ← 响应返回2. 可重入锁实现原理Hash 结构2.1 数据结构Redisson 使用 RedisHash结构存储锁信息而非简单 String# 锁的 Redis 结构HGETALL lock:order:1001# 返回# 1) UUID:ThreadID # field 线程唯一标识# 2) 3 # value 重入次数字段说明Key锁名如lock:order:1001FieldUUID:ThreadID格式为Redisson 实例 ID 线程 IDValue重入次数hincrby原子增减2.2 加锁 Lua 脚本源码级-- RedissonLock.tryLockInnerAsync 的核心脚本if(redis.call(exists,KEYS[1])0)then-- 锁不存在首次加锁redis.call(hincrby,KEYS[1],ARGV[2],1);redis.call(pexpire,KEYS[1],ARGV[1]);returnnil;-- 返回 nil 表示获取成功end;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]);returnnil;end;-- 锁被其他线程持有返回剩余过期时间毫秒returnredis.call(pttl,KEYS[1]);参数值说明KEYS[1]lock:order:1001锁名ARGV[1]30000过期时间毫秒ARGV[2]UUID:ThreadID线程唯一标识2.3 解锁 Lua 脚本源码级-- RedissonLock.unlockInnerAsync 的核心脚本if(redis.call(hexists,KEYS[1],ARGV[3])0)then-- 不是当前线程持有的锁返回 nil解锁失败returnnil;end;localcounterredis.call(hincrby,KEYS[1],ARGV[3],-1);if(counter0)then-- 仍持有锁重入次数 0续期并返回 0redis.call(pexpire,KEYS[1],ARGV[2]);return0;else-- 完全释放删除锁并发布解锁通知redis.call(del,KEYS[1]);redis.call(publish,KEYS[2],ARGV[1]);return1;end;参数值说明KEYS[1]lock:order:1001锁名KEYS[2]redisson_lock__channel:{lock:order:1001}解锁通知频道ARGV[1]0解锁消息内容ARGV[2]30000过期时间毫秒ARGV[3]UUID:ThreadID线程唯一标识2.4 可重入流程示例RLocklockredissonClient.getLock(order:1001);lock.lock();// Hash: { UUID:ThreadID: 1 }, pttl30000mslock.lock();// Hash: { UUID:ThreadID: 2 }, pttl30000ms重入lock.lock();// Hash: { UUID:ThreadID: 3 }, pttl30000ms重入lock.unlock();// Hash: { UUID:ThreadID: 2 }, pttl30000mslock.unlock();// Hash: { UUID:ThreadID: 1 }, pttl30000mslock.unlock();// DEL lock:order:1001 PUBLISH 解锁通知3. 看门狗Watchdog自动续期机制3.1 为什么需要看门狗业务执行时间不确定如 Full GC、慢查询固定过期时间可能导致锁提前释放。看门狗在锁持有期间自动续期直到业务完成。3.2 核心参数参数默认值说明lockWatchdogTimeout30000ms锁看门狗超时时间锁的过期时间续期周期10000mslockWatchdogTimeout / 3每 10 秒检查一次续期目标30000ms每次续期恢复到lockWatchdogTimeout3.3 续期 Lua 脚本-- 看门狗续期脚本if(redis.call(hexists,KEYS[1],ARGV[2])1)thenredis.call(pexpire,KEYS[1],ARGV[1]);return1;end;return0;3.4 看门狗启动与停止条件条件行为说明lock()不指定 leaseTime启动看门狗默认行为自动续期lock(10, TimeUnit.SECONDS)不启动看门狗显式指定过期时间10 秒后强制释放unlock()调用成功停止看门狗取消定时任务业务线程中断停止看门狗线程死亡不再续期3.5 底层实现Netty HashedWheelTimer// Redisson 看门狗定时任务简化源码逻辑privatevoidscheduleExpirationRenewal(longthreadId){// 使用 Netty 的时间轮定时器TimeouttaskcommandExecutor.getConnectionManager().newTimeout(timeout-{// 异步续期RFutureBooleanfuturerenewExpirationAsync(threadId);future.onComplete((res,e)-{if(e!null){// 续期异常重新调度scheduleExpirationRenewal(threadId);return;}if(res){// 续期成功递归调度下一次scheduleExpirationRenewal(threadId);}});},internalLockLeaseTime/3,// 延迟 10 秒执行TimeUnit.MILLISECONDS);// 将任务存入 Map解锁时取消expirationRenewalMap.put(getEntryName(),task);}关键设计时间轮定时器Netty 的HashedWheelTimer相比ScheduledThreadPoolExecutor性能更高适合高频定时任务异步续期续期操作通过异步命令发送不阻塞业务线程递归调度每次续期成功后递归调度下一次续期形成永续续期任务取消解锁时从expirationRenewalMap取出任务并取消停止续期。4. 阻塞重试机制Pub/Sub 事件驱动4.1 为什么不用自旋简单的while循环自旋会导致 CPU 空转。Redisson 通过Pub/Sub 订阅解锁通知实现事件驱动的阻塞重试无 CPU 浪费。4.2 核心流程// RedissonLock.tryLock 的阻塞重试逻辑简化publicbooleantryLock(longwaitTime,longleaseTime,TimeUnitunit){longdeadlineSystem.currentTimeMillis()unit.toMillis(waitTime);// 1. 首次尝试获取锁LongttltryAcquire(leaseTime,unit,threadId);if(ttlnull)returntrue;// 获取成功// 2. 计算剩余等待时间longremainingdeadline-System.currentTimeMillis();if(remaining0)returnfalse;// 3. 订阅解锁通知频道// 频道名redisson_lock__channel:{lock:order:1001}RFutureRedissonLockEntrysubscribeFuturesubscribe(threadId);// 4. 阻塞等待解锁通知或超时if(!subscribeFuture.await(remaining,TimeUnit.MILLISECONDS)){returnfalse;// 等待超时}// 5. 收到通知后循环重试while(remaining0){ttltryAcquire(leaseTime,unit,threadId);if(ttlnull)returntrue;// 获取成功remainingdeadline-System.currentTimeMillis();if(remaining0)returnfalse;// 等待锁释放通知或 ttl 过期getEntry(threadId).getLatch().tryAcquire(ttl,TimeUnit.MILLISECONDS);}returnfalse;}4.3 Pub/Sub 频道设计频道名用途消息内容redisson_lock__channel:{lockName}解锁通知0固定值redisson_lock__channel:{lockName}:1公平锁等待队列通知等待线程的排序信息关键设计每个锁对应一个独立的 Pub/Sub 频道解锁时通过PUBLISH广播通知所有等待线程被唤醒后竞争锁。5. 公平锁与非公平锁5.1 非公平锁默认RedissonLock默认实现获取锁时不考虑等待顺序所有线程公平竞争。优点吞吐量大无队列维护开销。缺点可能导致某些线程长期饥饿一直抢不到锁。5.2 公平锁RedissonFairLock按请求锁的顺序获取先请求的线程优先获得锁。RLockfairLockredissonClient.getFairLock(order:1001);fairLock.lock();// 公平锁实现原理每个请求锁的线程在 Redis 中创建一个等待队列节点ZSet 或 List解锁时按照队列顺序通知下一个等待线程被通知的线程获取锁其他线程继续等待。对比维度非公平锁公平锁吞吐量高低队列维护开销饥饿风险存在不存在实现复杂度低高需维护等待队列适用场景大多数场景需要严格顺序保证的场景6. 主从一致性方案MultiLock 与 Redlock6.1 MultiLock联锁要求所有指定的 Redis 节点都加锁成功才算获取锁成功。RLocklock1redissonClient1.getLock(order:1001);RLocklock2redissonClient2.getLock(order:1001);RLocklock3redissonClient3.getLock(order:1001);RLockmultiLockredissonClient.getMultiLock(lock1,lock2,lock3);multiLock.lock();// 三个节点全部加锁成功才生效特点可靠性最高所有节点都有锁但可用性最低任一节点故障即加锁失败。6.2 Redlock红锁要求多数派N/2 1节点加锁成功且总耗时小于锁过期时间。ConfigconfignewConfig();config.useRedLock(newRedissonClientConfig(redis://node1:6379),newRedissonClientConfig(redis://node2:6379),newRedissonClientConfig(redis://node3:6379));RedissonClientredissonRedisson.create(config);RLockredLockredisson.getRedLock(order:1001);redLock.lock();特点平衡可靠性和可用性容忍部分节点故障。6.3 MultiLock vs Redlock 对比维度MultiLockRedlock成功条件所有节点加锁成功多数派节点加锁成功可靠性极高所有节点有锁高多数派有锁可用性低任一节点故障即失败中容忍少数节点故障适用场景极端强一致、节点数少高可用要求、节点数多7. Redisson 其他分布式组件组件功能底层实现RMap分布式 MapRedis HashRSet分布式 SetRedis SetRList分布式 ListRedis ListRQueue分布式队列Redis List/StreamRDelayedQueue延迟队列Redis ZSet 定时任务RAtomicLong分布式原子计数Redis String LuaRCountDownLatch分布式倒计时门闩Redis String Pub/SubRPermitExpirableSemaphore分布式信号量Redis String ZSetRBucket分布式对象存储Redis String8. 生产环境避坑指南8.1 锁的过期时间必须大于业务最大执行时间如果业务执行时间不确定使用lock()启用看门狗而非lock(10, TimeUnit.SECONDS)。8.2 避免锁粒度过粗getLock(global)会导致所有操作串行。应按业务 ID 细粒度加锁。8.3 注意看门狗的续期上限看门狗默认无限续期如果业务线程死循环或阻塞锁将永远不被释放。建议业务逻辑设置超时熔断监控锁的持有时间超过阈值如 60 秒强制告警并人工介入。8.4 公平锁的性能陷阱公平锁维护等待队列吞吐量显著低于非公平锁。除非业务严格要求顺序否则使用默认非公平锁。8.5 Redlock 的时钟同步要求使用 Redlock 时必须确保所有 Redis 节点时钟同步NTP否则锁过期时间计算会出错。8.6 连接池配置优化# Redisson 连接池配置singleServerConfig:connectionMinimumIdleSize:10connectionPoolSize:64idleConnectionTimeout:10000connectTimeout:10000timeout:3000retryAttempts:3retryInterval:15009. 面试官追问与高分回答模板追问 1“Redisson 分布式锁的原理是什么”低分回答“用 Hash 结构实现可重入看门狗续期Pub/Sub 重试。”没有源码级细节高分回答Redisson 分布式锁的核心原理分四层数据结构层使用 Redis Hash 结构存储锁field为UUID:ThreadIDvalue为重入次数。Lua 脚本原子执行hexists判断归属 →hincrby修改计数 →del完全释放。看门狗层基于 Netty 的HashedWheelTimer每 10 秒leaseTime/3异步执行 Lua 脚本续期将锁过期时间重置为 30 秒。业务完成或线程中断时取消定时任务。重试层首次获取失败后订阅redisson_lock__channel:{lockName}频道阻塞等待解锁通知Pub/Sub避免 CPU 空转。收到通知后循环重试直到超时。多节点层MultiLock 要求所有节点加锁成功Redlock 要求多数派成功解决主从一致性问题。生产环境必须直接使用 Redisson严禁手写 SETNX。追问 2“看门狗机制是怎么实现的如果业务线程挂了怎么办”高分回答看门狗基于 Netty 的HashedWheelTimer实现获取锁时如果调用lock()不指定 leaseTime启动定时任务延迟 10 秒后执行任务执行时通过 Lua 脚本检查锁是否仍被当前线程持有hexists如果是则pexpire续期到 30 秒续期成功后递归调度下一次续期任务形成永续续期解锁时从expirationRenewalMap取出任务并取消。如果业务线程挂了未调用 unlock看门狗会随线程死亡而停止续期因为定时任务引用在线程上下文中锁在 30 秒后自动释放不会永久死锁。但如果线程死循环或阻塞看门狗会持续续期导致锁永不释放这是需要监控告警的极端情况。追问 3“Redisson 的公平锁和非公平锁有什么区别”高分回答Redisson 默认使用非公平锁RedissonLock所有线程公平竞争吞吐量大但可能饥饿。公平锁RedissonFairLock按请求顺序分配锁每个请求锁的线程在 Redis 中创建一个等待队列节点通常用 ZSet 或 List 维护顺序解锁时按照队列顺序通过 Pub/Sub 通知下一个等待线程被通知的线程获取锁其他线程继续阻塞等待。公平锁消除了饥饿问题但维护队列的开销大吞吐量显著低于非公平锁。除非业务严格要求顺序如排队扣减库存否则使用默认非公平锁。追问 4“MultiLock 和 Redlock 有什么区别”高分回答两者都是 Redisson 解决多节点一致性的方案但成功条件和适用场景不同MultiLock联锁要求所有指定的 Redis 节点都加锁成功才算获取锁成功。可靠性极高所有节点都有锁但可用性低任一节点故障即失败。适合节点数少、极端强一致的场景。Redlock红锁要求多数派N/2 1节点加锁成功且总耗时小于锁过期时间。平衡了可靠性和可用性容忍部分节点故障。适合节点数多、高可用要求的场景。但 Redlock 存在理论争议Martin Kleppmann 论文时钟漂移、GC 停顿、无 fencing token。实际工程中在时钟同步、网络稳定的环境中足够可靠但极端场景应优先选择 ZooKeeper 或 etcd。追问 5“Redisson 除了分布式锁还用过哪些组件”高分回答Redisson 是一个完整的 Redis 分布式工具集除了分布式锁我还使用过RMap分布式 Map基于 Redis Hash支持本地缓存Near Cache和读写分离。RDelayedQueue延迟队列基于 Redis ZSet 存储延迟任务Redisson 后台线程定时扫描到期任务并转移到目标队列。用于订单超时取消、定时通知等场景。RAtomicLong分布式原子计数器基于 Redis String Lua 脚本实现 CAS 操作。RCountDownLatch分布式倒计时门闩基于 Redis String Pub/Sub 实现跨 JVM 的线程协调。RPermitExpirableSemaphore分布式信号量支持许可证过期自动释放用于限流场景。Redisson 的优势是将 Redis 原语封装为 Java 熟悉的并发工具接口降低使用门槛。追问 6“Redisson 连接 Redis 用的是阻塞 IO 还是非阻塞 IO”高分回答Redisson 基于Netty实现NIO 非阻塞 IO。所有 Redis 命令通过异步管道发送支持连接池复用和自动重连。具体特点异步命令RFuture封装异步结果支持回调和组合操作连接池维护最小空闲连接和最大连接数避免频繁创建销毁连接自动重连连接断开后自动重试对业务透明Pub/Sub 独立连接订阅操作使用独立连接避免阻塞普通命令通道。相比 Jedis 的阻塞 BIORedisson 的 NIO 在高并发下性能更高连接数更少。10. 方案选型速查表场景推荐方案说明通用分布式锁redissonClient.getLock()非公平锁吞吐量高默认启用看门狗需要严格顺序保证redissonClient.getFairLock()公平锁消除饥饿但吞吐量低多 Redis 节点高可用redissonClient.getRedLock()多数派机制容忍部分节点故障极端强一致所有节点redissonClient.getMultiLock()所有节点加锁成功可用性低业务执行时间不确定lock()不指定 leaseTime启用看门狗自动续期业务执行时间确定lock(leaseTime, unit)禁用看门狗固定过期时间延迟队列redissonClient.getDelayedQueue()基于 ZSet 实现用于定时任务分布式计数器redissonClient.getAtomicLong()基于 String LuaCAS 操作面试官想要的满分总结Redisson 是 Java 生态中最成熟的 Redis 分布式锁实现其核心价值在于将能用的分布式锁升级为生产级可靠的分布式锁。理解 Redisson 必须抓住四个底层机制Hash 可重入fieldUUID:ThreadID, value重入次数Lua 脚本原子执行判断、计数、释放看门狗续期NettyHashedWheelTimer每 10 秒异步续期解决业务超时释放问题Pub/Sub 重试订阅解锁频道事件驱动阻塞重试避免 CPU 空转多节点方案MultiLock全成功和 Redlock多数派解决主从一致性。生产环境必须注意锁粒度按业务 ID 细分、看门狗监控防止死循环续期、公平锁仅在严格顺序场景使用、Redlock 需确保时钟同步。最后记住Redisson 不仅是分布式锁框架更是完整的 Redis 分布式工具集RMap、RDelayedQueue、RAtomicLong 等选型时应充分利用其生态能力而非仅使用锁功能。觉得对您有帮助麻烦点点关注啦您的关注是我创作的最大动力~