Redis分布式锁实战:原理、坑点与面试全解析
咱们今天不绕弯子直接聊一个让很多后端开发既熟悉又头疼的东西——Redis分布式锁。我记得有一次深夜上线代码里用了synchronized做库存扣减结果流量一上来库存直接扣成负数当时整个人都懵了。后来换成Redis分布式锁才把问题压下去。但别高兴太早分布式锁也不是一把锁走天下里面的坑多到你怀疑人生锁过期了怎么办主从切换锁丢了怎么办释放锁时删错锁怎么办这些问题你在网上搜“Redis分布式锁”能搜出一大堆但大部分文章要么太理论要么只给半段代码。我干脆把自己的实战经验、排查过程和面试里被反复追问的点全部梳理出来一次性讲透。这篇内容适合两类人一类是准备后端面试、被“分布式锁面试题”折磨过的开发者另一类就是真实项目里要落地分布式锁、但不想把线上搞挂的工程师。我会先从一道经典面试题切入讲清楚分布式锁到底在解决什么问题然后再一步步拆解Redis锁的写法、Redisson的原理、主从架构下的隐患最后附上我自己的避坑清单和面试作答逻辑。整个过程没有废话能直接抄作业。1. 从“库存扣成负数”说起为什么单机锁管不住分布式场景我先讲一个真实发生过的案例。某个秒杀活动上线后扣库存的接口在单机自测时一切正常但流量一大数据库里的库存就变成负数。定位了半天问题出在代码里用了synchronized锁。synchronized是JVM级别的锁只能锁住同一个进程内的多个线程。但线上服务通常不止一个实例请求经过负载均衡分发到多台机器上A机器锁住的是A机器的对象头B机器根本感知不到库存照样被多个实例同时扣改。这就是典型的单机锁边界。1.1 单机锁的边界到底在哪要理解分布式锁先得认清单机锁的边界。这里的“单机”不是指一台物理服务器而是指同一个操作系统进程。synchronized、ReentrantLock这类锁本质上是进程内部的互斥机制它们依赖JVM内存里的Monitor或AQS队列。进程一多各自锁各自的内存空间就谈不上互斥了。数据库本身的行锁、乐观锁版本号虽然能跨进程但性能瓶颈明显而且表结构、事务隔离级别会影响锁的可靠性。真正需要分布式锁的场景通常是多个服务实例同时操作同一个共享资源比如库存扣减、订单状态流转、防止用户重复提交、定时任务分布式调度。打个比方单机锁就像你家里的房门锁自己家只有这一扇门时锁上就安全了。分布式系统是一个有几十个门的小区你只锁自己家的门栋楼的公共入口不锁外人照样能进来。分布式锁就是要给整个“小区”上一道公共门禁。1.2 分布式锁必须满足的三个性质真正在生产环境评估一个分布式锁方案不能只看能否跑通要盯住三个性质互斥性在任意时刻只有一个客户端能够持有锁。这是锁的灵魂。安全性不会产生死锁。即使持有锁的进程突然崩溃、网络中断或机器宕机锁也能在有限时间内自动释放让其他客户端有机会拿到锁。可用性加锁、解锁的服务本身不能成为单点不能一崩全崩同时要尽量避免一个客户端在业务执行期间锁被别的客户端抢走。这三个性质看起来简单但在Redis主从架构、网络抖动、GC停顿等现实因素面前每个都可能是坑。1.3 用锁的本质问题锁的是“动作”而不是“资源”还有个非常容易搞混的点分布式锁到底锁的是数据还是操作很多人以为锁住某个Redis key就万事大吉但你锁的其实是“执行业务动作的权利”。同样是库存字段如果是“扣减库存”和“手动修改库存”两个动作它们在逻辑上必须使用同一把锁否则互斥关系就被绕开了。我在项目里见过一个事故订单服务和售后服务的开发者各自维护了一套库存扣减逻辑一个用stock:reduce:001做锁key一个用goods:001:stock做锁key两套key互不关联并发一高照样超卖。这个教训说明一个问题锁的key设计需要团队统一约定锁语义必须绑定到具体操作对象和操作类型而不是随便拼接一个字符串。2. Redis锁的三种写法和一次完整的Bug式翻车先声明一件事网络上搜索“Redis分布式锁”能翻出无数种写法有的写SETNX有的用SET key value NX EX有的释放锁时先GET再DEL。这些写法之间有明显的正确性差异。下面这节我把演进过程讲清楚。2.1 最原始的SETNX写法死锁重灾区最早实现Redis锁的博客基本都类似这样// 错误写法示范绝不要这样写 Boolean success redisTemplate.opsForValue() .setIfAbsent(lock:order:1001, 1); if (success) { try { // 执行订单操作 } finally { redisTemplate.delete(lock:order:1001); } }这个写法的问题很致命如果拿到锁之后、执行delete之前服务突然宕机或抛出OOMfinally代码块根本跑不到锁的key就一直留在Redis里永远不消失。其他线程再来请求时setIfAbsent永远返回false整个业务被卡死这就是死锁。2.2 加过期时间的演进解决了死锁但引入了新问题为了解决死锁问题大家开始给锁加过期时间// 错误写法示范setIfAbsent 和 expire 是两条命令不满足原子性 Boolean success redisTemplate.opsForValue().setIfAbsent(lock:order:1001, 1, 30, TimeUnit.SECONDS); if (success) { try { // 执行订单操作 } finally { redisTemplate.delete(lock:order:1001); } }这比死锁好一点但又引入一个经典问题如果进程在设置完锁之后、设置过期时间之前发生GC pause、网络分区或宕机那么过期时间没有设置上去key还是会永远存在。因为这两条命令之间不是原子操作。后来Redis提供了SET key value NX EX seconds这一个原子命令把加锁和设置过期时间合并为一步命令层面的原子性问题才算被解决了。到了这一步代码变成// 正确的基础版本 Boolean success redisTemplate.opsForValue() .setIfAbsent(lock:order:1001, requestId, 30, TimeUnit.SECONDS);注意这里引入了requestId作为value这个value有讲究接下来重点讲。2.3 释放锁的陷阱为什么不能直接delete上面代码的finally块里还是简单的delete。这个写法存在一个“误删锁”的经典bug线程A拿到锁业务处理比较慢超过了30秒的过期时间锁被Redis自动释放。线程B此时拿到同一把锁开始执行业务。线程A终于处理完了执行finally中的delete直接把线程B持有的锁删掉了。线程C这时又拿到锁于是线程B和线程C同时执行互斥失效。解决误删锁的思路并不复杂删除锁之前先校验value是不是自己加锁时的那个唯一标识确认是自己的锁才删除。校验和删除要保证原子性所以要用Lua脚本if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end对应的Java代码大致是String luaScript if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end; DefaultRedisScriptLong redisScript new DefaultRedisScript(luaScript, Long.class); Long result redisTemplate.execute(redisScript, Collections.singletonList(lock:order:1001), requestId);这个Lua脚本执行时Redis会保证整个脚本的原子性因此“检查value”和“删除key”不会被打断。不少开源项目里锁误删的事故就是因为没做这一层校验直接用delete。这里有个细节很多人忽略——value用什么生成我在项目里用的是UUID.randomUUID().toString()再加上线程ID或业务请求ID。有些团队图省事用Thread.currentThread().getId()加时间戳这种value很容易重复尤其是时间戳精度不够时两个不同请求可能拿到一模一样的value校验就形同虚设。安全起见用全局唯一的UUID不要自己拼。2.4 手写一个相对完整的锁工具类思路理解了上面的坑之后即使不使用Redisson也能手写一个相对完整的锁工具类。核心思路如下加锁使用SET key value NX EX原子命令value用唯一标识解锁使用Lua脚本先比较value再删除key流程加锁失败时可以快速返回失败也可以自旋重试但生产上我更倾向快速失败后直接提示“操作太频繁”避免线程堆积业务执行在try-finally里释放锁确保异常路径也能走解锁逻辑加锁超时时间不建议设太短要根据业务耗时评估宁可略长也不要频繁出现锁提前过期。写到这里分布式锁的基础正确姿势基本就有了。但我要说一句得罪人的话真实项目中如果不做二次封装我强烈建议直接用Redisson而不是自己写这套逻辑。原因后面单独讲。3. 锁过期了怎么办WatchDog续期机制与手动续期的取舍上一节提到的“线程A业务没执行完锁就过期了”的场景是最常见的线上事故诱因。我自己踩过一次后对锁过期时间变得异常敏感。这里把续期思路和Redisson的实现机制一起讲。3.1 固定过期时间为什么不够用假设你给每个锁设置30秒过期如果业务在正常情况下能在几十毫秒内执行完那么30秒看起来绰绰有余。但线上环境不是测试环境可能会出现以下情况业务访问数据库时遇到慢SQL执行时间超过30秒服务调用下游API下游迟迟不返回线程一直阻塞Full GC触发整个应用停顿数秒甚至数十秒网络抖动写入Redis成功后业务代码长时间无法继续执行。一旦业务还没执行完锁就过期了其他线程就会趁虚而入共享资源依然被并发修改。单纯把过期时间调大并不能解决本质问题反而会让死锁恢复时间变长。更合理的思路是动态续期锁快到期时自动延长时间业务执行完就立刻释放锁不需要继续续期。3.2 Redisson的WatchDog是做什么的Redisson里的lock()方法默认自带一个看门狗线程。它做两件事当线程持有锁时看门狗会定期检查剩余过期时间。如果业务还在运行且锁即将过期它就自动把锁的过期时间向后延长。默认情况下Redisson会把锁的过期时间设置为30秒然后每隔10秒左右续期一次。这个设计把“业务执行时长”这个不可控变量转变成一个持续的心跳机制只要客户端活着锁就一直有效一旦持有锁的客户端崩溃看门狗线程随进程死亡而停止锁在30秒后自然过期释放不会死锁。Redisson底层使用Netty的定时任务实现定时续期所以接入Redisson的项目本身都会引入Netty依赖这点在排查依赖冲突时要留意。3.3 WatchDog不是银弹务必注意这些使用边界WatchDog很强大但如果你不传leaseTime参数时它才生效。一旦你在调用lock时手动指定了leaseTimeRedisson就不会启动续期逻辑到点就释放。这个“听上去有点反直觉”的行为坑过不少人。我在代码评审里经常看见类似的代码// 这是正确写法不传leaseTime时才会启用看门狗续期 RLock lock redissonClient.getLock(lock:order:1001); lock.lock(); try { // 业务逻辑 } finally { lock.unlock(); }// 如果出现类似 lock(10, TimeUnit.SECONDS)则代表放弃了看门狗业务在10秒内没执行完就会提前释放 RLock lock redissonClient.getLock(lock:order:1001); lock.lock(10, TimeUnit.SECONDS);另外即使有看门狗续期依然存在一个问题如果Redis所在的节点在续期瞬间发生主从切换而锁还没来得及同步到新的主节点上那锁依然会丢失。这个问题靠看门狗解决不了需要其他手段也就是下文要聊的RedLock和它的争议。4. 主从切换和锁丢失Redis锁的致命伤及RedLock的争议搜索“Redis分布式锁”时主从切换下锁丢失的话题几乎必被提及。网上能看到无数文章讨论RedLock算法但很多人看了还是一头雾水。这里我用尽量简洁的方式来拆解。4.1 锁丢失是怎么发生的Redis最常用的高可用架构是主从架构加哨兵或者使用Cluster模式。主库负责写入从库负责同步备份。但主从同步通常是异步的客户端A向主节点Master写入锁keyMaster返回“加锁成功”Master还没把这个写操作同步到Slave节点Master突然宕机哨兵检测到Master不可用把Slave提升为新的Master新Master上没有A写入的锁key此时客户端B来加锁加锁成功A和B同时认为自己持有锁互斥性被打破。任何基于单个Redis节点的分布式锁在标准主从异步复制架构下都无法彻底避免这类问题。这是CAP理论在数据一致性上的体现不是某个库做得不好而是架构本身决定的上限。4.2 RedLock的核心思路和工作过程RedLock是Redis作者antirez提出的多节点锁算法用来解决单点Redis故障导致锁丢失的问题。它不是在一台Redis上加锁而是同时向多个相互独立的Redis节点尝试加锁。算法过程大致如下假设有N个独立的Redis节点一般建议N取奇数比如5个客户端依次使用相同的锁key和随机value向所有N个节点发送加锁命令每个节点的加锁超时时间要设置得很短比如几十毫秒避免某个节点故障导致等待过久当客户端在超过半数的节点上成功加锁且总耗时小于锁的有效时间时才认为加锁成功如果加锁失败则向所有节点发送解锁脚本把已经加上的锁释放掉。RedLock的目标是即使某几个Redis节点发生故障只要大部分节点还存活锁依然能够保持互斥不会出现主从切换时的那种窗口期。4.3 为什么RedLock引发了一场“神仙打架”RedLock并不是没有争议。分布式系统专家Martin Kleppmann专门写过一篇文章批判RedLockRedis作者antirez也发文回应这算是分布式领域非常有名的技术辩论。争辩的核心集中在几个问题上RedLock依赖于各节点上的时钟基本同步。如果某台Redis服务器的时钟发生跳跃锁的有效期就可能失真。但时钟问题在现实场景中确实存在比如NTP同步时的时钟回拨。业务线程的停顿时间可能超过锁的过期时间。比如Java应用GC停顿即使持锁线程还活着也无法及时续期在停顿期间锁过期了其他客户端依然能拿到锁。RedLock无法从原理上消除这一点。多节点加锁仍然存在“客户端加锁成功但通知失败”的模糊窗口期这时候客户端不知道该不该继续业务。在实际工作中我的建议是分场景看待。如果业务中出现了“锁丢失会造成资金损失或严重数据不一致”且无法通过其他手段兜底单纯依赖Redis锁无论是否RedLock都不是最优解需要引入更强一致性的协调服务或采用事务、版本号、对账补偿等手段。如果业务能容忍极低概率的偶发问题那么标准Redis锁加WatchDog通常已经够用。4.4 业务一致性兜底值得关注即使锁实现得再完美也不要让分布式锁成为数据安全的唯一防线。我参与过的几个高并发项目最终都要额外加一层兜底比如数据库层面使用乐观锁版本号更新时带上版本条件判断根据业务特征设计幂等表或去重表用数据库唯一索引拦截重复请求定时任务扫描异常数据并对账补偿。这三者结合后即使Redis锁本身出现极低概率的失效兜底机制也能把损失控制在可接受范围内。这里不是贬低Redis锁而是强调锁只是一道防线数据正确性最好由数据库最终把关。5. Redisson集成与高级特性从一把简单锁到可重入/读写锁说到工程落地我强烈建议直接使用Redisson而不是手写锁逻辑。为什么因为一个成熟的分布式锁库不仅解决了加锁解锁的基本问题还帮你规避了大量边角场景。5.1 为什么我不用手写锁很多团队最初嫌Redisson重自己封装了工具类。但时间一长会发现手写方案往往会漏掉这些细节网络抖动导致加锁时Redis连接超时是直接抛异常还是重试Redis命令返回null时到底算不算加锁成功看门狗续期的线程什么时候停、怎么停锁的粒度要不要细到某个具体资源不同业务如何统一规范是否支持可重入同一线程嵌套调用同一把锁会不会死锁这些问题在Redisson里基本都有成熟解法。Redisson的实现已经包含了自动续期、可重入、可重试、超时控制、公平锁和读写锁等常见需求经受过大量生产环境考验。5.2 一分钟跑起Redisson以Spring Boot项目为例引入Redisson大体分两步。第一步加依赖dependency groupIdorg.redisson/groupId artifactIdredisson-spring-boot-starter/artifactId version3.27.2/version /dependency第二步在配置类里声明RedissonClientConfiguration public class RedissonConfig { Bean(destroyMethod shutdown) public RedissonClient redissonClient() { Config config new Config(); config.useSingleServer().setAddress(redis://127.0.0.1:6379); return Redisson.create(config); } }然后业务代码里就能这样用Autowired private RedissonClient redissonClient; public void deductStock(Long goodsId) { RLock lock redissonClient.getLock(lock:stock: goodsId); boolean locked false; try { locked lock.tryLock(1, TimeUnit.SECONDS); if (!locked) { throw new BizException(系统繁忙请稍后重试); } // 执行扣减库存 } finally { if (locked) { lock.unlock(); } } }建议优先使用tryLock而不是无参lock()这样至少能用超时时间约束加锁等待行为避免长时间阻塞。注意tryLock没有传leaseTime时同样会启用看门狗续期。5.3 可重入和读写锁的实际价值Redisson的锁默认支持可重入。也就是说同一个线程已经持有锁之后再次调用加锁不会被阻塞内部会维护一个计数器。这个特性在一些递归调用、方法嵌套的场景里非常实用避免了手写锁因为重入问题产生死锁。还有一类场景推荐使用红锁的读写锁能力比如缓存刷新和读请求同时访问某个共享资源读锁是共享锁多个读操作之间互不阻塞写锁是排他锁写操作执行时不允许其他读或写操作同时进行。RReadWriteLock的用法类似RReadWriteLock rwLock redissonClient.getReadWriteLock(lock:cache:goods); RLock readLock rwLock.readLock(); RLock writeLock rwLock.writeLock();在分布式环境下如果直接用一把普通的排他锁保护读写混合操作并发读的性能会被严重牺牲。区分读写锁是更贴近业务的优化手段。5.4 与本地缓存、Redisson结合时的注意点用Redisson锁保护缓存刷新时需要额外留意“本地缓存副本”的问题。比如每个服务实例都有自己的本地缓存同一把Redis锁只控制“由谁去加载后端数据”但实例之间本地缓存可能不一致。此时锁只是预防缓存击穿的工具之一真正要防止的是大量请求同时打到数据库所以还需要考虑用空值缓存、布隆过滤器等方案一起配合。Redis锁不是万能药这一点在接口设计中要有意识。6. 完整落地Redis锁的实操步骤环境、工具链和配置避坑聊完算法和库选型很多读者已经在自己的电脑前摩拳擦掌了。想真正上手跑Demo环境准备是绕不开的一步。结合经常被搜索的“redis下载”“windows安装redis”“docker安装redis主从”等话题我直接把环境层面的方案和踩坑经历写出来。6.1 从下载到启动Windows下如何快速装一个可用Redis如果你本机开发环境是Windows最省心的方式是到Redis官网或官方Windows移植维护仓库下载压缩包解压后运行redis-server.exe即可。不需要复杂的编译安装。一个好消息是Windows版本同样可以直接演示加锁、解除锁和Lua脚本效果。需要注意的是Windows版本和Linux版本的版本号可能不完全同步但这并不影响学习分布式锁的核心概念和生产部署思路。在实际使用中我更推荐使用Docker来运行Redis这样环境和宿主机完全隔离换电脑或换系统都不受影响。一行命令就能拉起来docker run -d --name redis-local -p 6379:6379 redis:7.0比如验证Redis锁的关键行为时可以直接在容器日志里看到连接记录和命令执行情况比在本机裸跑更容易排查。6.2 可视化客户端怎么选Redis Desktop Manager与Another Redis Desktop Manager如果你需要看Redis里key的变化纯命令行有时不够直观。现在主流的可视化客户端有两款可以说都绕不开“Redis Desktop Manager”这个关键词官方新版Redis Desktop Manager已经更名为Redis Insight免费功能全支持查看key、分析内存、订阅pub/sub消息适合开发调试。Another Redis Desktop Manager简称ARDM优点是轻量、免安装、启动快深受不少开发者喜爱。我自己的习惯是开发调试用ARDM需要分析大key或慢日志时用Redis Insight。写分布式锁Demo时关键要用客户端去观察锁key的生命周期比如加锁后TTL key应该是过期时间对应的秒数WatchDog续期时会发现TTL被重置这样就能直观验证Redisson的续期机制。6.3 Docker搭建一套主从环境验证主从切换问题理解了“主从复制导致锁丢失”之后再亲手搭一套主从环境对深刻记忆会有很大帮助。官方推荐的是主从加哨兵生产上哨兵负责自动故障转移本地实验可以先不做哨兵只做主从复制环境。Docker Compose配置如下version: 3 services: redis-master: image: redis:7.0 container_name: redis-master ports: - 6380:6379 command: redis-server --appendonly yes redis-slave: image: redis:7.0 container_name: redis-slave ports: - 6381:6379 command: redis-server --slaveof redis-master 6379 --appendonly yes depends_on: - redis-master启动后在主节点上执行SET lock:test 1再去从节点上查一下通常会看到数据已经同步。接着可以停掉主节点手动将从节点提升为主节点后进行实验观察之前加的锁在切换后是否还存在。这种复现实验比单纯背结论更有效。需要注意当前版本的镜像中--slaveof参数有可能会被标记为需要迁移到--replicaof。如果启动时报参数错误可以换成--replicaof语义完全一致。7. 面试连环追问Redis分布式锁高频题目与回答逻辑把前面这些内容消化掉再回头应对“分布式锁面试题”就不会心虚了。网上相关热词一直在刷新面试官抛出的问题其实万变不离其宗。下面我按自己真实面试中遇到的追问顺序把高频问题和回答思路套在一起讲。7.1 第一问为什么需要分布式锁和synchronized有什么区别这题属于基础题面试官想考察你是否有真实的跨进程并发意识。回答时可以先点出synchronized是JVM层面的线程锁只对同一个JVM进程里的线程生效然后引出在微服务或多实例部署架构下多个进程操作同一资源时就需要分布式的互斥机制。更进阶的回答要能落到场景库存扣减、防止重复下单、跨节点定时任务调度等。能结合自己项目中的真实场景自然最好比如远程服务的幂等控制。7.2 第二问Redis分布式锁的set命令怎么就保证原子性了面试官会问“Redis实现分布式锁为什么需要用某个命令”考的是你对Redis命令的底层理解。参考答案是SET key value NX EX中NX保证只有key不存在时才设置这就是互斥性EX用来设置过期时间避免死锁单个命令在Redis上是原子执行所以不会出现先加锁后设置过期时间之间插入其他操作的问题。如果被追问“看门狗是什么”可以把Redisson的续期机制讲清楚然后手动指定leaseTime就不会启动看门狗这个细节很加印象分。7.3 第三问释放锁时为什么要用Lua脚本不用行不行这一问的坑非常经典。如果只说“为了原子性”显得太浅。我一般会展开讲误删锁的场景比如线程A锁过期被线程B拿到A执行完直接删除B的锁由此可能出现并发安全。有人会提“那在业务代码里先GET比较再DEL不就行了”这时候正好顺势解释GET和DEL是两条命令之间不是原子操作仍有可能被其他线程插队。Lua脚本让Redis服务端将多个命令作为一个原子单元执行加锁和解锁的检查都必须在同一个原子操作内完成。7.4 第四问Redis主从切换导致锁失效怎么办你了解RedLock吗不要把这道题简单理解成“RedLock就是标准答案”。更聪明的答法是先陈述问题再给出不同方案最后点明各自适用边界。技术领域很多问题没有银弹面试官更在意的是你有没有理解方案的边界和代价。可以这样组织回答主从复制是异步的主节点宕机后从节点没拿到锁数据因此可能让两个客户端同时持有锁。RedLock用多个独立节点加锁并要求多数成功来降低风险但它依赖系统时钟且不能完全消除问题。对一致性要求极高的场景建议使用ZooKeeper或etcd这类线性一致性的协调服务或增加版本号对账机制兜底对一致性要求没那么严的高并发场景用带续期机制的普通Redis锁配合监控告警基本够用。7.5 第五问线程A锁过期了业务还没执行完怎么办这题考你对失效语义的理解。最基础的方案是设置足够长的锁过期时间但这并不优雅进阶方案是看门狗自动续期。更进一步的思考是即使加了续期如果业务线程死循环或GC停顿续期也无法生效所以核心业务还需要考虑幂等设计不把正确性完全寄托给锁。7.6 给面试作答再加一点“项目感”想在这轮面试中拉开差距回答时一定要有“我实际踩过坑”的感觉而不是陷入背书模式。比如讲清你们团队的锁key命名规范为什么不允许各服务私自拼key讲清你们如何监控锁的等待时间、加锁失败率和锁过期释放次数讲清你们为什么最终选择tryLock(1, TimeUnit.SECONDS)而不是裸用lock()讲清Redisson版本升级时FairLock和MultiLock在集群模式下对你的影响。这些内容是背面试题背不出来的只有真正上线跑过、经历过报警才会知道它们的分量。8. 我踩过的Redis分布式锁相关坑和最后的经验赠言写到最后再分享几个我在真实项目中反复碰到、搜索关键词不一定能搜到的坑当作给读到这里的读者一份额外清单。第一个坑锁的粒度太粗。有人图省事一个订单接口只用一个全局锁把所有商品的扣减串行化性能直接降低一个量级。锁的粒度应该尽量收缩到唯一业务对象上比如锁key用lock:stock:{goodsId}而不是全局一把锁。第二个坑把Redis实例当作无限可靠。锁服务本身就是基础设施一定要有监控。我在项目里用Prometheus暴露了Redisson的锁等待耗时、获取失败率并用告警规则盯住获取锁失败次数突增的情况这一点小投入后期能省很多事。第三个坑团队之间锁key设计没有契约化。订单服务和库存服务曾经因为锁key后缀不统一导致明明想互斥却各锁各的数据最终还是乱了。后来我把锁key写进接口文档并做成公共常量类从源头避免拼写不一致。第四个坑Redis连接池配置没跟上。分布式锁本质上强依赖Redis的读写性能一旦连接池最大连接数太小、超时时间太短高并发时大概率出现获取连接失败。Redis锁的调用链路里如果有分布式追踪系统建议把加锁时间、等待时间都打点采集否则出问题时很难定位。如果非得给一句总结性的经验那我的理解就是Redis分布式锁不是什么高深算法但它的价值恰恰藏在细节里——原子命令、过期时间、唯一标识、Lua脚本、看门狗、主从边界、监控告警、业务兜底。把这些细节都照顾到你的锁才能在线上跑得稳。另外一个值得思考的方向是随着云原生和微服务的发展分布式锁的底层存储也在多元化ZooKeeper、etcd都有各自的优势。面试官如果问起你可以表达这样的观点选锁方案不是选最“高级”的而是选最匹配业务一致性等级和运维能力的。Redis锁适合追求低延迟、能容忍极端场景下有极小概率失效的业务而需要强一致性的业务可以考虑ZooKeeper或etcd并搭配数据库乐观锁作为最终防线。提前想清楚这一点无论做方案选型还是面试作答都会从容很多。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →