好的阿杰 👍
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 分布式锁示例,实现以下功能:
- 获取锁:SET NX EX 原子命令
- 释放锁:Lua 脚本保证只有持有者释放
- 看门狗自动续期:后台线程延长锁过期时间
我们用 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("获取锁失败,任务取消")
✅ 说明:
- 唯一标识锁:每个客户端生成 UUID,防止误释放别人的锁。
- Lua 脚本释放锁:保证释放操作原子性。
- 看门狗线程:每隔
renew_interval
秒续期 TTL,避免业务执行时间长导致锁过期被别人抢。 - 可调整 TTL 和 renew_interval:保证续期安全且不会频繁操作 Redis。
发表回复