好的阿杰 👍
Redis 分布式锁是分布式系统里防止资源竞争保证并发安全的重要手段。它的实现方式有很多种,不同方案在安全性、可用性、性能上各有取舍。下面我帮你梳理 Redis 实现分布式锁的 7 种典型方案,并逐个说明原理、优缺点和适用场景。


1. SETNX + EXPIRE (最基础方案)

原理

  • 使用 SETNX key value 来抢占锁(谁先成功谁拿到锁)。
  • 再调用 EXPIRE key ttl 给锁设置过期时间。

问题

  • SETNX 和 EXPIRE 是两个独立操作,非原子性。
    如果客户端在 SETNX 成功后崩溃,没执行 EXPIRE,锁会永久不释放 → 死锁

适用场景:学习用或低风险环境,不推荐生产。


2. SET key value NX EX (原子操作)

原理

  • 使用 Redis 2.6.12+ 提供的原子命令:SET lock_key unique_value NX EX 30
    • NX:只在 key 不存在时才设置。
    • EX:设置过期时间。
    • unique_value:用来区分锁的持有者(一般是 UUID)。

优点

  • 原子性,解决了 死锁问题
  • 轻量级,性能好。

缺点

  • 如果业务执行时间 > TTL,锁会过期被别人抢走 → 锁误释放

适用场景:最常用的分布式锁实现。


3. SET NX + Lua 脚本释放锁

原理

  • 获取锁同方案 2。
  • 释放锁时,使用 Lua 脚本保证原子性:if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end 确保 只有持有锁的客户端才能释放锁

优点

  • 防止误删别人的锁。

缺点

  • 仍然存在锁自动过期 → 锁续期问题

适用场景:比方案 2 更安全,常见的工程实践。


4. 加入 “看门狗”自动续期机制

原理

  • 在方案 3 的基础上,客户端获取锁后开启后台线程,每隔一段时间检查业务是否还在运行,如果还在就给锁 续期(比如延长过期时间)。
  • 常见实现:Redisson(Java 客户端)。

优点

  • 解决锁因过期而被误抢的问题。

缺点

  • 看门狗依赖客户端健康,若客户端宕机可能导致锁提前释放。

适用场景:生产常用方案(推荐 Redisson)。


5. 基于 SETNX 的 公平锁 / 队列锁

原理

  • 获取锁失败时,不是直接返回失败,而是把请求放进一个等待队列(ZSet 或 List)。
  • 按顺序尝试获取锁,实现公平性

优点

  • 防止饥饿问题(某些请求一直拿不到锁)。

缺点

  • 实现复杂,Redis 内存消耗增加。

适用场景:对公平性要求高的场景(比如排队系统)。


6. RedLock 算法(多实例分布式锁)

提出者:Redis 作者 Antirez。

原理

  • 部署 N 个(推荐 5 个)Redis 实例。
  • 客户端在大多数(> N/2)实例上成功获取锁,才算成功。
  • 锁过期时间按最小值 – 网络延迟来计算,防止时间漂移。

优点

  • 容错性高,即使部分 Redis 节点宕机,锁依然可用。

缺点

  • 实现复杂,网络分区下仍有一致性风险。
  • 社区对 RedLock 是否绝对安全仍有争议。

适用场景:跨机房、多 Redis 实例环境。


7. 基于 Zookeeper / etcd 的分布式锁(对比方案)

虽然不是 Redis,但常作为替代:

原理

  • Zookeeper 使用临时顺序节点,天然支持锁和续期。
  • etcd 提供租约(lease),锁自动释放。

优点

  • 天然的高可用、强一致性。
  • 不用自己实现看门狗或续期机制。

缺点

  • 性能比 Redis 差,延迟更高。

适用场景:对强一致性要求高的分布式系统。


📌 总结对比表

方案原理优点缺点适用场景
1. SETNX + EXPIRE两步实现锁简单非原子,死锁风险不推荐
2. SET NX EX原子命令性能好锁可能过期常用
3. SET NX EX + Lua加 Lua 释放锁防止误删锁锁过期问题安全性更高
4. 看门狗机制自动续期防止锁过期依赖客户端健康推荐(Redisson)
5. 公平锁排队获取防止饥饿实现复杂排队、公平性场景
6. RedLock多实例投票容错性高仍有一致性风险跨机房/多实例
7. Zookeeper/etcd临时节点/租约强一致性性能差金融、强一致业务

好的阿杰,我给你写一个完整的 Python Redis 分布式锁示例,实现以下功能:

  1. 获取锁:SET NX EX 原子命令
  2. 释放锁:Lua 脚本保证只有持有者释放
  3. 看门狗自动续期:后台线程延长锁过期时间

我们用 redis-py 库实现,示例代码如下:

import redis
import threading
import time
import uuid

class RedisDistributedLock:
    def __init__(self, redis_client, lock_key, ttl=10, renew_interval=3):
        """
        redis_client: Redis连接
        lock_key: 锁的Key
        ttl: 锁过期时间,单位秒
        renew_interval: 看门狗续期间隔
        """
        self.redis = redis_client
        self.lock_key = lock_key
        self.ttl = ttl
        self.renew_interval = renew_interval
        self.lock_value = str(uuid.uuid4())
        self._renew_thread = None
        self._stop_renew = threading.Event()

        # Lua脚本保证原子释放
        self._release_script = """
        if redis.call("get", KEYS[1]) == ARGV[1] then
            return redis.call("del", KEYS[1])
        else
            return 0
        end
        """
        self._release_lua = self.redis.register_script(self._release_script)

    def acquire(self):
        """尝试获取锁"""
        result = self.redis.set(self.lock_key, self.lock_value, nx=True, ex=self.ttl)
        if result:
            # 启动看门狗线程
            self._stop_renew.clear()
            self._renew_thread = threading.Thread(target=self._renew_lock)
            self._renew_thread.daemon = True
            self._renew_thread.start()
        return result

    def release(self):
        """释放锁"""
        self._stop_renew.set()  # 停止看门狗
        if self._renew_thread:
            self._renew_thread.join()
        self._release_lua(keys=[self.lock_key], args=[self.lock_value])

    def _renew_lock(self):
        """后台看门狗线程,自动续期"""
        while not self._stop_renew.is_set():
            time.sleep(self.renew_interval)
            try:
                # 续期,只有持有锁的客户端才能续期
                script = """
                if redis.call("get", KEYS[1]) == ARGV[1] then
                    return redis.call("expire", KEYS[1], ARGV[2])
                else
                    return 0
                end
                """
                renew_lua = self.redis.register_script(script)
                renew_lua(keys=[self.lock_key], args=[self.lock_value, self.ttl])
            except Exception as e:
                print("续期失败:", e)

# ------------------ 使用示例 ------------------
if __name__ == "__main__":
    r = redis.Redis(host='localhost', port=6379, db=0)
    lock = RedisDistributedLock(r, "my_lock", ttl=10, renew_interval=3)

    if lock.acquire():
        print("获取锁成功,开始执行任务")
        try:
            # 模拟业务操作
            time.sleep(15)
        finally:
            lock.release()
            print("任务完成,释放锁")
    else:
        print("获取锁失败,任务取消")


✅ 说明:

  1. 唯一标识锁:每个客户端生成 UUID,防止误释放别人的锁。
  2. Lua 脚本释放锁:保证释放操作原子性。
  3. 看门狗线程:每隔 renew_interval 秒续期 TTL,避免业务执行时间长导致锁过期被别人抢。
  4. 可调整 TTL 和 renew_interval:保证续期安全且不会频繁操作 Redis。