在分布式系统中,分布式锁是一种非常重要的机制,它能够保证多个进程、线程或机器之间对共享资源的互斥访问。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 实现分布式锁的方案。不同方案适用于不同的场景,选择时需要根据系统的规模、容错要求、锁的粒度等进行权衡。
发表回复