Redis分布式锁怎么实现?

wen python案例 2

本文目录导读:

Redis分布式锁怎么实现?

  1. 核心基础:SET NX + EX/PX(最简方案)
  2. 生产级推荐:Redisson(Java生态)或类似客户端
  3. 高可用场景:Redlock 算法
  4. 总结建议

实现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()

需要解决的几个关键问题

  1. 防止误删锁: 锁的 Value 必须是一个唯一标识(如 UUID + 线程ID),释放锁时,需要先检查 Value 是否是自己设置的值,再删除,这正是 Lua 脚本的作用。
  2. 原子性: 获取锁和检查+释放锁都必须是原子操作。
  3. 锁的超时与业务执行时间
    • 问题: 如果业务执行时间超过了锁的过期时间,锁会自动释放,其他线程就会拿到锁,导致并发问题。
    • 解决方案锁续期(Watch Dog),启动一个后台线程,在锁快过期时(比如过期时间过了1/3),自动为锁续期,这在对时间敏感的业务中至关重要,Redisson 客户端内置了这个功能。

生产级推荐:Redisson(Java生态)或类似客户端

手动实现 Lua 脚本和看门狗(Watch Dog)并不简单,官方推荐使用 Redisson(Java)这样的成熟客户端,它封装了分布式锁的所有细节。

Redisson 的核心优势

  1. 自动续期(Watch Dog): 默认的锁超时是 30 秒,只要业务线程没有结束,看门狗会自动每 10 秒将锁的过期时间重置为 30 秒,有效防止业务执行过长导致锁自动释放。
  2. 重入锁(Reentrant Lock): 同一个线程可以多次获取同一个锁,支持重入。
  3. 多种锁模式: 公平锁、读写锁、红锁等。

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 之间没有数据同步。

  1. 获取当前时间 T1
  2. 依次向 5 个 Master 发送加锁请求,每个请求带有相同的 Key 和 Value,以及一个很小的超时时间(远小于锁的自动过期时间)。
  3. 判断是否加锁成功: 如果客户端在大多数(N/2 + 1,即 3/5) 实例上成功加锁,且总耗时(当前时间 - T1) 小于锁的过期时间,则认为锁被成功获取。
  4. 释放锁: 向所有 5 个实例发送删除锁命令(即使之前加锁失败的也要发,清除残留)。

适用场景与争议

  • 适用场景: 对锁的安全性要求极高,且 Redis 作为基础设施必须保证高可用的场景。
  • 争议: 分布式系统专家 Martin Kleppmann 曾撰文指出 Redlock 存在时钟跳跃、GC 暂停等导致锁失效的安全隐患,Redis 作者也进行了反驳。
  • 实际建议: 对于绝大部分业务,单 Master 配合合适的策略(如 Redisson 看门狗)已经足够,只有当你的系统极度敏感、需要跨主从自动故障转移仍保证锁互斥时,再考虑 Redlock。

总结建议

场景 推荐方案 理由
大多数业务 单 Master + Redisson(Java)/ 或自己实现的 Lua 脚本 简单、高效、性能好,利用看门狗解决超时问题,利用Lua解决原子性问题。
高可用、对安全要求极高 Redlock 算法(或 Redisson MultiLock) 跨多个独立实例,容忍单点故障,但实现复杂,有一定理论争议,只在必要时使用。

核心要点回顾

  1. 加锁用 SET NX EX 保持原子性。
  2. Value 用唯一 ID,释放锁用 Lua 脚本检查身份。
  3. 看门狗(Watch Dog) 避免业务超时导致锁释放。
  4. 优先使用成熟的客户端库(如 Redisson)。

抱歉,评论功能暂时关闭!