{T}

分布式锁有哪些应用场景和实现?

0. 引言

在单机多线程中,我们用 synchronized/ReentrantLock 保证互斥;服务拆成多实例后,锁必须跨进程、跨机器生效——这就是分布式锁。它的本质是:在共享存储(数据库/Redis/ZooKeeper/etcd)上达成一个"谁拿到、谁释放"的互斥约定。本文梳理应用场景、四种主流实现方案与选型标准。

1. 应用场景

场景问题锁的作用
秒杀/库存扣减多实例并发扣减库存,超卖同一商品的扣减操作串行化
定时任务多实例同时执行同一定时任务,重复处理全局只允许一个实例执行
分布式缓存重建缓存击穿时多实例同时查库重建只允许一个实例回源重建
资源幂等操作重复支付回调、重复下单同一业务 ID 只处理一次
分布式组件协调选主、分片分配、全局 ID 生成互斥选出一个执行者

判断是否真需要分布式锁的标准:多个进程并发操作同一共享资源,且不允许并发。若可通过"唯一约束 + 幂等 + 乐观锁"解决,优先用数据库能力,而不是引入锁服务。

2. 四种实现方案对比

方案实现方式优点缺点
数据库SELECT ... FOR UPDATE 行锁 / 唯一索引 + 插入记录简单可靠、无额外组件性能差(行锁 + 连接开销)、锁无超时易死锁、DB 压力大
RedisSET key value NX EX + Lua 释放性能最高(微秒级)、实现简单锁自动过期有边界问题、主从切换可能丢锁
ZooKeeper临时顺序节点 + Watch可靠(会话级自动释放)、无死锁、公平(FIFO)性能低于 Redis(毫秒级)、需维护 ZK 集群
etcdLease + Revision + Watchconcurrency 包)强一致(Raft)、TTL 可续租、Java/Go SDK 成熟性能低于 Redis、K8s 生态绑定

3. 数据库方案速览

sql
-- 方案 A:悲观锁(行锁)
BEGIN;
SELECT * FROM inventory WHERE id = 1 FOR UPDATE;
-- 业务处理...
UPDATE inventory SET stock = stock - 1 WHERE id = 1;
COMMIT;

-- 方案 B:唯一索引 + 插入(乐观)——锁表 lock_table(lock_name 唯一, expire_time)
INSERT INTO lock_table(lock_name, owner, expire_time) VALUES ('order_lock', 'svc-1', ?);
-- 成功 = 加锁;删除 = 释放;更新 expire_time = 续期
  • 方案 A 简单但锁粒度=行锁,长事务拖垮库;方案 B 无锁但需处理过期清理重入
  • 适用:低并发、无额外中间件的轻量场景。

4. Redis 方案(详见下章)

bash
# 加锁(原子:NX + EX)
SET lock:order:1001 uuid-xxx NX EX 30
# 释放(Lua 保证"只释放自己的锁")
if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end

核心要点:唯一标识(value=uuid)防误删、EX 自动过期防死锁、Lua 原子释放、续期防业务超时——详见《如何使用 Redis 快速实现分布式锁?》。

5. ZooKeeper 方案:临时顺序节点

text
加锁:在 /locks/order 下创建临时顺序节点 /locks/order/lock_0000000001
判断:自己是序号最小的节点 → 获得锁
等待:不是最小 → 监听自己前一个节点的删除事件(Watch)
释放:删除节点 / 会话断开(临时节点自动删除)
图表渲染中…
  • 临时节点 + 会话:客户端崩溃时 ZK 自动删除节点,天然防死锁
  • 顺序节点 + Watch:天然公平(FIFO),且避免"惊群"(只盯前一个节点);
  • 缺点:加锁/释放各需 1-2 次 RPC,毫秒级延迟;ZK 本身要保证多数派可用。

6. etcd 方案:Lease + Revision

go
// Go 示例(etcd concurrency 包)
lock, err := concurrency.NewMutex(client, "/orders/1001")
lock.Lock(ctx)      // 内部:创建 Lease + Revision 排序 + Watch
defer lock.Unlock() // 释放 Lease
  • 基于 Raft 强一致:锁状态不会因主从切换丢失(对比 Redis 主从场景);
  • Lease 续租:业务未结束可续租,避免"锁过期但业务还在跑";
  • K8s/云原生环境首选(etcd 常已存在,零新增组件)。

7. 选型建议

text
性能优先、可容忍极端边界(锁丢失) → Redis(Redisson 封装)
强一致、防死锁、公平锁 → ZooKeeper / etcd
云原生环境、已有 etcd → etcd concurrency
极简场景、无中间件 → 数据库乐观/悲观锁

高频面试追问:Redis 锁与 ZK 锁的可靠性差异——Redis 锁在"主节点宕机、锁数据未同步到从节点"时可能同时有两个客户端持锁(边界场景);ZK/etcd 靠多数派共识无此问题。极端安全场景(扣款、发券)选 ZK/etcd,常规防重场景 Redis 足够。

8. 小结

  • 分布式锁解决跨进程互斥:秒杀、定时任务、缓存重建、幂等防重;
  • 四方案:数据库(最简)、Redis(最快)、ZooKeeper(最可靠防死锁)、etcd(云原生强一致);
  • 选型三维度:性能、一致性、运维成本
  • 通用要求:唯一标识防误删、超时释放防死锁、可续期、可重入

下一章实战 Redis 分布式锁:SET NX EX 演进史、Redisson 看门狗与 RedLock 争议。