Redis分布式锁
Redis 分布式锁常用于保护跨进程临界区,但它不是万能一致性工具。正确的锁至少需要唯一 token、过期时间、原子释放、超时处理和业务幂等。
# 1. 学习定位
| 维度 | 内容 |
|---|---|
| 难度层级 | 分布式协调 |
| 核心目标 | 先会用,再理解机制,最后能做工程取舍和故障分析 |
| 学习方法 | 带着业务场景看命令、SQL、数据结构、日志和运行时指标 |
# 2. 核心地图
Redis分布式锁
├─ SET NX PX
├─ 唯一 token
├─ Lua 释放
├─ 锁续期
├─ 超时
├─ Redlock
├─ 幂等
└─ fencing token
# 3. 小章节深度讲解
下面把核心地图中的每个点拆开讲。每个小节都同时面向开发和运维:开发侧关注怎么设计、怎么写代码、怎么避免错误;运维侧关注怎么监控、怎么定位、怎么恢复。
# 3.1. SET NX PX
# 是什么
SET key token NX PX ttl 是 Redis 分布式锁的基础加锁语义:NX 保证不存在才写入,PX 设置毫秒级过期时间,整条 SET 命令原子执行。
# 开发人员怎么用
开发人员应避免 SETNX 后再单独 EXPIRE 的两步写法,因为两步之间进程崩溃会留下无过期锁。token 必须随机唯一,TTL 要覆盖业务最坏执行时间并留安全余量。
# 运维人员怎么看
运维人员要观察锁 key 的 QPS、失败率、等待时间和过期分布。锁 key 成为热点时,普通缓存慢命令也可能影响锁获取延迟。
# 常见风险
常见风险是 TTL 设置过短导致业务没执行完锁已过期,另一个客户端又获得锁,最终多个执行者同时操作同一资源。
# 3.2. 唯一 token
# 是什么
唯一 token 表示锁的持有者身份。它通常是随机 UUID 或带请求 ID 的唯一值,用来区分“我持有的锁”和“别人后来获得的锁”。
# 开发人员怎么用
开发人员释放锁前必须比较 token,只有值相同才允许删除。这个比较和删除要用 Lua 原子完成,不能先 GET 再 DEL 分两步做。
# 运维人员怎么看
运维人员排查锁误删时,要检查客户端是否复用了 token、释放脚本是否正确、锁是否被非业务命令删除。
# 常见风险
常见风险是释放锁不校验 token,A 客户端超时后锁过期,B 获得新锁,A 最后执行 DEL 把 B 的锁删掉。
# 3.3. Lua 释放
# 是什么
Lua 释放锁的核心是“比较 token + 删除 key”原子化。脚本通常先 GET 锁值,等于自己的 token 才 DEL,否则不做任何操作。
# 开发人员怎么用
开发人员要把释放脚本封装成统一组件,禁止业务代码手写 GET/DEL。脚本也要保持短小,不能在释放逻辑里扫描其他 key。
# 运维人员怎么看
运维人员要通过 slowlog 观察脚本耗时,并检查脚本缓存、EVALSHA 命中和 Redis Cluster 跨槽限制。
# 常见风险
常见风险是脚本里访问多个无 hash tag 的 key,在 Cluster 下失败;或者脚本过长导致主线程阻塞。
# 3.4. 锁续期
# 是什么
锁续期用于业务执行时间可能超过初始 TTL 的场景。续期必须只对仍由自己 token 持有的锁执行,否则会给别人的锁延长生命周期。
# 开发人员怎么用
开发人员要设置续期间隔、最大续期次数和任务超时上限。续期不是让任务无限跑,而是为可预期的长任务提供缓冲。
# 运维人员怎么看
运维人员要观察续期失败率、任务执行时间分布和锁过期事件。频繁续期说明业务临界区可能过长,需要拆分或异步化。
# 常见风险
常见风险是 watchdog 无限续期,业务线程卡死后锁长期不释放,最终比没有续期更危险。
# 3.5. 超时
# 是什么
超时包含获取锁等待超时、锁 TTL 超时和业务执行超时。三者必须分别设计,不能只依赖 Redis key 过期兜底。
# 开发人员怎么用
开发人员要明确拿不到锁时是失败、排队、降级还是稍后重试。业务执行超时后要停止后续写入,避免锁已失效但旧执行者继续提交结果。
# 运维人员怎么看
运维人员要监控锁等待时长、超时次数、Redis 延迟和业务线程池积压。锁超时增加可能是 Redis 慢,也可能是业务临界区变长。
# 常见风险
常见风险是客户端等待锁无限重试,热点资源竞争时把 Redis 和应用线程池一起打满。
# 3.6. Redlock
# 是什么
Redlock 是 Redis 作者提出的多实例加锁算法,目标是在多个相互独立的 Redis 节点上取得多数派锁,从而降低单实例故障导致锁语义失效的概率。它适合讨论分布式锁边界,但不是强一致事务协议。
# 开发人员怎么用
开发人员使用 Redlock 时,要理解它依赖时间窗口、节点独立性、锁超时和多数派成功。对资金、库存、订单状态这类强一致资源,仍然需要数据库条件更新、版本号或 fencing token 作为最终保护。
# 运维人员怎么看
运维人员要确认多个 Redis 节点不是同一故障域,监控各节点时钟漂移、延迟、可用性和网络分区。多数派节点如果共享同一物理机、同一机架或同一存储风险,Redlock 的容错假设会被削弱。
# 常见风险
常见风险是把 Redlock 当成“绝对安全锁”。在网络分区、客户端长 GC、业务超时、时钟异常等场景下,仍可能出现旧持有者继续执行的问题,因此必须配合幂等和资源侧栅栏校验。
# 3.7. 幂等
# 是什么
幂等表示同一个业务请求重复执行多次,最终结果仍然等同于执行一次。分布式锁只能减少并发,不能消除网络重试、超时重放和消息重复。
# 开发人员怎么用
开发人员要使用业务唯一键、请求流水号、状态机条件更新或去重表保证幂等。锁失败重试、提交超时重试和消息重投都必须走同一套幂等保护。
# 运维人员怎么看
运维人员排查重复扣款、重复发货、重复创建时,要检查幂等键是否生效,而不是只看锁有没有成功。
# 常见风险
常见风险是以为加锁后就不需要幂等。锁在网络分区、过期、主从切换或客户端超时时都可能失效,幂等才是最后防线。
# 3.8. fencing token
# 是什么
fencing token 是单调递增的栅栏令牌,用于让资源服务识别旧锁持有者。即使旧客户端恢复后继续写入,资源侧也能拒绝较小 token 的请求。
# 开发人员怎么用
开发人员在强一致场景中应把 token 传给真正的资源服务,例如数据库、文件系统或下游服务,并让资源侧比较版本后再接受写入。
# 运维人员怎么看
运维人员要关注 token 生成是否单调、是否持久、是否在故障切换后回退。token 回退会让栅栏机制失效。
# 常见风险
常见风险是只在 Redis 里拿到锁,却没有把 fencing token 落到资源侧校验。没有资源侧拒绝,旧持有者仍可能写坏数据。
# 3.9. 本篇学习实验
建议准备一个独立 Redis 实例,构造小 key、大 key、热点 key、过期 key 和慢命令,观察 INFO、SLOWLOG、LATENCY、内存变化和客户端超时。每个实验都要记录命令复杂度和返回数据量。
开发侧实验重点是理解数据结构和命令边界,运维侧实验重点是理解延迟、内存和复制如何变化。真正掌握本篇内容,应该能从延迟尖刺反推大 key、慢命令、持久化或网络问题。
# 4. 核心机制
- 加锁通常使用
SET key value NX PX ttl,确保不存在时写入并设置过期时间。 - 释放锁必须校验 value 是否为自己的 token,再删除,避免误删他人锁。
- 锁过期只是故障兜底,不代表业务执行一定完成。
- 在强一致资源保护中,仅有 Redis 锁可能不足,需要数据库约束、版本号或 fencing token。
# 5. 工程实践
- 锁 value 使用随机唯一 token,释放用 Lua 原子校验删除。
- 锁 TTL 按最坏执行时间设置,并考虑续期或任务拆分。
- 业务操作必须幂等,锁失败要明确返回、排队或降级。
- 金融、库存等强一致场景优先使用数据库条件更新和唯一约束保护最终正确性。
# 6. 常见坑
- 用 SETNX 后再 EXPIRE,两步之间崩溃导致死锁。
- 释放锁不校验 token,误删其他线程刚获得的锁。
- 业务执行超过 TTL,多个客户端同时进入临界区。
- 把 Redis 锁当分布式事务,忽略网络分区和时钟问题。
# 7. 专家视角
- 分布式锁只能降低并发冲突概率,不能替代资源侧的正确性校验。
- 专家会用 fencing token 或版本号让资源服务拒绝旧持有者写入。
- 锁设计要回答四个问题:锁丢了怎么办、锁超时怎么办、锁重入怎么办、业务重复怎么办。
# 8. Tips 快问快答
Q:Redis 锁最基本写法是什么?
A:SET lockKey token NX PX ttl,释放时 Lua 校验 token 后删除。
Q:锁有过期时间就安全吗?
A:不完全。业务超时后仍可能并发执行,必须有幂等和资源侧校验。
Q:强一致场景只靠 Redis 锁够吗?
A:通常不够,还需要数据库约束、版本号或 fencing token。
# 9. 阶段小结
Redis分布式锁 的学习重点不是记住零散概念,而是把它放回真实系统:数据如何进入、如何存储、如何被查询、如何在并发下保持正确、如何在故障后恢复。掌握这些连接关系,才能从“会用”走向“能设计、能优化、能排障”。