本文目录导读:

实现Redis分布式锁,最经典且目前被广泛推荐的方式是基于 Redlock 算法的变体,或者直接使用单节点 Redis 配合恰当的原子命令。
下面我为你详细介绍两种主流实现方式:最简可行方案(基于 SET NX EX) 和 官方推荐的 Redisson 方案,最后分析高可用场景下的 Redlock 算法逻辑。
核心基础:SET NX + EX/PX(最简方案)
这是最基础的实现,也是理解所有高级方案的前提,核心是利用 Redis 的原子命令 SET key value NX EX seconds。
- 命令含义:
NX: Not eXists,只有 key 不存在时才设置成功,这是“互斥”。EX/PX: 设置过期时间,这是“自动解锁”,防止死锁。
- 为什么不能用
SETNX+Expire分开执行? 因为这两个操作不是原子的,如果设置完SETNX后,程序崩溃了,Expire没执行,锁就永远不会释放,造成死锁。
简单代码实现(伪代码)
import redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
lock_key = "my_lock"
lock_value = "unique_request_id_123" # 非常重要:每个客户端生成的唯一值
timeout = 10 # 锁自动过期时间,单位秒
def acquire_lock():
# 原子操作:只在key不存在时设置,并指定过期时间
result = r.set(lock_key, lock_value, nx=True, ex=timeout)
if result:
print("获取锁成功")
return True
else:
print("获取锁失败")
return False
def release_lock():
# 重要:使用Lua脚本保证原子性,只释放自己的锁
lua_script = """
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
"""
# 执行Lua脚本,传递key和唯一值
result = r.eval(lua_script, 1, lock_key, lock_value)
if result == 1:
print("释放锁成功")
else:
print("释放锁失败,可能已经超时或不是自己的锁")
# 使用示例
if acquire_lock():
try:
# 执行业务逻辑...
pass
finally:
release_lock()
需要解决的几个关键问题
- 防止误删锁: 锁的 Value 必须是一个唯一标识(如 UUID + 线程ID),释放锁时,需要先检查 Value 是否是自己设置的值,再删除,这正是 Lua 脚本的作用。
- 原子性: 获取锁和检查+释放锁都必须是原子操作。
- 锁的超时与业务执行时间:
- 问题: 如果业务执行时间超过了锁的过期时间,锁会自动释放,其他线程就会拿到锁,导致并发问题。
- 解决方案: 锁续期(Watch Dog),启动一个后台线程,在锁快过期时(比如过期时间过了1/3),自动为锁续期,这在对时间敏感的业务中至关重要,Redisson 客户端内置了这个功能。
生产级推荐:Redisson(Java生态)或类似客户端
手动实现 Lua 脚本和看门狗(Watch Dog)并不简单,官方推荐使用 Redisson(Java)这样的成熟客户端,它封装了分布式锁的所有细节。
Redisson 的核心优势
- 自动续期(Watch Dog): 默认的锁超时是 30 秒,只要业务线程没有结束,看门狗会自动每 10 秒将锁的过期时间重置为 30 秒,有效防止业务执行过长导致锁自动释放。
- 重入锁(Reentrant Lock): 同一个线程可以多次获取同一个锁,支持重入。
- 多种锁模式: 公平锁、读写锁、红锁等。
Redisson 代码示例(Java)
// 1. 创建配置
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
// 2. 创建 RedissonClient
RedissonClient redisson = Redisson.create(config);
// 3. 获取分布式锁
RLock lock = redisson.getLock("myLock");
try {
// 4. 加锁(默认等待时间0,锁有效期30秒,会自动续期)
// 也可以指定等待时间:lock.tryLock(100, 10, TimeUnit.SECONDS);
lock.lock();
// 5. 执行业务逻辑
System.out.println("执行业务...");
Thread.sleep(60000); // 假设业务需要60秒,锁会自动续期
} finally {
// 6. 释放锁
lock.unlock();
}
在 Java 生态中,直接用 Redisson 是最省心、最安全的做法,它已经处理好了原子的加解锁、唯一标识、自动续期等问题。
高可用场景:Redlock 算法
上面的方案基于单点 Redis Master,如果这个 Master 宕机,锁就丢失了,为了在 Redis 集群(Master/Replica)中保证锁的高可用,Redis 作者提出了 Redlock 算法。
Redlock 算法的核心逻辑
它不是在一个 Redis 实例上加锁,而是在 N 个(奇数,5 个) 独立、不相关的 Redis Master 实例上同时加锁,所有 Master 之间没有数据同步。
- 获取当前时间
T1。 - 依次向 5 个 Master 发送加锁请求,每个请求带有相同的 Key 和 Value,以及一个很小的超时时间(远小于锁的自动过期时间)。
- 判断是否加锁成功: 如果客户端在大多数(N/2 + 1,即 3/5) 实例上成功加锁,且总耗时(当前时间 - T1) 小于锁的过期时间,则认为锁被成功获取。
- 释放锁: 向所有 5 个实例发送删除锁命令(即使之前加锁失败的也要发,清除残留)。
适用场景与争议
- 适用场景: 对锁的安全性要求极高,且 Redis 作为基础设施必须保证高可用的场景。
- 争议: 分布式系统专家 Martin Kleppmann 曾撰文指出 Redlock 存在时钟跳跃、GC 暂停等导致锁失效的安全隐患,Redis 作者也进行了反驳。
- 实际建议: 对于绝大部分业务,单 Master 配合合适的策略(如 Redisson 看门狗)已经足够,只有当你的系统极度敏感、需要跨主从自动故障转移仍保证锁互斥时,再考虑 Redlock。
总结建议
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 大多数业务 | 单 Master + Redisson(Java)/ 或自己实现的 Lua 脚本 | 简单、高效、性能好,利用看门狗解决超时问题,利用Lua解决原子性问题。 |
| 高可用、对安全要求极高 | Redlock 算法(或 Redisson MultiLock) | 跨多个独立实例,容忍单点故障,但实现复杂,有一定理论争议,只在必要时使用。 |
核心要点回顾:
- 加锁用
SET NX EX保持原子性。 - Value 用唯一 ID,释放锁用 Lua 脚本检查身份。
- 用看门狗(Watch Dog) 避免业务超时导致锁释放。
- 优先使用成熟的客户端库(如 Redisson)。