Redis 分布式锁的可执行核心只有一条命令:SET key value NX PX ttl。NX 保证同一时刻只有一个客户端能写入成功,PX 保证持锁进程挂掉后锁会自己过期,value 存一个每次加锁都不同的随机串,是为了在释放时能分辨“这把锁是不是我加的”。续期、可重入、排队等待这些,都是在这条命令之上自己拼的逻辑,Redis 本身不提供。
先在单实例上确认三件事:加锁命令的返回值是 OK 还是 nil、过期时间是否真的生效、释放时是否用 Lua 比对 value 再删。三件事都成立,这套锁才算“能用”;业务执行时间可能超过 TTL 的,还要在业务侧补幂等或续期兜底。跨多个 Redis 实例要求强一致时,别把单实例锁当成分布式共识来用。
Redis的分布式锁到底指什么
它指的是一段“谁先写成功谁干活,干完主动还回去,干不完靠过期自动还”的约定,常用于防止同一个订单被两个进程重复处理、同一个定时任务在多台机器上同时跑。只写能核对的东西:命令行里 redis-cli SET lock:order:12345 "b7f3c1e2-9d4a" NX PX 30000 返回 OK 就是抢到,返回 nil 就是没抢到;MONITOR 能看到这条 SET 的实际入参;INFO keyspace 和 TTL lock:order:12345 能确认 key 存在且带过期时间。
# 加锁:OK = 抢到,nil = 已被别人占住
redis-cli SET lock:order:12345 "b7f3c1e2-9d4a" NX PX 30000
# 释放:先比对 value 再删,必须原子执行
redis-cli EVAL "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end" 1 lock:order:12345 "b7f3c1e2-9d4a"
成立的前提是:业务能接受极小概率下的双持(比如锁刚好过期、原持锁方还在跑),并且释放动作一定走比对删除。不满足这两条,锁只是看起来像锁。

Redis的分布式锁和哪几个说法容易混
搜到的方案看着都叫锁,成因和坑完全不同,先按下面的顺序判断你手上是哪一种:
- SET NX PX 单命令:一条命令同时拿到互斥和过期。判断条件:
MONITOR里只有一条 SET。 - SETNX 再加 EXPIRE 两步:老写法。判断条件:
MONITOR里是 SETNX 和 EXPIRE 两条,中间客户端崩了就是死锁。看到这种写法,先改成单条 SET。 - GET 再 DEL 释放:非原子。判断条件:释放路径上能抓到 GET 和 DEL 两条独立命令。两个客户端交错执行时会删掉别人的锁,必须换成上面的 EVAL。
- 看门狗续期:不是 Redis 的能力,是客户端库自己起线程定期延长 TTL。判断条件:
TTL反复被拉回接近初始值。续期线程挂了,锁照样会掉。 - Redlock 多实例投票:跟单实例 SET NX 不是一回事。判断条件:配置里出现多个互相独立的 Redis 地址。它换来的是对时钟和网络更敏感的复杂度,不是免费的强度提升。
- 数据库行锁 / 唯一索引:名字里没有“锁”但常被拿来替代。判断条件:能直接在事务里加
SELECT ... FOR UPDATE或建唯一索引。并发量不高的场景,这比引入 Redis 更省事。
最容易踩的坑在第 3 条:释放锁只写了 DEL lock:key。业务 A 的锁过期后 B 抢到,A 跑完一 DEL,删的是 B 的锁,第三个进程立刻又能进来。这类问题在日志里通常只表现为“同一单号处理了两次”,看不出是锁的问题,所以释放脚本必须先比对 value、再删除,且比对和删除放在同一个 Lua 里。

什么情况其实用不上Redis的分布式锁
四种情况建议先别上。一是临界区本身不幂等、又绝对不允许重复执行(比如扣减一次就出库),这类应该靠数据库唯一索引或状态机流转来兜,锁过期双持时数据库仍然拦得住。二是并发量很小、冲突本来就少,加锁的运维成本大于收益。三是老版本 Redis 上没有 SET 的 NX/PX 组合参数,得用 redis-cli INFO server 看 redis_version,再对照该版本的命令文档确认能不能用单命令写法;不能就考虑升级或换方案,不要退回到 SETNX+EXPIRE。四是需要跨机房强一致的场景,单实例锁在没有主从切换保证时会丢锁,得改用有共识语义的组件。
还有一层成本门槛:如果团队没人维护续期线程、也没有监控锁等待和超时的面板,那把 TTL 设得很长其实是在赌进程一定不会挂,反过来设得很短又会频繁双持。这两头都要有人盯,没人盯就先别把它当关键路径的唯一保障。

Redis的分布式锁:三个动作确认你真的搞懂了
下面三个动作在测试环境的 redis-cli 里就能做,不需要改业务代码。
- 动作一:对同一个 key 连发两次加锁命令。预期结果是第一条返回
OK、第二条返回nil。如果第二条也返回OK,说明 NX 没生效,可能是客户端把参数拼错了位置,或者走的根本不是 SET 而是普通赋值——这时候锁等于不存在。 - 动作二:加锁后不释放,等 TTL 过去再抢一次。预期结果是 TTL 到期后重新加锁返回
OK,TTL命令在加锁瞬间返回的秒数接近你设的值。如果TTL返回 -1,说明过期没设上,进程一挂就是永久锁;如果返回的数字远小于预期,检查 PX 的单位是不是被写成了秒当成毫秒用。 - 动作三:换一个 value 执行释放脚本。预期结果是返回
0,并且EXISTS lock:order:12345仍是 1,即别人的锁没被删掉。如果返回1、锁消失了,说明释放逻辑没有比对 value,上线后迟早会删到别人的锁。
三个动作都通过,只能说明加锁和释放这条链路是对的;业务执行时间是否会超过 TTL、续期由谁负责、锁超时后业务怎么收敛,仍然要在这个基础上单独确认。