[Redis小技巧29]从 Setnx 到 Redlock:Redis 分布式锁的演进之路与生产级实践

在单机系统中,我们习惯使用 synchronizedReentrantLock 来保证线程安全。然而,在微服务架构普及的今天,服务实例往往部署在多台机器上,JVM(Java Virtual Machine,Java 虚拟机) 级别的锁无法跨进程生效。当多个客户端同时竞争共享资源(如秒杀库存、数据库行记录、定时任务调度)时,分布式锁便应运而生。

Redis 凭借其高性能、原子操作特性以及天然的支持过期时间(TTL),成为了实现分布式锁的首选中间件。

一、初识分布式锁------SETNX + EXPIRE

最直观的实现方式是利用 Redis 的 SETNX (SET if Not Exists) 命令。

原理:

利用 SETNX 的互斥性。当 Key 不存在时,设置成功,代表获取锁;当 Key 已存在时,设置失败,代表锁被占用。为了防止客户端宕机导致锁无法释放,必须配合 EXPIRE 设置过期时间。

伪代码逻辑:

java 复制代码
if (jedis.setnx(lock_key, "1") == 1) {
    jedis.expire(lock_key, 10); // 设置10秒过期
    try {
        doSomething();
    } finally {
        jedis.del(lock_key);
    }
}

存在的问题:

  • 原子性缺失: SETNXEXPIRE 是两条独立的指令。如果客户端在 SETNX 成功后、EXPIRE 执行前崩溃(如进程被杀、断电),这把锁将永远无法释放(死锁),导致后续所有请求阻塞。

二、原子性优化------Lua 脚本与 SET 扩展指令

为了解决 SETNXEXPIRE 非原子执行的问题,业界提出了两种优化方案。

方案 A:Lua 脚本

利用 Redis 执行 Lua 脚本的原子性,将加锁和设置过期时间封装在一个脚本中。

方案 B:SET 扩展参数(推荐)

Redis 2.6.12 版本后,SET 命令支持了扩展参数,能够原子地完成"不存在则设置"和"设置过期时间"两个动作。

命令格式:

bash 复制代码
SET key value [EX seconds] [PX milliseconds] [NX]

参数解析:

  • NX: Not Exists,仅当键不存在时执行。
  • EX/PX: 设置过期时间(秒/毫秒)。

演进对比表:

方案 原子性 复杂度 推荐指数 备注
SETNX + EXPIRE 存在死锁风险,生产环境禁用的基础版
Lua 脚本 ⭐⭐⭐ 灵活,但代码维护成本稍高
SET ... NX PX ⭐⭐⭐⭐⭐ 官方推荐,简洁高效

三、安全性与误删问题------唯一标识与 Lua 解锁

在第二阶段的基础上,解决了加锁的原子性,但解锁环节依然存在隐患。

场景描述:

客户端 A 获取了锁,设置了 10 秒过期。由于业务逻辑卡顿(如 Full GC),执行了 15 秒。

  1. 第 10 秒时,锁自动过期释放。
  2. 客户端 B 成功获取锁。
  3. 第 15 秒时,客户端 A 执行完毕,执行 DEL 命令释放锁。
  4. 结果: 客户端 A 误删了客户端 B 的锁!

解决方案:

  • Value 唯一化: 在加锁时,Value 设置为一个唯一标识(如 UUID + ThreadID)。
  • 解锁校验: 删除锁之前,先判断 Value 是否匹配。
  • 原子性保障: "判断 Value" 和 "删除 Key" 必须原子执行,因此必须使用 Lua 脚本。

解锁 Lua 脚本:

lua 复制代码
if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('del', KEYS[1])
else
    return 0
end

四、高可用挑战------Redlock 算法

上述方案在 Redis 单机或"主从复制"架构下仍存在缺陷。如果 Redis Master 节点在获取锁后、同步给 Slave 之前宕机,Slave 晋升为新 Master,锁信息丢失,导致多个客户端同时持有锁。

为了解决这个问题,Redis 作者提出了 Redlock 算法。

核心思想:

部署 N 个(通常 5 个)独立的 Redis Master 节点。获取锁时,客户端需向所有节点发起请求。

执行流程:

  1. 获取当前时间(毫秒)。
  2. 向 N 个节点依次发送加锁请求(设置较短的超时时间)。
  3. 计算获取锁的耗时。
  4. 成功条件: 客户端在至少 N/2 + 1 个节点上获取锁成功,且总耗时小于锁的有效期。

Redlock 的争议:

尽管 Redlock 提高了安全性,但也存在争议(如 Martin Kleppmann 提出的时钟跳跃问题)。在实际工程中,如果不需要极致的强一致性,通常使用"单机 Redis + 主从复制 + 等待从库同步"或 Zookeeper/Etcd 等 CP 系统作为替代。

五、常见面试题与解析

Q1: 既然有了 SETNX,为什么还需要 Redlock?

A: SETNX 方案通常基于单点 Redis。如果 Redis Master 宕机且数据未同步到 Slave,会导致锁丢失,破坏互斥性。Redlock 通过多数派原则(Quorum)解决了单点故障带来的数据一致性问题,适用于对锁安全性要求极高的场景。

Q2: 业务执行时间超过了锁的过期时间怎么办?

A: 这是一个经典问题,通常有三种解法:

  1. 调大过期时间: 预估业务最大耗时,但这会导致资源浪费。
  2. 看门狗机制: 类似 Redisson 的实现,启动一个后台线程,每隔一段时间(如 10s)检测锁是否还持有,如果持有则自动续期(Reset TTL)。
  3. 快速失败: 如果获取锁失败或执行超时,直接抛出异常,不进行重试。

Q3: Redis 分布式锁和 Zookeeper 分布式锁有什么区别?

A:

  • Redis: 基于 AP (Availability + Partition Tolerance)模型(通常),性能极高,适合高并发、对锁安全性要求不是绝对严苛的场景(如缓存重建、普通秒杀)。
  • Zookeeper: 基于 CP (Consistency + Partition Tolerance)模型,利用临时顺序节点实现,强一致性,但性能略低于 Redis,适合对数据一致性要求极高的场景(如 Leader 选举、配置管理)。

CP (Consistency + Partition Tolerance):"宁可宕机,不可出错"(严谨派)

AP (Availability + Partition Tolerance):"宁可数据旧点,也要一直服务"(实用派)

相关推荐
七夜zippoe2 小时前
联邦学习实战:隐私保护的分布式机器学习——联邦平均与差分隐私
分布式·python·机器学习·差分隐私·联邦平均
传感器与混合集成电路2 小时前
从拉曼散射到相位解调:分布式光纤测井技术解析
分布式·架构
-ONLY-¥2 小时前
MySQL备份恢复全攻略
数据库·oracle
一个天蝎座 白勺 程序猿2 小时前
源网荷储实时互动需求下,时序数据库如何赋能新型电力系统?
数据库·时序数据库
zs宝来了2 小时前
Redis 数据结构底层实现:intset、ziplist、skiplist 深度剖析
数据结构·redis·源码解析·skiplist·ziplist·intset
笑梦无境2 小时前
mysql基础篇二(多年前整理)
数据库·mysql
艾伦_耶格宇2 小时前
【zabbix】-2 zabbix本地部署
数据库·zabbix
麻花20132 小时前
Oracle 数据泵导出与还原操作指南
数据库·oracle
bearpping3 小时前
MacOs安装Redis并设置为开机、后台启动
redis·macos·蓝桥杯