当前位置:首页 > 问答 > 正文

分布式系统 高可用架构 基于Redis的高可用分布式锁实现方法与原理

用Redis实现高可用锁的实战指南

场景引入:电商秒杀引发的血案

去年双十一,我们团队负责的秒杀系统差点酿成重大事故,当时有个爆款商品限量1000件,活动开始瞬间涌入50万请求,由于锁机制设计有缺陷,最终超卖了200多件——这要是发生在金融系统,可能就是几百万的损失了。

事后排查发现,问题出在简单的单机锁根本无法应对分布式环境,今天我就来聊聊如何用Redis构建一个真正可靠的高可用分布式锁,这些都是我们用真金白银买来的经验。

分布式锁的四大核心要求

在动手写代码前,我们必须明确一个好的分布式锁应该具备哪些特性:

  1. 互斥性:同一时刻只能有一个客户端持有锁
  2. 防死锁:即使客户端崩溃,锁也能自动释放
  3. 高可用:Redis集群部分节点宕机不影响锁服务
  4. 高性能:加锁/解锁操作必须足够轻量

基础版:SETNX实现的朴素锁

最直观的做法是利用Redis的SETNX命令(SET if Not eXists):

# 加锁
SETNX lock_key unique_client_id
EXPIRE lock_key 30  # 设置过期时间防止死锁
# 解锁
if GET lock_key == unique_client_id:
    DEL lock_key

这个方案有两个致命缺陷:

  1. SETNX和EXPIRE不是原子操作,中间可能崩溃导致死锁
  2. 解锁时检查值和删除操作不是原子的,可能误删别人的锁

进阶版:Redis 2.8+的官方推荐方案

Redis 2.8版本后,我们可以用更优雅的方式实现:

分布式系统 高可用架构 基于Redis的高可用分布式锁实现方法与原理

# 原子性加锁
SET lock_key unique_client_id NX PX 30000
# 使用Lua脚本保证原子性解锁
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

这里的关键改进:

  • NX表示只在键不存在时设置
  • PX自动设置毫秒级过期时间
  • 解锁使用Lua脚本保证原子性

高可用方案:Redlock算法

当我们需要跨多个Redis节点时,Redis作者提出了Redlock算法,假设有5个独立的Redis主节点(不是集群模式):

  1. 获取当前时间戳T1
  2. 依次向所有节点发送加锁请求(包含相同的key和value,以及较短的超时时间)
  3. 当从至少3个节点获得锁,且总耗时小于锁的有效时间时,认为加锁成功
  4. 锁的实际有效时间 = 初始有效时间 - 获取锁的总耗时
  5. 如果加锁失败,需要向所有节点发起解锁请求
def acquire_lock():
    start_time = time.time()
    success_count = 0
    for node in redis_nodes:
        try:
            if node.set(lock_key, unique_id, nx=True, px=30000):
                success_count += 1
        except:
            continue
    elapsed = time.time() - start_time
    if success_count >= 3 and elapsed < 30000:
        return True
    else:
        release_lock()  # 失败时要清理
        return False

锁续约机制:应对长耗时任务

有时候业务处理时间可能超过锁的初始有效期,这时需要"看门狗"机制:

def watchdog():
    while locking:
        # 每隔10秒续约一次
        redis.expire(lock_key, 30)
        time.sleep(10)
# 业务线程
threading.Thread(target=watchdog, daemon=True).start()

注意:续约前要再次验证自己还是锁的持有者,防止续错了锁。

避坑指南:我们踩过的那些雷

  1. 网络延迟陷阱:客户端认为锁已过期,但Redis服务器上还未过期,解决方案是给锁设置足够缓冲时间,或者使用fencing token机制。

  2. GC停顿灾难:Java应用的GC停顿可能导致锁过期而不自知,建议监控GC情况,适当延长超时时间。

    分布式系统 高可用架构 基于Redis的高可用分布式锁实现方法与原理

  3. 时钟漂移问题:不同服务器时钟不同步会影响Redlock的正确性,建议使用NTP服务保持时钟同步。

  4. 误删锁风险:一定要用唯一client ID作为value,并在删除前验证。

性能优化小技巧

  1. 锁粒度控制:库存系统可以按商品ID分片加锁,而不是全局大锁
  2. 非阻塞尝试:使用SET key value NX PX timeout的返回值判断是否获得锁,避免轮询
  3. 本地缓存:对热点资源可以在本地缓存中维护二级锁状态,减少Redis访问
  4. 适当超时:根据业务特点设置合理的超时时间,太短会导致频繁重试,太长会影响可用性

终极方案:专业级实现推荐

对于生产环境,我建议直接使用这些成熟方案:

  • Redisson:Java生态中最完善的Redis客户端,内置多种分布式锁实现
  • Redis官方文档提供的分布式锁模式
  • Curator:Zookeeper实现的分布式锁(适合已经使用ZK的系统)

分布式锁看似简单,实则暗藏玄机,我们团队从最初的SETNX一路升级到现在的Redlock+续约机制,中间踩过的坑比解决的问题还多,没有放之四海而皆准的方案,一定要根据你的业务特点(并发量、容忍度、响应要求)选择合适的实现。

下次当你准备写分布式锁时,不妨先问问自己:这个锁真的有必要吗?很多时候,通过设计避免竞争(比如使用消息队列),比用锁解决竞争要优雅得多。

发表评论