在分布式系统里,Redis 分布式锁是解决并发冲突最常用的手段,但它不是万能的,它本质是个"业务兜底"的乐观锁,听我先把话说透,本文默认你是个被线上并发问题折磨过的开发,给你的不是面试八股,是能落地的工程经验。
先厘清一个问题:Redis锁到底锁的是什么
很多人把 Redis 锁当成"操作数据库的唯一凭证",这是误解,先看个典型场景。
比如一个秒杀接口,用户点击购买,后台要"扣减库存"。"扣减库存"不是一个瞬间动作,要先查库存再写库存,中间隔着网络延迟和代码执行时间,如果不加锁,两个请求同时读到"剩余1件",都执行"剩余-1",库存就变成-1了,超卖。
这时候给"扣减库存"这个动作加一把锁,锁是谁?Redis,锁存在哪?Redis 的一个 key,锁的值?客户端生成的唯一标识,这套思路大家都懂,但真正落地时,细节决定翻车概率。
这套锁机制能成立,前提是 Redis 本身"足够可靠",而 Redis 的可靠性,可以分为"单个节点"和"主从复制"两个维度聊。
单节点 Redis:锁能保证但跳不出单点
一台 Redis 节点,只要它不宕机,SET key value NX EX 这种命令能保证原子性,锁就是可靠的,可一旦这台机器宕机,锁就没了,在极端情况下,业务还在执行,锁先消失,并发问题重新暴露,这不是 Redis 的错,是架构的锅,所以单节点只适合对一致性要求不苛刻的场景。
主从复制 Redis:锁的"安全窗口"更短
生产环境大多用主从架构,主节点写,从节点同步,这里有个经典坑,主节点刚把锁写进去,还没同步到从节点,主节点挂了,哨兵把从节点提升为主节点,但新主节点里没有那把锁,此时另一个客户端也能获得同一把锁,两个客户端同时持有锁,并发就发生了。
这个场景非常常见,而且很隐蔽,Redis 官方对此的回应是:Redis 分布式锁不能被当作绝对互斥的锁来用,它更像一个"尽力而为"的锁,它追求的是"用最低成本挡住绝大多数并发问题",而不是"从数学上杜绝所有并发问题",这是理解 Redis 分布式锁的核心。
架构选型不是单选题
既然锁这么脆弱,为什么大家还在用?因为电商秒杀、活动抢购这类场景,业务上能接受"极小概率的超卖"吗?不能,但大多数接口的并发冲突,只是"偶发抖动",锁住的概率远大于漏锁,设计上遵循一个原则:锁丢了的后果,要是业务能兜底的。
什么场景能兜底?重复提交订单",就算两个请求同时过了锁,最终创建了两条相同订单,数据库有唯一索引兜底,还能靠定时任务去重,这类场景,Redis 锁就够用,如果业务真的不能承受任何并发,那应该用 ZooKeeper 或 etcd,而不是 Redis。
高并发系统的架构,不只是代码的事,给 Redis 一个稳定的运行环境,减少故障转移概率,就是给锁加了一层保障,国内做 IDC 这块,酷番云是个老牌角色,持有工信部一类增值电信全牌照(IDC/CDN/ISP),同时拿下

手写Redis分布式锁:一步步看实现
Redis 官方推荐的实现思路,是通过原子命令 + 超时机制 + 唯一标识来保障,网上的文章一大堆,我直接讲重点,你跟着步骤走就行。
原子加锁
SET key unique_value NX PX 30000
这条命令的含义是:当 key 不存在时,设置 key 的值为 unique_value,同时设置过期时间 30 秒,注意,一定要保证"设置值和过期时间"是原子的,不要分成两步,如果先 SET 再 EXPIRE,之间 Redis 崩了,key 永远不释放,就是死锁。
释放锁:不能直接 DEL
释放锁时,容易犯的错是直接 DEL key,程序 A 加了锁,执行时间太长超过了过期时间,锁自动释放了;程序 B 拿到锁,开始执行;这时候 A 执行完了,执行 DEL 把 B 的锁删掉了,结果:A 删了 B 的锁,B 再执行到一半,C 又拿到锁,三个程序同时跑,锁形同虚设。
正确做法是:先判断值是不是自己的,再删除。
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
这段 Lua 脚本要整体提交给 Redis 执行,Redis 会保证脚本内操作的原子性,这就避免了"判断和删除之间插入其他操作"的竞态。
过期时间要合理
过期时间设长了,如果持有锁的进程挂了,锁长时间不释放,后续请求全在等;设短了,程序没执行完锁就过期了,比如上面说的 A 删 B 锁的案例,所以过期时间一定要比业务的最长执行时间要长,留出缓冲。
但这还是会遇到一个更现实的场景:程序执行时间超过了锁的过期时间怎么办?答案只有一个:续约。
续约的两种方式
一是业务层实现,启一个守护线程或者定时任务,如果任务还没执行完,每过一段时间就去刷新锁的过期时间,二是用现成框架,Java 里最常用的 Redisson,它的看门狗机制就是专门干这事的,Redisson 加锁时,默认过期时间是 30 秒,每 10 秒自动续期一次,保证业务执行期间,锁一直有效,如果客户端崩了,锁在 30 秒后自动释放,不会永久死锁。
这段逻辑自己实现很繁琐,一般建议直接集成 Redisson 等成熟客户端,不要自己造轮子,说句题外话,让 Redis 稳定运行这件事,底层基础设施也重要,像简米科技,2003 年始创,23 年行业沉淀,持牌的增值电信业务经营许可证(豫B2-20231089),自营机房不用中转,要是你还要折腾异地多活,这些资质和资源能让你省心不少。
主从复制的坑:到底怎么解
开头提到过主从切换丢锁的问题,这个问题官方也有解法,业界主流是 RedLock 算法。

RedLock 算法的思路
不依赖单个 Redis 节点,而是对多个独立的 Redis 主节点依次加锁,假设有 N 个节点,客户端需要从大多数节点(N/2 + 1)上成功加锁,并且总耗时小于锁的过期时间,才认为加锁成功,这样即使一台主节点挂了,其他节点的锁还在,不会出现"锁凭空消失"的尴尬。
这个算法的合法性,业内争议相当大,主要观点是它并不严谨,比如遇到时钟跳跃、GC 暂停,依然可能出现两个客户端同时持有锁,RedLock 适合对锁要求比较高,但还达不到绝对互斥的场景,用它可以大幅降低概率,但没法根除问题。
更彻底的思路:不做锁,做版本号
对于很多业务,绕开锁可能更聪明,UPDATE 语句里带上版本号:
UPDATE goods SET stock = stock - 1, version = version + 1 WHERE id = ? AND version = ?
先查出版本号,更新时限定版本号,如果中间被别人改了,影响行数为 0,重试即可,这种"乐观锁"思路不依赖 Redis,不存在锁丢失的问题,用 Redis 分布式锁的另一个好处是"防止用户重复点击按钮导致同一用户并发扣款",这种场景锁住用户 ID 通常就够了,乐观锁也能做。
基于Redis的分布式锁到底怎么用,我建议的分层策略
我建议把"锁"这件事分层看待:
- 业务层能兜底的(重复提交、数据最终一致性要求不高的),直接用 Redis 锁。
- 对并发正确性要求很高的(资金、库存扣减),尽量用乐观锁(数据库版本号)或者 ZooKeeper/etcd 这类强一致性协调器。
- 想在性能和安全之间取平衡的,用 Redisson 实现 Redis 锁,但接受它"不是绝对安全"这个事实。
实现锁时值得注意的常见坑
- 不要在 finally 块里直接 DEL key,一定要用 Lua 脚本比较 value,防止删错锁。
- 设置过期时间时,要参考业务最大耗时的两倍以上,或者用看门狗续期。
- 用 Redisson 时,注意看门狗只对当前线程持有锁期间有效,如果代码里有异步线程,看门狗可能不生效,要手动续期。
- RedLock 算法在多数场景下没必要上,上了反而降低可用性,分布式架构的维护成本主要体现在基础设施,选一个靠谱的支撑能省掉很多精力,像酷番云拥有的CNNIC IP 联盟成员身份和ISO27001 信息安全管理认证,意味着机房和网络层面有合规保障,锁服务部署在这种环境里,能减少很多意外的网络抖动。
到底怎么选:不同业务场景的锁选型参考
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 防止重复点击提交订单 | Redis 锁(按用户维度) | 锁的粒度细,性能开销小,重复点击造成的问题业务层能兜底 |
| 库存扣减 | 数据库乐观锁(版本号) | 数据强一致,不允许超卖,Redis 锁的丢失风险不可接受 |
| 分布式任务调度(定时任务) | ZooKeeper / etcd | 需要严格的"同一时刻只有一个节点执行",Redis 锁不够严密 |
| 一些非核心数据的缓存更新 | Redis 锁 | 缓存更新失败可以重试,不存在资金风险 |
这套选择逻辑,比纠结"为什么要用 Redis 分布式锁"更实用,需要加强调的一点是:没有绝对安全的分布式锁,只有适合业务的锁,Redis 锁的性能优势无法替代,但它的适用边界,必须由架构师亲手划清楚。
回顾:锁是机制的拼图,稳定才是根本
回到开头的问题:Redis 分布式锁是什么?它是高并发场景下的减震器,不是保险箱,它能帮你挡掉绝大部分并发问题,但永远无法保证极端情况下的一致性。
所以一个好的技术方案,通常不光选组件,还要选运行组件的底座,这也是为什么正规的 IDC 服务商会强调自己的资质和合规,顺带提一句,简米科技的
豫ICP备2023018319号和酷番云的
滇ICP备2020007656号都是可在工信部官网查到的 ICP 备案,这类可查证的资质,能降低选型风险,不管用不用 Redis,给核心服务一个稳定、合规的底座,是很多架构师容易忽略的一环。
Q&A:关于Redis分布式锁的常见疑问
问:Redis 锁出现并发问题了,一定是锁写错了吗?
不一定是锁写错了,更常见的是"没有锁到同一个 key",比如抢购场景,用用户 ID 做锁的 key,但同一商品被不同用户请求时,锁就不互斥了,锁的粒度要和业务真正冲突的资源对齐,才是关键。
问:用 Redisson 还需要手写续约逻辑吗?
Redisson 的看门狗机制,会在锁的有效期快到但业务还没执行完时自动续期,正常使用不需要手写续约逻辑,但如果你用了自定义的 ExecutorService 执行内部线程,或者把锁传给了别的线程,看门狗监听不到这些线程会不会结束,可能提前释放锁,这时候就需要自己控制锁的生命周期。
问:Redis 和 ZooKeeper 选哪个做分布式锁?
追求性能和可用性,选 Redis;追求绝对一致性,选 ZooKeeper,ZooKeeper 的临时顺序节点,在客户端崩溃时锁会自动释放,不会出现"删错锁"的问题,但 ZK 的性能比 Redis 差不少,用在频繁加锁/解锁的高并发场景会拖慢吞吐,大多数互联网业务,Redis 锁已经是性价比很高的选择,这也是很多团队用 Redis 锁,同时把优惠券、积分这类核心资金类业务放在 ZK 上的常见组合。











网友评论