分布式锁有哪些应用场景和实现?
0. 引言
在单机多线程中,我们用 synchronized/ReentrantLock 保证互斥;服务拆成多实例后,锁必须跨进程、跨机器生效——这就是分布式锁。它的本质是:在共享存储(数据库/Redis/ZooKeeper/etcd)上达成一个"谁拿到、谁释放"的互斥约定。本文梳理应用场景、四种主流实现方案与选型标准。
1. 应用场景
| 场景 | 问题 | 锁的作用 |
|---|---|---|
| 秒杀/库存扣减 | 多实例并发扣减库存,超卖 | 同一商品的扣减操作串行化 |
| 定时任务 | 多实例同时执行同一定时任务,重复处理 | 全局只允许一个实例执行 |
| 分布式缓存重建 | 缓存击穿时多实例同时查库重建 | 只允许一个实例回源重建 |
| 资源幂等操作 | 重复支付回调、重复下单 | 同一业务 ID 只处理一次 |
| 分布式组件协调 | 选主、分片分配、全局 ID 生成 | 互斥选出一个执行者 |
判断是否真需要分布式锁的标准:多个进程并发操作同一共享资源,且不允许并发。若可通过"唯一约束 + 幂等 + 乐观锁"解决,优先用数据库能力,而不是引入锁服务。
2. 四种实现方案对比
| 方案 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
| 数据库 | SELECT ... FOR UPDATE 行锁 / 唯一索引 + 插入记录 | 简单可靠、无额外组件 | 性能差(行锁 + 连接开销)、锁无超时易死锁、DB 压力大 |
| Redis | SET key value NX EX + Lua 释放 | 性能最高(微秒级)、实现简单 | 锁自动过期有边界问题、主从切换可能丢锁 |
| ZooKeeper | 临时顺序节点 + Watch | 可靠(会话级自动释放)、无死锁、公平(FIFO) | 性能低于 Redis(毫秒级)、需维护 ZK 集群 |
| etcd | Lease + Revision + Watch(concurrency 包) | 强一致(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 争议。