# Redis的分布式锁怎么实现?
# 简要回答
- Redis 分布式锁本质上就是利用 Redis 的原子命令,让多个服务实例去竞争同一把“全局锁”,最常见的加锁方式是
SET lockKey requestId NX PX 30000。 - Redis 分布式锁解决四个问题,分别是加锁原子性、锁自动过期、防误删以及业务超时后的续期。在释放锁时最好不要直接
DEL,而要先比对 value 再删除,通常用 Lua 脚本保证原子性。 - Redis 分布式锁适合做高性能协调,尤其是在主从切换、网络抖动、业务执行过长等场景下。
# 详细回答
- Redis 分布式锁
- 分布式锁被应用在分布式系统里,同一时刻只允许一个客户端执行某段临界业务,比如扣减库存、生成唯一任务、定时任务抢占执行权等。和 JVM 内部的
synchronized、ReentrantLock不同,Redis 分布式锁是跨进程、跨机器生效的,所以常用在多服务实例部署的场景。
- 分布式锁被应用在分布式系统里,同一时刻只允许一个客户端执行某段临界业务,比如扣减库存、生成唯一任务、定时任务抢占执行权等。和 JVM 内部的
- Redis 分布式锁的使用
- 最常见的方式是:
SET lock_key requestId NX PX 30000 - 这里的几个关键点分别是:
NX:只有 key 不存在时才设置成功,可以保证只有一个客户端拿到锁。PX/EX:给锁设置过期时间,避免客户端异常退出后锁永远不释放。requestId:每个客户端生成唯一值,表示“这把锁是谁加的”,比如 UUID、请求 ID、线程 ID 组合值。这样在释放锁时才能做归属校验,避免误删别人的锁。
SET NX PX把“加锁”和“设置过期时间”合成了一个原子操作。
- 最常见的方式是:
- 释放锁操作
- 假设线程 A 拿到锁,业务执行时间过长,锁先过期了;这时线程 B 成功拿到同名锁。如果线程 A 此时执行完,直接
DEL lockKey,删掉的就可能是线程 B 的锁,导致两个线程同时进入临界区。 - 因此,释放锁时要做“先比对 value,再删除”的原子操作,通常用 Lua 脚本:先
GET lockKey,然后判断值是否等于自己的requestId,如果相等才进行DEL。
- 假设线程 A 拿到锁,业务执行时间过长,锁先过期了;这时线程 B 成功拿到同名锁。如果线程 A 此时执行完,直接
- 处理业务超时
- 如果锁超时时间设置太短,业务还没执行完锁就过期了,别的线程就可能重新拿到锁。为了避免这种情况,常见做法有两类:如果业务可预估,就直接设置一个足够覆盖业务执行时间的 TTL。业务不可预估时可以引入自动续期机制,比如 Redisson 的 watchdog,在业务还没结束时定期延长锁时间。
- 常见错误情况
SETNX** 和EXPIRE分两步执行**:如果SETNX成功后服务突然挂掉,还没来得及执行EXPIRE,锁就会变成死锁。- 锁时间设置不合理:TTL 太短会提前过期,TTL 太长又会放大故障恢复时间。
- 获取锁失败后疯狂自旋:大量线程高频重试会把 Redis 打出额外压力,通常要配合退避重试或快速失败。
- 实际开发中,需要根据需求选择合适的实现方式:如果只需要简单互斥控制:可以用单 Redis + 唯一 value + Lua 释放;需要可重入、自动续期、看门狗,则优先使用 Redisson 这样的成熟实现。如果强一致要求极高:不要只盯着 Redis 命令本身,要从业务容错和一致性要求反推锁方案。
# 知识图解
- Redis 分布式锁的关键环节
| 环节 | 核心做法 | 目的 |
|---|---|---|
| 加锁 | SET key value NX PX ttl | 保证“只在不存在时设置”且自动过期 |
| 锁标识 | 每次加锁都生成唯一 requestId | 区分锁归属,避免误删 |
| 释放锁 | Lua 比对 value 后再 DEL | 保证校验和删除的原子性 |
| 续期 | watchdog / 定时延长 TTL | 避免业务未完成锁先过期 |
- Redis分布式锁示意图

# 代码示例
SET lock_key request-uuid-001 NX PX 30000
# 安全释放锁:值匹配才删除
EVAL "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" 1 lock_key request-uuid-001
1
2
3
4
2
3
4
# 知识扩展
- 面试官可能追问:
- Q1:为什么不推荐
SETNX成功后再单独EXPIRE?- 因为这两个操作不是原子的。如果
SETNX成功后进程挂了,EXPIRE没执行,锁就可能永远不释放。
- 因为这两个操作不是原子的。如果
- Q2:为什么释放锁一定要校验 value?
- 因为锁可能已经过期并被别人重新获取。如果不校验归属,当前线程就可能误删别人的锁。
- Q3:Redis 分布式锁能保证 100% 绝对安全吗?
- 不能。尤其是主从异步复制场景下,主节点刚加的锁来不及同步就故障切换,理论上可能出现锁丢失和重复加锁。
- Q4:Redisson 的 watchdog 解决了什么问题?
- 它解决的是“业务执行时间不可预估,锁可能提前过期”的问题。只要持锁线程还活着,watchdog 就会继续帮你续期。
← 缓存雪崩、穿透、击穿 Redis布隆过滤器 →
评论
验证登录状态...