在分布式系统中,分布式锁是一种非常重要的机制,它能够保证多个进程、线程或机器之间对共享资源的互斥访问。Redis作为一个高效的内存数据库,经常用于实现分布式锁。以下是通过Redis实现分布式锁的七种常见方案,每种方案的优缺点都有不同的侧重点,适用于不同的场景。
1. SETNX + EXPIRE(最基础的分布式锁)
实现方式:
通过Redis的 SETNX
命令来实现锁的加锁操作,通过 EXPIRE
设置过期时间来避免死锁。
SETNX
是“SET if Not Exists”的缩写,它的作用是只有当指定的 key 不存在时,才会设置该 key 的值。- 设置一个锁,锁的键是唯一的,通过
SETNX
获取锁。 - 设置一个过期时间,避免在锁持有者死掉时锁不会释放。
代码示例:
import redis
import time
r = redis.Redis()
def acquire_lock(lock_name, timeout=10):
lock_key = f"lock:{lock_name}"
# 设置锁,过期时间为timeout
if r.setnx(lock_key, 1):
r.expire(lock_key, timeout)
return True
return False
def release_lock(lock_name):
lock_key = f"lock:{lock_name}"
r.delete(lock_key)
# 使用
if acquire_lock("mylock"):
try:
# 执行业务逻辑
pass
finally:
release_lock("mylock")
优缺点:
- 优点:
- 简单、易于理解。
- 通过过期时间防止死锁。
- 缺点:
- 如果客户端崩溃或断开连接,锁会被持有直到超时。
- 不支持锁重入,无法解决锁的持有者在运行过程中多次请求锁的情况。
2. SET command(Redis 2.6+)
实现方式:
使用 SET
命令的 NX
和 EX
参数,能够同时设置值和过期时间。这种方法在Redis 2.6+中可以使用。
NX
表示只有当 key 不存在时才会设置值。EX
设置键的过期时间。
代码示例:
import redis
import time
r = redis.Redis()
def acquire_lock(lock_name, timeout=10):
lock_key = f"lock:{lock_name}"
# 使用SET命令实现加锁和设置过期时间
if r.set(lock_key, 1, nx=True, ex=timeout):
return True
return False
def release_lock(lock_name):
lock_key = f"lock:{lock_name}"
r.delete(lock_key)
# 使用
if acquire_lock("mylock"):
try:
# 执行业务逻辑
pass
finally:
release_lock("mylock")
优缺点:
- 优点:
- 原子性更强,
SET
命令直接支持设置过期时间和NX条件,避免了使用SETNX
和EXPIRE
组合的额外操作。 - 不容易出现 race condition(竞态条件)。
- 原子性更强,
- 缺点:
- 只适用于Redis 2.6及以上版本。
3. RedLock(Redis官方分布式锁方案)
实现方式:
RedLock 是由 Redis 官方提出的一种分布式锁算法,适用于多个 Redis 实例的分布式环境。
- RedLock 算法利用了多个独立的 Redis 实例进行加锁,确保当某个 Redis 实例出现故障时,其他实例仍然可以确保锁的安全性。
工作流程:
- 客户端从多个 Redis 实例中获取锁。
- 客户端对大多数实例中的锁加锁成功后认为获得了锁。
- 设置锁的过期时间,并确保锁在每个实例中超时一致。
- 解锁时需要释放所有 Redis 实例中的锁。
代码示例:
import redis
import time
class RedLock:
def __init__(self, redis_nodes):
self.redis_nodes = [redis.Redis(host=node[0], port=node[1]) for node in redis_nodes]
def acquire_lock(self, lock_name, timeout=10):
lock_key = f"lock:{lock_name}"
locks = []
for r in self.redis_nodes:
if r.set(lock_key, 1, nx=True, ex=timeout):
locks.append(r)
if len(locks) >= len(self.redis_nodes) // 2 + 1:
return True
else:
# 释放已获得的锁
for r in locks:
r.delete(lock_key)
return False
def release_lock(self, lock_name):
lock_key = f"lock:{lock_name}"
for r in self.redis_nodes:
r.delete(lock_key)
# 使用
redis_nodes = [("127.0.0.1", 6379), ("127.0.0.1", 6380), ("127.0.0.1", 6381)]
redlock = RedLock(redis_nodes)
if redlock.acquire_lock("mylock"):
try:
# 执行业务逻辑
pass
finally:
redlock.release_lock("mylock")
优缺点:
- 优点:
- 高可用性,适用于分布式环境,故障容忍度高。
- 提供了一种基于多个节点的锁实现,提高了系统的可靠性。
- 缺点:
- 实现复杂。
- 对Redis的依赖较大。
4. Lua脚本(原子性加锁与释放)
实现方式:
通过Redis的Lua脚本实现加锁和释放的原子操作,保证在执行过程中不会出现竞态条件。
- 使用Redis的
EVAL
命令执行Lua脚本,可以将加锁和解锁操作封装成一个原子操作。
代码示例:
import redis
r = redis.Redis()
def acquire_lock(lock_name, timeout=10):
lock_key = f"lock:{lock_name}"
lock_script = """
if redis.call("exists", KEYS[1]) == 0 then
redis.call("set", KEYS[1], ARGV[1], "ex", ARGV[2])
return 1
else
return 0
end
"""
# 使用Lua脚本加锁
return r.eval(lock_script, 1, lock_key, 1, timeout)
def release_lock(lock_name):
lock_key = f"lock:{lock_name}"
r.delete(lock_key)
# 使用
if acquire_lock("mylock"):
try:
# 执行业务逻辑
pass
finally:
release_lock("mylock")
优缺点:
- 优点:
- 原子性强,避免了多个操作之间的竞态条件。
- 适用于复杂的分布式锁场景。
- 缺点:
- Lua脚本可能影响Redis的性能,尤其是在大量并发请求下。
5. RedLock 的延时过期锁(Redisson方案)
实现方式:
基于 RedLock 算法的实现,不同的是会为每个锁设置随机的过期时间,以防止不同客户端争抢锁的情况。
6. Zookeeper 实现的分布式锁
虽然 Zookeeper 不属于 Redis,但它是一种常见的分布式锁实现,可以和 Redis 配合使用,在需要更高一致性的场景中使用。
7. Redis + 客户端控制锁的超时机制
通过 Redis 和客户端代码配合实现超时机制。客户端会定期发送心跳请求保持锁定,防止死锁。
以上是常见的七种 Redis 实现分布式锁的方案。不同方案适用于不同的场景,选择时需要根据系统的规模、容错要求、锁的粒度等进行权衡。
发表回复