跳到主要内容

Redis分布式锁

· 阅读需 5 分钟

很多人在刚接触分布式系统的时候,都会遇到一个问题:
多个服务实例同时处理同一件事情,如何避免数据被重复处理?

例如:

  • 用户抢优惠券
  • 定时任务执行
  • 库存扣减
  • 订单状态更新

如果系统只有一个进程,其实很简单,用 本地锁(mutex) 就能解决。但在微服务架构或者集群部署之后,问题就变了:系统可能有 10 个服务实例、100 个 Worker,甚至多个数据中心。这时候本地锁就完全失效了,因为不同进程之间根本不知道彼此的锁状态。

于是就出现了 分布式锁(Distributed Lock) 这个概念。它的目标很简单:

在分布式环境下,保证某一时刻只有一个节点能执行某段逻辑。

为什么 Redis 可以做分布式锁?

实现分布式锁的时候,很多人第一反应是数据库,例如:

-- 借助数据库行锁实现互斥
select ... for update

但数据库锁的问题是:性能差、锁粒度大、并发高时压力很大。于是大家开始寻找一个更适合做锁的系统,Redis 就非常合适,因为它天然具备三个优势。

1)单线程模型

Redis 核心命令是单线程执行的,同一时间只有一个命令在执行。所以像下面这种操作:

# NX:key 不存在才写入,天然互斥
SET key value NX

绝对原子操作,不会出现竞争条件。

2)内存操作,速度极快

Redis 所有数据都在内存中,一次锁操作通常只需要一次 SET、一次 DEL,延迟通常在 几十微秒级别,远远比数据库快。

3)支持自动过期

Redis key 可以设置 TTL,例如:

# NX:不存在才创建;EX 10:10 秒自动过期
SET lock:order 123 NX EX 10

这样即使服务崩溃,锁也不会永远卡住。

Redis 锁的释放

释放锁时不能直接 DEL,因为可能发生这种情况:

线程A获取锁
线程A执行超时
锁过期
线程B获取锁
线程A执行完删除锁

这时候线程 A 会误删线程 B 的锁。正确做法是 只删除自己持有的锁,通常通过 Lua 脚本实现:

-- 先比对锁的值(唯一标识),匹配才删除,保证只释放自己的锁
if redis.call("GET",KEYS[1]) == ARGV[1] then
return redis.call("DEL",KEYS[1])
else
return 0
end

Lua 脚本在 Redis 中整体原子执行,GET 和 DEL 之间不会被其他命令插入,这样才能保证安全释放。

Redis 分布式锁的几个经典问题

虽然 Redis 锁很简单,但在真实系统中会遇到很多坑,最常见的是下面两个。

1)锁过期问题

如果业务执行时间超过 TTL,锁就会提前释放,其他节点就可能同时进入。解决办法是 自动续期(WatchDog)。例如:

  • 锁的 TTL 设为 10 秒
  • 在剩余有效期进入最后 30% 时自动续期一次,并加入随机抖动,避免大量续期请求在同一时刻集中打到 Redis

只要任务还在执行,锁就不会过期。

2)Redis 单点问题

如果 Redis 挂掉,所有锁都会失效。常见解决方案是:

  • Redis Sentinel
  • Redis Cluster
  • RedLock 算法

Redis AP 设计与 etcd、ZooKeeper CP 架构的区别

Redis 本身是一个偏 AP(可用性优先) 的系统:在网络分区或者节点故障的时候,Redis 会优先保证服务可用,而不是保证数据绝对一致。这就可能带来一个风险——极端情况下,可能同时出现两个客户端都认为自己拿到了锁

例如:

客户端 A 在主节点获取了锁
主节点还没来得及同步到从节点
主节点突然宕机
从节点被提升为新的主节点

这时候新的主节点并不知道 A 已经加过锁,客户端 B 就可能再次获取锁,出现两个客户端同时持有锁的情况。这就是 Redis 分布式锁在理论上的一致性风险。

相比之下,像 etcd 或 ZooKeeper 这样的系统是基于 CP 模型(强一致) 设计的,内部通过 Raft 或 ZAB 协议 保证多数节点确认之后才会提交数据。因此一旦锁被获取成功,整个集群都会达成一致,不会出现多个客户端同时拿到锁的情况。

所以在工程实践中通常有一个经验:业务级锁(例如库存、任务控制) 用 Redis 分布式锁通常已经足够;但 强一致要求很高的场景——金融交易、全局调度系统、分布式协调服务——很多系统会选择 etcd / ZooKeeper 这种强一致的锁实现。

小结

Redis 分布式锁的核心思想非常简单:利用 Redis 原子操作实现互斥。它包含三个关键点:原子加锁、安全释放、自动过期或续期。在实际工程中,一套成熟的 Redis 锁通常还会加上重试机制、WatchDog 自动续期、Lua 安全释放和锁唯一标识。同时也要清楚它偏 AP 的定位——业务级互斥够用,强一致场景则考虑 etcd / ZooKeeper。这些设计组合在一起,才能真正成为一个稳定可靠的分布式锁系统。

评论 / COMMENTS