4. Redis分布式锁原理:SETNX、EXPIRE、Redlock与原子性
在支付防重场景中,Redis分布式锁是保证同一时刻只有一个支付请求被处理的核心手段。理解其底层原理,是正确设计幂等方案的前提。本章深入剖析Redis分布式锁的四个关键知识点:SETNX命令、EXPIRE过期机制、Redlock算法以及原子性问题。
4.1 Redis SETNX命令:锁的原子创建
SETNX(Set if Not eXists)是Redis实现分布式锁的基础命令。其语义是:只有当键不存在时,才设置键值对;如果键已存在,则不做任何操作。这一原子性检查与写入的特性,天然适合“尝试获取锁”的场景。
核心命令格式:
SETNX lock_key unique_value
# 返回 1 表示获取锁成功
# 返回 0 表示锁已被其他客户端持有
在支付防重中,lock_key通常设计为order:{orderId}:pay_lock,unique_value使用requestId或UUID,用于后续安全释放锁(防止误删其他客户端的锁)。
| 场景 | SETNX返回值 | 含义 |
|---|---|---|
| 首次支付请求 | 1 | 获取锁成功,允许执行支付逻辑 |
| 重复支付请求(锁未释放) | 0 | 获取锁失败,直接返回“处理中”或“已支付” |
| 锁已过期释放后重试 | 1 | 重新获取锁,继续处理(需业务层判断幂等) |
4.2 EXPIRE过期机制:防止死锁
如果客户端在获取锁后突然崩溃(如进程宕机、网络断连),锁将永远不会被释放,导致死锁。EXPIRE机制为锁设置一个自动过期时间(TTL),确保锁最终会被释放。
常见陷阱:SETNX与EXPIRE是两个独立命令,非原子执行。如果SETNX成功后在执行EXPIRE前客户端崩溃,锁将永久存在。这是早期Redis分布式锁最常见的Bug。
正确的做法是使用Redis 2.6.12之后提供的原子化扩展命令:
SET lock_key unique_value NX PX 30000
# NX 等同于 SETNX(不存在才设置)
# PX 30000 设置过期时间为30秒(毫秒)
# 整个命令是原子操作,一步完成
过期时间的选择需要权衡:过短可能导致业务未完成锁就释放(被其他请求误入);过长则增加死锁风险窗口。支付场景通常设置为5~30秒,并配合锁续期(Watch Dog)机制。
4.3 Redlock算法简介:跨主节点的强一致性
在Redis单节点或主从架构下,如果主节点宕机,锁可能丢失。Redlock算法(由Redis作者提出)通过在多个独立Redis节点上同时获取锁,实现高可用的分布式锁。
适用场景:对锁可靠性要求极高的金融级支付系统,且部署了至少3个独立Redis实例(非主从,相互独立)。
Redlock算法核心步骤:
- 获取当前时间戳
T1。 - 依次向 N个独立Redis节点(通常N=5)发送SET命令(NX+PX),使用相同的
lock_key和unique_value。每个命令设置一个较小的超时时间(如10ms),避免等待故障节点。 - 计算获取锁消耗的总时间
elapsed = 当前时间 - T1。 - 判断条件:成功获取锁的节点数 > N/2(即多数节点),且 elapsed < 锁的过期时间。
- 如果满足条件,锁获取成功;否则,向所有节点发送解锁命令(释放部分获取的锁)。
4.4 Redis锁的原子性问题:从拆分到融合
原子性是分布式锁正确性的基石。Redis锁的原子性问题经历了三个阶段:
| 阶段 | 操作方式 | 原子性 | 风险 |
|---|---|---|---|
| 早期(错误) | SETNX + EXPIRE(两条命令) | ❌ 非原子 | SETNX后崩溃,锁永久存在 |
| 改进(正确) | SET key value NX PX timeout | ✅ 原子 | 单点故障风险 |
| 高级(Redlock) | 多节点原子SET + 多数派决策 | ✅ 原子 + 高可用 | 实现复杂,性能开销大 |
除了加锁,解锁操作也必须保证原子性。使用Lua脚本实现“检查-删除”原子操作:
-- 解锁Lua脚本:只有value匹配时才删除
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
支付防重最佳实践:
- 加锁:使用
SET key value NX PX 30000原子命令。 - 解锁:使用Lua脚本保证“检查value+删除”原子性,防止误删其他客户端的锁。
- 锁续期:后台线程(Watch Dog)在锁过期前自动续期,避免业务未完成锁就释放。
- 高可用:对金融级场景,考虑Redlock或使用Redis Cluster的强一致性特性。