编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • 性能优化实践

  • 程序编程原理

  • 稳定性与可靠性

  • 工程化与运维

  • 方案设计思想

    • README
    • 架构与组件

    • 数据与存储

    • 通信与协议

    • 稳定性与安全

      • 幂等性设计方案
      • 分布式锁方案设计
        • 01.超卖 1200 件
          • 1.1 秒杀的灾难
          • 1.2 故障扩散链路
          • 1.3 反思分布式锁
        • 02.要解决的核心矛盾
          • 2.1 单机锁失效
          • 2.2 性能与正确
          • 2.3 可用与一致
          • 2.4 分布式锁的本质
        • 03.业界主流方案
          • 03.1 三大主流方案
          • 03.2 横向对比矩阵
          • 03.3 Redlock 争议
        • 04.设计核心原则
          • 04.1 互斥性原则
          • 04.2 防死锁原则
          • 04.3 锁主对应原则
          • 04.4 可重入原则
        • 05.方案落地实战
          • 05.1 整体架构
          • 05.2 Redis 分布式锁
          • 05.3 Zookeeper 锁
          • 05.4 etcd 锁
          • 05.5 Redisson 实战
        • 06.关键问题解决
          • 06.1 锁超时问题
          • 06.2 锁续期问题
          • 06.3 锁粒度问题
        • 07.常见陷阱与反例
          • 07.1 误删别人锁
          • 07.2 锁未释放
          • 07.3 滥用分布式锁
        • 08.演进路线
          • 08.1 V1 数据库锁
          • 08.2 V2 Redis 锁
          • 08.3 V3 高可用锁集群
        • 09.总结与决策
          • 09.1 上线检查表
          • 09.2 选型决策树
      • 监控告警方案设计
      • 终端设备鉴权设计
      • 移动端防抓包实践
    • 端侧专项性

    • 研发的效能

  • 专栏
  • 方案设计思想
  • 稳定性与安全
杨充
2026-05-21
目录

分布式锁方案设计

# 18.分布式锁方案设计

本篇定位:分布式锁是分布式系统的"互斥保险丝"——多节点要协调访问共享资源时离不开它。本文从一次"超卖 1200 件"的秒杀事故讲起,回答三个核心问题——为什么需要分布式锁?业界三大方案怎么选?怎么避开分布式锁的"九大坑"?

# 目录介绍

  • 01.超卖 1200 件
    • 1.1 秒杀的灾难
    • 1.2 故障扩散链路
    • 1.3 反思分布式锁
  • 02.要解决的核心矛盾
    • 2.1 单机锁失效
    • 2.2 性能与正确
    • 2.3 可用与一致
    • 2.4 分布式锁的本质
  • 03.业界主流方案
    • 03.1 三大主流方案
    • 03.2 横向对比矩阵
    • 03.3 Redlock 争议
  • 04.设计核心原则
    • 04.1 互斥性原则
    • 04.2 防死锁原则
    • 04.3 锁主对应原则
    • 04.4 可重入原则
  • 05.方案落地实战
    • 05.1 整体架构
    • 05.2 Redis 分布式锁
    • 05.3 Zookeeper 锁
    • 05.4 etcd 锁
    • 05.5 Redisson 实战
  • 06.关键问题解决
    • 06.1 锁超时问题
    • 06.2 锁续期问题
    • 06.3 锁粒度问题
  • 07.常见陷阱与反例
    • 07.1 误删别人锁
    • 07.2 锁未释放
    • 07.3 滥用分布式锁
  • 08.演进路线
    • 08.1 V1 数据库锁
    • 08.2 V2 Redis 锁
    • 08.3 V3 高可用锁集群
  • 09.总结与决策
    • 09.1 上线检查表
    • 09.2 选型决策树

# 01.超卖 1200 件

# 1.1 秒杀的灾难

某商城做"iPhone 抢购",库存 1000 台,结果卖出去 2200 台——超卖 1200 台,单台亏 4000 元,直接损失 480 万元。

sequenceDiagram
    participant U1 as 用户 A
    participant U2 as 用户 B
    participant S1 as 应用节点 1
    participant S2 as 应用节点 2
    participant DB as 数据库
    
    par 同时下单
        U1->>S1: 抢购
        S1->>DB: SELECT stock<br/>结果: 1
        S1->>S1: synchronized 锁本地
        S1->>S1: 检查 stock > 0
        S1->>DB: UPDATE stock = 0
    and
        U2->>S2: 抢购
        S2->>DB: SELECT stock<br/>结果: 1
        S2->>S2: synchronized 锁本地
        S2->>S2: 检查 stock > 0
        S2->>DB: UPDATE stock = 0
    end
    
    Note over DB: 库存被扣 2 次<br/>但实际只有 1 件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

根因:开发用了 synchronized 做并发控制——单机内有效,但集群部署后每个节点各自有自己的锁,根本不互斥。

# 1.2 故障扩散链路

flowchart TD
    A[业务部署 5 个节点] --> B[每个节点 synchronized 各管各的]
    B --> C[5 个节点同时通过库存校验]
    C --> D[5 个节点同时扣库存]
    D --> E[实际扣成 -1200<br/>但代码没校验为负数]
    E --> F[订单系统已生成 2200 单]
    
    Cause[根因] --> R1[用了 JVM 进程内锁]
    Cause --> R2[没意识到分布式]
    Cause --> R3[DB 没用乐观锁<br/>UPDATE WHERE stock>0]
    Cause --> R4[订单生成与扣库存非原子]
    
    style E fill:#ffebee
    style F fill:#ffebee
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 1.3 反思分布式锁

事后这个团队总结了三个最深刻的教训:

  1. JVM 锁(synchronized / ReentrantLock)只在单进程内有效
  2. 多节点部署一定要用分布式锁——这是底线
  3. 业务正确性"第二道防线"很重要——库存扣减用 UPDATE stock = stock - 1 WHERE stock > 0,DB 也守一道

分布式锁的价值就是把"多机"变回"单机"——让 N 个节点像 1 个节点一样竞争资源。

# 02.要解决的核心矛盾

# 2.1 单机锁失效

graph LR
    subgraph "单进程 synchronized 有效"
        T1[线程 1] --> Lock1[JVM Monitor]
        T2[线程 2] --> Lock1
        T3[线程 3] --> Lock1
    end
    
    subgraph "多进程 各自管自己"
        P1[进程 1] --> Lock2[Monitor 1]
        P2[进程 2] --> Lock3[Monitor 2]
        P3[进程 3] --> Lock4[Monitor 3]
        Lock2 -.无关.- Lock3
        Lock3 -.无关.- Lock4
    end
    
    style Lock1 fill:#e8f5e8
    style Lock2 fill:#ffebee
    style Lock3 fill:#ffebee
    style Lock4 fill:#ffebee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

核心:分布式锁要把锁状态放在所有进程都能访问的"第三方"——Redis、Zookeeper、etcd、DB 都行。

# 2.2 性能与正确

graph LR
    A[强一致<br/>Zookeeper/etcd] --> B[性能中等<br/>每次锁要走 Raft]
    A --> C[100% 正确]
    
    A2[最终一致<br/>Redis] --> B2[性能极高<br/>10w+ QPS]
    A2 --> C2[极端场景可能失效]
    
    A3[DB 锁] --> B3[性能差<br/>每次走 DB]
    A3 --> C3[正确]
    
    style B fill:#fff3e0
    style B2 fill:#e8f5e8
    style B3 fill:#ffebee
1
2
3
4
5
6
7
8
9
10
11
12
13

# 2.3 可用与一致

CAP 取舍:

锁实现 倾向 取舍
Redis 单节点 AP 高性能,挂了能重启
Redis 哨兵 AP 主备切换瞬间可能丢锁
Redlock 试图 CP 实际仍是 AP,争议大
Zookeeper CP 强一致,性能略低
etcd CP Raft 共识,云原生标配

实战经验:

  • 大多数业务(防误操作、降低重复率)→ Redis 够用
  • 金融、库存等绝不能错 → Zookeeper / etcd
  • 业务上还要有"二道防线"(DB 唯一约束、状态机)

# 2.4 分布式锁的本质

分布式锁 = 一个所有节点都看得到的"标记位"

它的核心追求是 互斥:同一时刻最多一个节点持有锁。

但它不是"绝对正确"的银弹——所以分布式锁要和业务幂等设计配合,不要把"系统正确性"完全押在锁上。

# 03.业界主流方案

# 03.1 三大主流方案

Redis(最流行) SETNX + 过期时间,单条命令搞定。配合 Redisson 等框架成熟。互联网公司 80% 选这个。

Zookeeper(最严谨) 临时顺序节点 + Watch 机制。金融系统首选。

etcd(云原生标配) 基于 Raft 的强一致 KV,K8s 生态默认。

flowchart LR
    A[DB 锁<br/>1990s] --> B[Zookeeper<br/>2008]
    B --> C[Redis 锁<br/>2010s]
    C --> D[Redlock<br/>2014]
    D --> E[etcd v3<br/>2016]
    
    A1[低性能<br/>少用] -.- A
    B1[严谨<br/>金融常用] -.- B
    C1[流行<br/>性能好] -.- C
    D1[Redis 多节点<br/>有争议] -.- D
    E1[云原生<br/>标准] -.- E
    
    style C fill:#e8f5e8
    style B fill:#fff3e0
    style E fill:#f3e5f5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 03.2 横向对比矩阵

维度 Redis Zookeeper etcd DB
一致性 AP CP CP CP
性能 ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐
客户端复杂度 中 高 中 低
死锁风险 中(依赖过期) 低(临时节点) 低(lease) 高
Watch / 通知 弱 ✅ 完美 ✅ 完美 ❌
可重入 需框架支持 需框架支持 需框架支持 自己实现
公平锁 难 ✅ 顺序节点 ✅ 难
生态 Redisson 成熟 Curator 成熟 clientv3 原生
适用场景 互联网常规 金融严谨 云原生 简单场景

# 03.3 Redlock 争议

Redis 之父 Antirez 提出的多节点锁算法 Redlock:在 N 台 Redis 上同时获取锁,多数(N/2+1)成功才算锁住。

graph TB
    Client[客户端] -->|同时申请| R1[Redis 1]
    Client --> R2[Redis 2]
    Client --> R3[Redis 3]
    Client --> R4[Redis 4]
    Client --> R5[Redis 5]
    
    Note[多数成功 ≥3 才算锁住<br/>否则全部释放]
    
    style R1 fill:#fff3e0
    style R2 fill:#fff3e0
    style R3 fill:#fff3e0
1
2
3
4
5
6
7
8
9
10
11
12

Martin Kleppmann 反驳:Redlock 在某些异常场景(GC 暂停、时钟跳变)下仍会出错。真正强一致需要 fencing token(递增序列)。

实战建议:

  • 业务能接受小概率失误 → 简单 Redis 单节点足够
  • 必须严格正确 → 直接用 Zookeeper / etcd
  • 不推荐 Redlock:复杂度上去了正确性还有争议

# 04.设计核心原则

# 04.1 互斥性原则

任何时刻最多一个客户端持有锁——这是分布式锁的灵魂。

sequenceDiagram
    participant C1 as Client 1
    participant C2 as Client 2
    participant Redis as Redis
    
    C1->>Redis: SET lock 1, NX, EX 30
    Redis-->>C1: OK
    
    C2->>Redis: SET lock 1, NX, EX 30
    Redis-->>C2: nil 失败
    
    Note over C2: 重试 / 等待 / 拒绝
1
2
3
4
5
6
7
8
9
10
11
12

核心命令:SET key value NX EX seconds——原子操作,不存在才设置 + 设置过期时间。

# 04.2 防死锁原则

铁律:锁必须有过期时间——客户端崩溃没释放锁也能自动释放。

// ✅ 正确:原子设置 + 过期
redis.set(key, value, "NX", "EX", 30)

// ❌ 错误:分两步 - 中间崩溃就死锁
redis.setnx(key, value)
redis.expire(key, 30)  // 万一崩在这一步...永久死锁
1
2
3
4
5
6

# 04.3 锁主对应原则

释放锁时必须验证"是不是自己的锁"——避免误删别人的锁。

sequenceDiagram
    participant C1 as Client 1
    participant C2 as Client 2
    participant Redis as Redis
    
    C1->>Redis: SET lock A, NX, EX 30
    Note over C1: 业务执行 35 秒(超过过期时间)
    Note over Redis: 锁过期自动释放
    C2->>Redis: SET lock B, NX, EX 30 拿到锁
    
    C1->>Redis: 业务结束 DEL lock
    Note over C1,Redis: ❌ 删的是 C2 的锁!
1
2
3
4
5
6
7
8
9
10
11
12

正确做法:用 Lua 脚本保证"判断 + 删除"原子性:

-- 释放锁的 Lua 脚本
if redis.call('GET', KEYS[1]) == ARGV[1] then
    return redis.call('DEL', KEYS[1])
else
    return 0
end
1
2
3
4
5
6

加锁时 value 用 UUID(每个客户端唯一),释放时校验 value 一致才删。

# 04.4 可重入原则

同一个客户端能否多次获取同一把锁?

graph LR
    A[业务 A 持有锁] --> B[业务 A 调用业务 B]
    B --> C[业务 B 也想获取同一把锁]
    C --> D{可重入?}
    
    D -->|是| Allow[允许 计数+1]
    D -->|否| Block[阻塞 → 死锁]
    
    style Allow fill:#e8f5e8
    style Block fill:#ffebee
1
2
3
4
5
6
7
8
9
10

实现:value 里存 "客户端 ID + 重入次数",每次加锁判断。Redisson 默认就实现了可重入。

# 05.方案落地实战

# 05.1 整体架构

graph TB
    subgraph "客户端"
        App1[App 1]
        App2[App 2]
        AppN[App N]
    end
    
    subgraph "锁服务"
        LockSvc[锁中间件<br/>Redis/ZK/etcd]
    end
    
    subgraph "保险层"
        DB[(DB 唯一约束)]
        State[状态机]
        Idem[幂等键]
    end
    
    App1 & App2 & AppN -->|加锁/释放| LockSvc
    App1 & App2 & AppN -->|二道防线| DB
    App1 & App2 & AppN -->|二道防线| State
    App1 & App2 & AppN -->|二道防线| Idem
    
    style LockSvc fill:#fff3e0
    style DB fill:#e8f5e8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 05.2 Redis 分布式锁

完整实现:

class RedisLock(private val redis: Jedis, private val key: String) {
    private val value = UUID.randomUUID().toString()
    
    fun tryLock(expireSec: Long = 30): Boolean {
        val result = redis.set(key, value, SetParams().nx().ex(expireSec))
        return "OK" == result
    }
    
    fun unlock(): Boolean {
        val script = """
            if redis.call('GET', KEYS[1]) == ARGV[1] then
                return redis.call('DEL', KEYS[1])
            else
                return 0
            end
        """.trimIndent()
        val result = redis.eval(script, listOf(key), listOf(value))
        return result == 1L
    }
}

// 使用
val lock = RedisLock(redis, "lock:order:$orderId")
if (lock.tryLock(30)) {
    try {
        doBusinessLogic()
    } finally {
        lock.unlock()
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

关键点:

  • SET ... NX EX 原子操作
  • value 用 UUID 确保唯一
  • 释放用 Lua 脚本

# 05.3 Zookeeper 锁

临时顺序节点机制:

sequenceDiagram
    participant C1 as Client 1
    participant C2 as Client 2
    participant ZK as Zookeeper
    
    C1->>ZK: 创建临时顺序节点 /lock/req-001
    ZK-->>C1: 你是第一个 → 拿到锁
    
    C2->>ZK: 创建临时顺序节点 /lock/req-002
    ZK-->>C2: 不是第一 → Watch /lock/req-001
    
    Note over C1: 业务完成
    C1->>ZK: 删除 /lock/req-001
    ZK->>C2: 通知 → 你是新的第一 → 拿到锁
    
    Note over C1,ZK: 如果 C1 崩溃<br/>临时节点自动删除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

优点:

  • 天然防死锁:客户端断连临时节点自动删
  • 公平锁:按创建顺序排队
  • 强一致:基于 ZAB 协议

缺点:性能比 Redis 差一个数量级。

# 05.4 etcd 锁

基于 lease(租约)+ KV 的实现:

// Go 客户端示例
session, _ := concurrency.NewSession(client, concurrency.WithTTL(10))
defer session.Close()

mutex := concurrency.NewMutex(session, "/lock/order")
if err := mutex.Lock(ctx); err != nil {
    return err
}
defer mutex.Unlock(ctx)

// 业务逻辑
1
2
3
4
5
6
7
8
9
10
11

特点:

  • Raft 强一致
  • 自动续约(lease keep-alive)
  • K8s 生态原生支持

# 05.5 Redisson 实战

Java 业界事实标准:

RLock lock = redisson.getLock("order:" + orderId);

// 简单加锁
lock.lock();
try {
    // 业务
} finally {
    lock.unlock();
}

// 带超时
boolean ok = lock.tryLock(3, 30, TimeUnit.SECONDS);
// 等待 3s,持有最多 30s

// 公平锁
RLock fairLock = redisson.getFairLock("queue:" + bizId);

// 读写锁
RReadWriteLock rwLock = redisson.getReadWriteLock("data");
rwLock.readLock().lock();   // 读
rwLock.writeLock().lock();  // 写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

Redisson 自动解决了 4 个难题:

  • ✅ 自动续期(看门狗 watchdog)
  • ✅ 可重入
  • ✅ Lua 脚本保证原子
  • ✅ 多种锁类型(公平 / 读写 / 联锁)

# 06.关键问题解决

# 06.1 锁超时问题

经典场景:业务执行时间不确定,锁过期了业务还没结束。

gantt
    title 锁超时危险时间线
    dateFormat X
    axisFormat %s
    
    section Client 1
    持有锁         :a, 0, 30
    业务执行       :b, 0, 45
    业务超过锁时长!  :crit, c, 30, 15
    
    section Client 2
    抢到过期锁     :d, 30, 30
    业务执行       :e, 30, 30
    与 C1 并发!    :crit, f, 30, 15
1
2
3
4
5
6
7
8
9
10
11
12
13
14

解决方案:

  • 看门狗(自动续期):Redisson 默认每 1/3 锁时长自动续 1 次
  • 业务时长可控:能预估就设大一点(比业务时长长 2 倍)
  • 失败补偿:业务结束发现锁已过期 → 触发对账或补偿

# 06.2 锁续期问题

Redisson 看门狗实现思路:

sequenceDiagram
    participant C as Client
    participant Redis as Redis
    participant WD as 看门狗线程
    
    C->>Redis: SET lock, EX 30
    C->>WD: 启动续期线程
    
    loop 每 10 秒
        WD->>Redis: 检查锁还在吗?
        WD->>Redis: EXPIRE lock 30 (续期)
    end
    
    Note over C: 业务结束
    C->>Redis: 释放锁
    C->>WD: 停止续期
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

关键设计:

  • 续期间隔 = 锁时长 / 3(默认)
  • 释放锁时停止续期线程
  • 客户端宕机 → 续期停止 → 锁自然过期

# 06.3 锁粒度问题

粒度过粗:锁住整张商品表 → 所有商品互相阻塞。 粒度过细:每个 SKU 一把锁 → 锁数量爆炸。

实战经验:

场景 锁粒度
秒杀单 SKU lock:sku:${skuId}
用户操作 lock:user:${userId}:${action}
订单防重 lock:order:create:${userId}:${requestId}
全局开关 lock:global:promotion

原则:锁粒度 = 业务竞争的最小单元。

# 07.常见陷阱与反例

# 07.1 误删别人锁

反例:

// ❌ 错误
redis.setnx("lock", "1", 30)
doBusiness()  // 业务超过 30 秒
redis.del("lock")  // 删的可能是别人的锁
1
2
3
4

修正:用 UUID + Lua 校验删除(前面已展示)。

# 07.2 锁未释放

反例:

// ❌ 错误:异常时锁不释放
lock.tryLock()
doBusiness()  // 抛异常
lock.unlock()  // 永远不会执行
1
2
3
4

修正:

// ✅ 正确:finally 兜底
if (lock.tryLock()) {
    try {
        doBusiness()
    } finally {
        lock.unlock()
    }
}
1
2
3
4
5
6
7
8

# 07.3 滥用分布式锁

反例:每个 HTTP 请求都加分布式锁——QPS 直接跌 70%。

问题:

  • 大多数场景 DB 乐观锁就够
  • 分布式锁是网络调用,开销大
  • 高竞争场景才有必要

正确:先用乐观锁、唯一索引、状态机;真正高竞争场景才上分布式锁。

mindmap
  root((三大反例))
    误删别人锁
      不带 UUID
      不用 Lua 校验
      线上灾难
    锁未释放
      没用 finally
      宕机泄漏
      死锁
    滥用分布式锁
      不该用也用
      性能下降
      该用 乐观锁/状态机
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 08.演进路线

# 08.1 V1 数据库锁

特征:业务起步、并发不高。

做法:

  • DB 行锁(SELECT ... FOR UPDATE)
  • 乐观锁(version 字段)
  • 唯一索引

适用阶段:< 1000 QPS

# 08.2 V2 Redis 锁

特征:并发上来、需要更好性能。

做法:

  • Redis SETNX + 过期
  • Redisson 框架
  • 看门狗自动续期

适用阶段:1000-10w QPS

# 08.3 V3 高可用锁集群

特征:金融级 / 严格正确性。

做法:

  • Zookeeper / etcd 集群
  • fencing token 二次校验
  • 业务层补偿对账

适用阶段:金融、库存、票务

flowchart LR
    V1[V1 DB 锁<br/>低并发] --> V2[V2 Redis 锁<br/>主流]
    V2 --> V3[V3 ZK/etcd<br/>金融级]
    
    style V1 fill:#e3f2fd
    style V2 fill:#e8f5e8
    style V3 fill:#fff3e0
1
2
3
4
5
6
7

# 09.总结与决策

# 09.1 上线检查表

引入分布式锁前对照:

  • [ ] 真的需要分布式锁吗?(先尝试乐观锁/唯一索引)
  • [ ] 锁服务高可用(Redis 哨兵 / ZK 集群)
  • [ ] 加锁有过期时间
  • [ ] 加锁是原子操作(SET NX EX)
  • [ ] value 是唯一标识(UUID)
  • [ ] 释放锁用 Lua 脚本校验
  • [ ] 锁超时有兜底(自动续期 / 失败补偿)
  • [ ] 业务有 finally 保证释放
  • [ ] 锁粒度合理(不太粗也不太细)
  • [ ] 业务层有"二道防线"(DB 唯一约束 / 状态机)
  • [ ] 监控(加锁失败率、持有时长、超时次数)
  • [ ] 压测覆盖

# 09.2 选型决策树

flowchart TD
    Start([我要做并发控制]) --> Q1{真的多机?}
    Q1 -->|否 单机| Local[ReentrantLock 即可]
    Q1 -->|是| Q2{业务接受偶尔失误?}
    
    Q2 -->|是 互联网常规| Q3{已有 Redis?}
    Q3 -->|是| Redisson[Redisson 推荐]
    Q3 -->|否| Redis[搭 Redis]
    
    Q2 -->|否 金融级| Q4{已有 ZK 或 etcd?}
    Q4 -->|有 ZK| ZK[Zookeeper + Curator]
    Q4 -->|云原生 K8s| Etcd[etcd]
    Q4 -->|都没| ZK2[搭 ZK 集群]
    
    Q5([强烈不推荐]) --> Avoid1[Redlock 复杂还有争议]
    Q5 --> Avoid2[DB 锁 性能差]
    
    style Local fill:#e3f2fd
    style Redisson fill:#e8f5e8
    style ZK fill:#fff3e0
    style Etcd fill:#f3e5f5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

最后一句话:分布式锁是看似简单实则陷阱多多的组件——开篇 480 万损失只是因为开发用 synchronized 替代了分布式锁。

好的分布式锁设计 = 互斥可靠、防死锁、可重入、二道防线兜底。

上次更新: 2026/06/07, 10:26:12
幂等性设计方案
监控告警方案设计

← 幂等性设计方案 监控告警方案设计→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式