一、为什么需要分布式锁?
假设你开发了一个电商系统,商品库存只剩 1 件,两个请求同时购买。

如果只有一个 Java 进程,可以考虑 synchronized 或 ReentrantLock。
但是,如果服务部署了三个实例:
- Java 实例 A 有自己的锁。
- Java 实例 B 有自己的锁。
- Java 实例 C 也有自己的锁。
它们的锁互不相通,无法协调所有实例。
所以,需要一个所有实例都能访问的公共锁服务,Redis 就可以承担这个角色。
二、Redis 分布式锁怎么实现?
最基础的实现是使用 Redis 的 SET 命令:
SET lock:product:1001 unique-token NX EX 30这条命令包含四个关键部分:
| 部分 | 含义 |
|---|---|
lock:product:1001 | 锁的名称 |
unique-token | 当前持有者的唯一标识 |
NX | 只有 Key 不存在时才设置成功 |
EX 30 | 锁在 30 秒后过期 |
加锁过程
假设请求 A 和请求 B 同时抢同一把锁。

这里最重要的是:加锁必须通过一条原子命令完成。
不能先执行 EXISTS 检查锁是否存在,再执行 SET,因为两个请求可能同时检查到锁不存在,随后都加锁成功。
三、为什么需要唯一标识?
假设请求 A 获得了锁,但业务执行超过 30 秒,锁自动过期了。
此时请求 B 成功获得了同一把锁。
如果请求 A 执行完后,直接执行:
DEL lock:product:1001就会把请求 B 的锁也删除!
所以,锁必须带有唯一标识,释放锁时必须确认自己仍然是锁的持有者。
例如:
请求 A 获得锁:token-A请求 B 获得锁:token-B释放锁时,先判断锁的值是否等于自己的 token,只有相等才能删除。
通常使用 Lua 脚本原子完成比较和删除:
if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1])else return 0end这里:
KEYS[1]:锁的 Key。ARGV[1]:当前请求持有的唯一标识。- 相等:删除锁。
- 不相等:不删除,避免误删其他请求的锁。
为什么要用 Lua?因为比较和删除必须作为一个不可分割的操作执行,否则可能在比较后、删除前发生锁被其他请求重新获取的情况。
四、锁过期了,但业务还没执行完怎么办?
这是分布式锁的另一个重要问题。
例如:
- 请求 A 获得锁,有效期 30 秒。
- 请求 A 执行业务,需要 40 秒。
- 第 30 秒时,锁过期。
- 请求 B 获得锁,开始执行业务。
- 请求 A 仍然没有执行完。
此时 A、B 可能同时操作共享资源。
常见解决方式是 Redisson 看门狗机制。
- 使用 Redisson 获取锁时,如果没有显式指定租约时间,且看门狗机制启用,Redisson 会定期续期。
- 只要持锁客户端仍然正常运行并满足续期条件,就会延长锁的有效期。
- 客户端异常退出后,续期停止,锁最终会过期释放。
注意:看门狗不能彻底解决所有并发安全问题。对于执行时间不确定、可能发生长时间暂停的业务,还可以考虑使用** fencing token(栅栏令牌)**等机制保护资源。
五、面试时怎么回答?
Redis 分布式锁通常通过 SET key value NX EX 实现。
首先,使用 SET 命令原子地完成加锁,其中 NX 表示 Key 不存在时才加锁成功,EX 用于设置过期时间,避免客户端异常退出后锁一直无法释放。
其次,锁的 value 必须使用唯一标识,防止其他请求误释放自己的锁。释放锁时,通常通过 Lua 脚本原子地比较锁的持有者标识并删除锁。
另外,如果业务执行时间超过锁的过期时间,可能出现多个请求同时执行业务的问题。可以使用 Redisson 的看门狗机制自动续期;对于要求更高的场景,还需要考虑锁过期、客户端暂停和资源保护等问题。
六、记住这四个关键词
SET NX EX:原子加锁,并设置过期时间。- 唯一标识:防止误删别人的锁。
- Lua 脚本:原子地校验并释放锁。
- Redisson 看门狗:在满足条件时自动续期。
这四个知识点掌握好,Redis 分布式锁的基础面试题基本就有了回答框架。