redis如何实现分布式锁?

239 阅读4分钟

知其然要知其所以然,探索每一个知识点背后的意义,你知道的越多,你不知道的越多,一起学习,一起进步,如果文章感觉对您有用的话,关注、收藏、点赞,有困惑的地方请评论,我们一起交流!


Redis 实现分布式锁的核心目标是 在分布式系统中确保对共享资源的互斥访问。其设计需解决 锁竞争死锁预防容错性 等关键问题。以下是 Redis 分布式锁的完整实现方案及深度分析:

一、基础实现:SETNX + 过期时间

1. 加锁

SET lock_key unique_value NX PX 30000
  • NX:仅当键不存在时设置成功(原子性竞争锁)。
  • PX 30000:锁自动过期时间(毫秒),避免死锁。
  • unique_value:客户端唯一标识(如 UUID),用于安全释放锁。

2. 解锁(Lua 脚本保证原子性)

if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end
  • 脚本作用:只有锁的持有者才能释放锁,避免误删其他客户端的锁。

3. 风险与缺陷

  • 锁过期但业务未完成:锁自动释放后,其他客户端可能获取锁,导致并发问题。
  • 单点故障:单节点 Redis 崩溃时锁失效。

二、高可用方案:Redlock 算法

Redis 官方推荐的分布式锁算法,适用于多节点 Redis 集群(如 Redis Sentinel 或 Cluster)。

1. 加锁流程

  1. 获取当前时间(T1)。
  2. 依次向 N 个独立 Redis 节点请求加锁(使用相同的 Key 和随机值),每个请求设置超时(远小于锁过期时间)。
  3. 计算加锁总耗时(T2 - T1),仅当在多数节点(≥ N/2 + 1)加锁成功且总耗时小于锁过期时间时,认为加锁成功。
  4. 锁的实际有效时间 = 初始过期时间 - 加锁总耗时。

2. 解锁流程

向所有节点发送 Lua 解锁脚本(同基础实现)。

3. 配置建议

  • 节点数量:至少 5 个(允许最多 2 个节点故障)。
  • 锁过期时间:根据业务操作时间调整(如 10~30 秒)。

4. Redlock 争议点

  • 时钟漂移问题:若节点时钟不同步,可能导致锁提前失效。
  • 性能开销:需与多个节点交互,延迟较高。

三、生产环境最佳实践

1. 锁续期(Watchdog)

  • 问题:业务执行时间可能超过锁过期时间。
  • 解决方案:启动后台线程定期检查并延长锁持有时间(如每 10 秒续期一次)。
    // Java 示例(Redisson 实现)
    RLock lock = redisson.getLock("lock_key");
    lock.lock(30, TimeUnit.SECONDS);  // 自动续期
    

2. 避免锁冲突

  • 分段锁:将大 Key 拆分为多个小 Key(如 lock:1lock:2),降低竞争概率。
  • 非阻塞尝试
    SET lock_key unique_value NX PX 10000
    
    若失败,可退避重试或直接放弃。

3. 容灾与监控

  • Redis 节点高可用:使用 Sentinel 或 Cluster 模式。
  • 锁状态监控
    # 检查锁是否存在及剩余生存时间
    TTL lock_key
    

四、与其他分布式锁方案的对比

方案优点缺点适用场景
Redis 单节点锁实现简单,性能高(毫秒级)单点故障风险非关键业务,允许短暂不一致
Redlock高可用,多数节点存活即可工作实现复杂,性能较低(约 100ms)金融交易、库存扣减等强一致场景
ZooKeeper 锁强一致性,无过期时间问题性能差(百毫秒级),依赖 ZK 集群对一致性要求极高的系统
数据库乐观锁/悲观锁/唯一索引无需额外组件高并发下性能骤降低并发、已有数据库集成,没有引入redis、zk的场景

五、代码示例(Python + Redlock)

from redlock import RedLock

# 加锁
lock = RedLock("resource_name", ttl=30000)  # ttl 单位毫秒
if lock.acquire():
    try:
        # 执行业务逻辑
        print("Do something...")
    finally:
        # 释放锁
        lock.release()
else:
    print("Failed to acquire lock")

六、关键问题与解决方案

1. 锁被其他客户端释放

  • 原因:客户端 A 的锁过期后,客户端 B 获取锁,此时 A 仍可能误删 B 的锁。
  • 解决:解锁时验证 unique_value(如 Lua 脚本)。

2. 锁永久失效

  • 原因:Redis 崩溃且未持久化,锁信息丢失。
  • 解决:使用 Redlock 多节点部署,或切换为 ZooKeeper/etcd。

3. 业务执行时间不确定

  • 解决:动态调整锁过期时间 + Watchdog 续期。

七、总结

  • 简单场景:单节点 SETNX + 过期时间 + Lua 解锁 即可。
  • 高可用场景:使用 Redlock 算法,但需权衡性能与一致性。
  • 终极方案:对于强一致性需求,可结合 ZooKeeper数据库事务

Redis 分布式锁的核心在于 互斥性容错性避免死锁,合理选择方案需根据业务对性能与一致性的要求。