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算法核心步骤:

  1. 获取当前时间戳 T1。
  2. 依次向 N个独立Redis节点(通常N=5)发送SET命令(NX+PX),使用相同的lock_key和unique_value。每个命令设置一个较小的超时时间(如10ms),避免等待故障节点。
  3. 计算获取锁消耗的总时间 elapsed = 当前时间 - T1。
  4. 判断条件:成功获取锁的节点数 > N/2(即多数节点),且 elapsed < 锁的过期时间。
  5. 如果满足条件,锁获取成功;否则,向所有节点发送解锁命令(释放部分获取的锁)。
Redlock算法流程(N=5) 客户端(支付服务) Redis-1 Redis-2 Redis-3 Redis-4 Redis-5 成功获取锁的节点数 > 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的强一致性特性。