在分布式系统架构中,多个独立进程对共享资源的并发访问控制是常见需求,分布式锁作为解决这一问题的关键技术,在缓存更新、任务调度、库存管理等场景中发挥着重要作用。本文将从基础原理出发,详细阐述基于 Redis 的分布式锁实现方案,包括单实例模式与 Redlock 算法,并探讨其在实际应用中的关键考量。
分布式锁是一种跨进程、跨机器的同步机制,用于保证多个分布式节点对共享资源的互斥访问。一个可靠的分布式锁需要满足以下核心特性:
单实例 Redis 实现分布式锁是最基础的方案,利用 Redis 的原子操作特性可以简洁地实现锁机制,适合对可靠性要求不高的场景。
使用SET
命令的扩展参数实现原子性的锁获取操作:
redis
SET resource_name unique_value NX PX 30000
参数说明:
resource_name
:锁的标识,对应具体的共享资源unique_value
:客户端生成的唯一值,用于标识锁的持有者NX
:仅当键不存在时才执行设置操作,保证互斥性PX 30000
:设置锁的自动过期时间为 30000 毫秒(30 秒),避免死锁返回结果:
OK
,表示锁获取成功nil
,表示锁已被其他客户端持有释放锁必须保证原子性和安全性,需通过 Lua 脚本实现 "检查 - 删除" 的原子操作:
lua
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
执行方式(以 Python 为例):
python
import redis
import uuid
class RedisLock:
def __init__(self, redis_client, resource_name, expire=30000):
self.redis = redis_client
self.resource_name = resource_name
self.expire = expire
self.unique_value = str(uuid.uuid4()) # 生成唯一标识
self.script = self.redis.register_script("""
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
""")
def acquire(self):
"""获取锁"""
return self.redis.set(
self.resource_name,
self.unique_value,
nx=True,
px=self.expire
) is not None
def release(self):
"""释放锁"""
return self.script(keys=[self.resource_name], args=[self.unique_value]) == 1
单实例 Redis 分布式锁存在明显的单点故障风险:当 Redis 主节点宕机时,若从节点尚未同步锁数据,从节点升级为主节点后,可能导致新的客户端获取到相同的锁,破坏互斥性。因此,该方案仅适用于对一致性要求不高的场景。
Redlock 算法是为解决单实例 Redis 分布式锁可靠性问题而设计的分布式锁方案,通过多个独立的 Redis 实例实现高可用的锁服务。
Redlock 算法基于 N 个完全独立的 Redis 实例(通常 N=5),客户端需要在超过半数(N/2+1)的实例上获取锁,才能认为锁获取成功。这种设计可以避免单点故障导致的锁服务失效。
向所有 Redis 实例发送锁释放请求(无论是否成功获取该实例的锁),释放操作同样通过 Lua 脚本实现,确保只删除客户端自身持有的锁。
python
import redis
import time
import uuid
from typing import List, Tuple
class Redlock:
def __init__(self, redis_instances: List[redis.Redis], lock_ttl: int = 30000):
self.redis_instances = redis_instances
self.lock_ttl = lock_ttl # 锁的默认有效时间(毫秒)
self.unique_value = str(uuid.uuid4())
self.acquired_locks = [] # 记录成功获取锁的实例
def acquire(self) -> Tuple[bool, int]:
start_time = int(time.time() * 1000)
success_count = 0
# 向所有实例尝试获取锁
for instance in self.redis_instances:
try:
# 设置锁,超时时间50ms
result = instance.set(
self.resource_name,
self.unique_value,
nx=True,
px=self.lock_ttl,
socket_timeout=0.05
)
if result:
success_count += 1
self.acquired_locks.append(instance)
except:
continue
# 计算总耗时
end_time = int(time.time() * 1000)
elapsed_time = end_time - start_time
# 验证是否满足多数原则且未超时
if success_count >= (len(self.redis_instances) // 2 + 1) and elapsed_time < self.lock_ttl:
valid_time = self.lock_ttl - elapsed_time
return True, valid_time
else:
# 释放已获取的锁
self.release()
return False, 0
def release(self):
# 释放锁的Lua脚本
script = """
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
"""
for instance in self.redis_instances:
try:
instance.eval(script, 1, self.resource_name, self.unique_value)
except:
continue
def set_resource(self, resource_name: str):
self.resource_name = resource_name
锁超时时间设置:需根据业务处理时间合理设置,过短可能导致任务未完成锁已释放,过长则会降低系统并发度
随机值生成:应保证唯一性,可使用 UUID 或 "客户端 ID + 时间戳 + 随机数" 的组合方式
Redis 实例部署:Redlock 算法要求 Redis 实例完全独立,建议部署在不同物理机或虚拟机,避免因基础设施故障导致多实例同时失效
重试机制:锁获取失败后,应采用随机延迟重试策略,避免多个客户端同时竞争导致的活锁
持久化配置:建议开启 AOF 持久化并配置fsync=always
,或确保实例重启后有足够长的隔离时间(超过锁的最大有效时间)
锁扩展机制:对于长时间运行的任务,可实现锁扩展机制,在锁过期前向多数实例发送锁延长请求
Redis 分布式锁是解决分布式系统资源竞争的有效方案,单实例实现简单但存在单点风险,适合对可靠性要求不高的场景;Redlock 算法通过多实例部署提供了更高的可靠性,适用于对一致性要求严格的业务场景。
在实际应用中,需根据业务特性选择合适的实现方案,并重点关注锁的安全性、有效性和容错性设计。目前已有多个成熟的开源库实现了 Redlock 算法(如 Redisson、redlock-py 等),可根据技术栈选择使用。
如果本文对你有帮助,别忘了点赞收藏,关注我,一起探索更高效的开发方式~