【学习篇】Redis 分布式锁

Redis 分布式锁是解决分布式系统中 并发资源竞争 的常用方案,通过 Redis 的原子操作实现多节点间的互斥访问。其核心目标是:在分布式环境下,确保同一时刻只有一个客户端能持有锁,从而安全地操作共享资源

一、分布式锁的核心要求

实现一个可靠的 Redis 分布式锁,需满足以下条件:

  1. 互斥性:同一时刻只有一个客户端持有锁。
  2. 安全性:锁只能被持有它的客户端释放(防误删)。
  3. 避免死锁:即使持有锁的客户端崩溃,锁也能在一定时间后自动释放(超时机制)。
  4. 高可用:Redis 集群环境下,锁服务需具备容错能力(如主从切换时锁不丢失)。
  5. 性能:加锁和解锁操作需高效(低延迟、高吞吐量)。

二、基础实现方案(基于 Redis 命令)

1. 加锁:SET NX EX(推荐原子操作)

使用 Redis 的 SET 命令的 NX(Only if Not Exists)EX(Expire) 选项,原子性地完成"判断锁是否存在 + 设置锁 + 设置过期时间":

bash 复制代码
# 语法:SET key value NX EX seconds
SET lock:resource_name <唯一随机值> NX EX 30
  • 参数说明
  • lock:resource_name:锁的 key(需包含具体资源名,如 lock:order:1001)。
  • <唯一随机值>:客户端生成的唯一标识(如 UUID),用于解锁时验证身份(防误删)。
  • NX:仅当 key 不存在时才设置(保证互斥性)。
  • EX 30:自动过期时间(30 秒,避免死锁)。
  • 返回值
  • 成功:OK(表示加锁成功)。
  • 失败:nil(表示锁已被其他客户端持有)。
2. 解锁:Lua 脚本(原子操作)

解锁需满足 "验证唯一标识 + 删除锁" 两步原子性,否则可能误删其他客户端的锁(如:客户端 A 锁超时自动释放,客户端 B 加锁,此时客户端 A 才执行解锁,可能删除 B 的锁)。
必须通过 Lua 脚本实现原子操作

lua 复制代码
-- 解锁脚本:判断 value 是否匹配,匹配则删除
if redis.call("GET", KEYS[1]) == ARGV[1] then
    return redis.call("DEL", KEYS[1])
else
    return 0
end
  • 调用方式(以 Python 为例):
python 复制代码
  import redis
  import uuid

  r = redis.Redis()
  lock_key = "lock:resource_name"
  client_id = str(uuid.uuid4())  # 客户端唯一标识

  # 加锁
  lock_acquired = r.set(lock_key, client_id, nx=True, ex=30)

  # 解锁
  if lock_acquired:
      unlock_script = """
          if redis.call("GET", KEYS[1]) == ARGV[1] then
              return redis.call("DEL", KEYS[1])
          else
              return 0
          end
      """
      r.eval(unlock_script, 1, lock_key, client_id)  # 1 表示 KEYS 的数量
3. 避免死锁:超时时间与锁续期
  • 合理设置过期时间:需大于业务操作的最大耗时(如操作需 10 秒,过期时间设为 30 秒)。
  • 锁续期(Watchdog) :若业务操作耗时可能超过过期时间,需启动后台线程,定期(如每 10 秒)为锁续期(重置过期时间),操作完成后停止续期。
    ▶ 示例:Redisson 客户端的 watch dog 机制。

三、进阶方案:解决集群环境下的高可用问题

基础方案依赖单节点 Redis,若节点故障(如主从切换时主库宕机,从库未同步到锁数据),可能导致 锁丢失重复加锁。以下是两种高可用方案:

1. Redlock 算法(Redis 官方推荐)

由 Redis 作者 Antirez 提出,基于 多个独立 Redis 节点(通常 5 个)实现分布式锁,核心思想:

  • 客户端向 过半(≥3 个)独立节点 发送加锁请求(使用 SET NX EX)。
  • 若过半节点加锁成功,且总耗时 ≤ 锁过期时间的 1/3,则认为加锁成功。
  • 解锁时需向 所有节点 发送解锁请求(无论加锁是否成功)。

优势 :不依赖单节点,即使部分节点故障,仍能保证锁的可用性(满足 CAP 中的 AP,但牺牲部分一致性)。
缺点:实现复杂,性能较低(需访问多个节点)。

2. 基于 Redis Cluster 的方案

利用 Redis Cluster 的 主从复制 + 哨兵 保证高可用,但需注意:

  • 主库宕机后,从库升级为主库前,可能存在短暂的 锁丢失窗口(主库未同步锁数据到从库)。
  • 可通过 延迟从库同步 降低风险,但无法完全避免(适合对一致性要求不极致的场景)。

四、常见问题与解决方案

1. 问题:锁超时与业务耗时不匹配
  • 风险:若业务操作耗时超过锁过期时间,锁自动释放,其他客户端可能重复加锁。
  • 解决
  • 预估合理的过期时间(略大于最大业务耗时)。
  • 实现 锁续期 (如 Redisson 的 Watchdog 机制:加锁时启动定时任务,每 expire/3 秒续期一次,业务完成后取消任务)。
2. 问题:误删其他客户端的锁
  • 风险 :解锁时未验证唯一标识,直接执行 DEL,可能删除其他客户端的锁。
  • 解决必须用 Lua 脚本原子性执行"验证 + 删除"(见上文"解锁"部分)。
3. 问题:Redis 主从切换导致锁丢失
  • 场景:客户端 A 向主库加锁,主库宕机,从库未同步锁数据即升级为主库,客户端 B 再次加锁成功,导致"双活"。
  • 解决
  • 采用 Redlock 算法(多节点加锁,避免单节点依赖)。
  • 业务层增加 最终一致性校验(如分布式事务的补偿机制)。
4. 问题:并发加锁时的"惊群效应"
  • 场景:大量客户端等待一个锁释放,锁释放瞬间所有客户端同时抢锁,导致 Redis 压力激增。
  • 解决
  • 加锁失败后,通过 随机延迟重试(如 50~200ms 随机值),分散抢锁请求。
  • 使用 阻塞式锁 (如 Redisson 的 RLock.tryLock(30, 10, TimeUnit.SECONDS),内部通过信号量唤醒等待线程,避免轮询)。

五、生产级客户端推荐(避免重复造轮子)

手动实现分布式锁易出错(如 Lua 脚本编写、续期逻辑等),推荐使用成熟的客户端:

1. Redisson(Java)
  • 支持单机、主从、哨兵、Cluster 等多种 Redis 部署模式。
  • 内置 Watchdog 自动续期Redlock 算法可重入锁公平锁 等高级特性。
  • 示例代码
java 复制代码
  RLock lock = redissonClient.getLock("lock:resource_name");
  try {
      // 尝试加锁,最多等待 10 秒,锁过期时间 30 秒
      boolean locked = lock.tryLock(10, 30, TimeUnit.SECONDS);
      if (locked) {
          // 执行业务操作
      }
  } finally {
      if (lock.isHeldByCurrentThread()) {
          lock.unlock(); // 自动验证唯一标识并解锁
      }
  }
2. Python:redis-py + 自定义封装 / RedLock-py
  • 基础方案 :用 redis-py 实现 SET NX EX + Lua 脚本解锁。
  • Redlock 方案 :使用第三方库 redlock-py(实现 Redlock 算法)。
python 复制代码
from redlock import RedLock

with RedLock("lock:resource_name", connection_details=[{"host": "localhost", "port": 6379}], ttl=30000):
# 执行业务操作

六、总结

Redis 分布式锁的核心是通过 原子命令SET NX EX、Lua 脚本)保证互斥性和安全性,通过 过期时间 避免死锁,通过 Redlock 或集群方案 提高可用性。
最佳实践

  1. 锁 key 需包含具体资源名,避免全局锁竞争。
  2. 客户端唯一标识必须随机且唯一(UUID 或随机字符串 + 客户端 ID)。
  3. 解锁必须使用 Lua 脚本原子操作。
  4. 优先使用成熟客户端(如 Redisson),避免重复造轮子。
  5. 根据业务场景选择单节点(简单高效)或 Redlock(高可用)方案。

通过以上策略,可实现一个既可靠又高效的 Redis 分布式锁,满足分布式系统的并发控制需求。

相关推荐
A9better14 小时前
嵌入式开发学习日志38——stm32之看门狗
stm32·嵌入式硬件·学习
DemonAvenger14 小时前
Redis HyperLogLog 深度解析:从原理到实战,助你优雅解决基数统计问题
数据库·redis·性能优化
matlab的学徒14 小时前
nginx+springboot+redis+mysql+elfk
linux·spring boot·redis·nginx
Vizio<17 小时前
《基于 ERT 的稀疏电极机器人皮肤技术》ICRA2020论文解析
论文阅读·人工智能·学习·机器人·触觉传感器
weixin_5142218519 小时前
FDTD与matlab、python耦合
python·学习·matlab·fdtd
心态特好20 小时前
详解redis,MySQL,mongodb以及各自使用场景
redis·mysql·mongodb
递归不收敛20 小时前
吴恩达机器学习课程(PyTorch 适配)学习笔记大纲
pytorch·学习·机器学习
不太可爱的叶某人20 小时前
【学习笔记】kafka权威指南——第10章 监控kafka (7-10章只做了解)
笔记·学习·kafka
不良人天码星1 天前
redis-zset数据类型的常见指令(sorted set)
数据库·redis·缓存