|
| 1 | +这个问题非常好!分布式场景下的协调机制远不止"分布式锁"一种。Lease(租约)确实是其中非常核心的一种,但它和"锁"既有联系又有本质区别。 |
| 2 | + |
| 3 | +我们来系统性地梳理一下分布式场景中**所有主流的协调机制**。 |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## 一、 Lease(租约)机制 |
| 8 | + |
| 9 | +### 它和分布式锁是什么关系? |
| 10 | + |
| 11 | +**Lease 是分布式锁的底层灵魂。** 准确地说: |
| 12 | + |
| 13 | +> **分布式锁 = Lease(租约)+ 资源绑定(锁名)+ 释放协议** |
| 14 | +
|
| 15 | +一个纯粹的 Lease 只关心一件事:**"我在一段时间内拥有某种权利,到期自动作废"**。 |
| 16 | +而分布式锁是 Lease 的一种具体应用:把这个"权利"绑定到了"独占某个资源"上。 |
| 17 | + |
| 18 | +### Lease 的核心特征 |
| 19 | + |
| 20 | +``` |
| 21 | +┌──────────────────────────────────────────────┐ |
| 22 | +│ Lease 租约模型 │ |
| 23 | +│ │ |
| 24 | +│ 持有者: Node-A │ |
| 25 | +│ 租约内容: "我有权处理 order-1001" │ |
| 26 | +│ 有效期: 10秒 (TTL) │ |
| 27 | +│ 到期行为: 自动失效,无需手动释放 │ |
| 28 | +│ │ |
| 29 | +│ 续约(Renew): 持有者可以在到期前主动续期 │ |
| 30 | +│ 撤销(Revoke): 中央协调者可以强制撤销 │ |
| 31 | +└──────────────────────────────────────────────┘ |
| 32 | +``` |
| 33 | + |
| 34 | +### Lease vs Lock 的本质区别 |
| 35 | + |
| 36 | +| 对比维度 | 传统分布式锁 | Lease 租约 | |
| 37 | +| :--- | :--- | :--- | |
| 38 | +| **生命周期** | 必须显式释放(`unlock()`) | **到期自动失效**,天然防死锁 | |
| 39 | +| **续期机制** | 可选(如 Redisson Watchdog) | **续约是核心设计**(心跳续约) | |
| 40 | +| **持有者崩溃** | 如果没设超时,可能永久死锁 | **自动过期释放**,其他节点可接管 | |
| 41 | +| **典型用途** | 保护临界区(如扣库存) | Leader 选举、服务注册、缓存一致性 | |
| 42 | + |
| 43 | +### Lease 在实际系统中的应用 |
| 44 | + |
| 45 | +| 系统 | 怎么用 Lease 的 | |
| 46 | +| :--- | :--- | |
| 47 | +| **Kubernetes** | 每个 Pod 通过 Lease 对象向 API Server 上报心跳,**到期未续约则判定节点死亡**,触发 Pod 驱逐重调度 | |
| 48 | +| **Zookeeper** | 客户端与 ZK 建立 Session,本质就是一个 Lease。Session 过期 → 临时节点自动删除 → 触发 Watcher 通知 | |
| 49 | +| **etcd** | 通过 `Grant(TTL)` 创建租约,绑定到 Key 上。**Leader 选举的底层核心就是 Lease** | |
| 50 | +| **Redis** | `SET key value EX 10 NX` 本质就是一个 10 秒的 Lease | |
| 51 | +| **HDFS** | NameNode 给 DataNode 颁发 Lease,**持有 Lease 的客户端独占写文件权** | |
| 52 | + |
| 53 | +--- |
| 54 | + |
| 55 | +## 二、 分布式场景中的其他常用协调机制 |
| 56 | + |
| 57 | +除了"锁"和"租约",分布式系统中还有一整套工具箱: |
| 58 | + |
| 59 | +--- |
| 60 | + |
| 61 | +### 1. Leader Election(领导者选举) |
| 62 | + |
| 63 | +**解决的问题**:集群中多个节点,必须选出**唯一一个节点**来执行某项工作(比如定时任务调度、数据分片协调)。 |
| 64 | + |
| 65 | +**底层原理**:通常就是基于 Lease 实现的!谁先抢到 Lease 谁就是 Leader,Leader 持续续约保持身份,一旦 Leader 崩溃、Lease 到期,其他节点重新竞选。 |
| 66 | + |
| 67 | +``` |
| 68 | +┌─────────────────────────────────────────────┐ |
| 69 | +│ Leader Election 流程 │ |
| 70 | +│ │ |
| 71 | +│ Node-A ──► 尝试写入 /election/leader ──► 成功!成为 Leader |
| 72 | +│ Node-B ──► 尝试写入 /election/leader ──► 失败,Watch 等待 |
| 73 | +│ Node-C ──► 尝试写入 /election/leader ──► 失败,Watch 等待 |
| 74 | +│ │ |
| 75 | +│ [Leader 崩溃] ──► Lease 到期 ──► Key 被删除 │ |
| 76 | +│ Node-B ──► 抢到写入 ──► 成为新 Leader │ |
| 77 | +└─────────────────────────────────────────────┘ |
| 78 | +``` |
| 79 | + |
| 80 | +**实际应用**: |
| 81 | + |
| 82 | +| 系统 / 框架 | 怎么做 Leader 选举 | |
| 83 | +| :--- | :--- | |
| 84 | +| **Kubernetes** Controller Manager | 通过 **Lease 对象** 竞选,只有 Leader 实例工作 | |
| 85 | +| **Elasticsearch** | 通过内部共识协议选举 Master 节点 | |
| 86 | +| **Kafka** | Controller Broker 通过 Zookeeper 临时节点竞选 | |
| 87 | +| **自研业务** | 用 Redis `SETNX` 或 etcd `CreateRevision` 实现 | |
| 88 | + |
| 89 | +--- |
| 90 | + |
| 91 | +### 2. Fencing Token(隔离令牌 / 防护令牌) |
| 92 | + |
| 93 | +**解决的问题**:分布式锁的一个致命漏洞 —— **锁过期了,但持有者还不知道**。 |
| 94 | + |
| 95 | +``` |
| 96 | +时间线: |
| 97 | +t1: Node-A 获取锁 (TTL=10s) |
| 98 | +t2: Node-A 发生 Full GC 停顿 30 秒...(它以为自己还持有锁) |
| 99 | +t3: 锁自动过期,Node-B 成功获取锁 |
| 100 | +t4: Node-A 从 GC 中醒来,以为自己还有锁,继续写数据库 ← 💥 脑裂! |
| 101 | +``` |
| 102 | + |
| 103 | +**Fencing Token 的解决方案**: |
| 104 | +每次获取锁时,锁服务会颁发一个**全局单调递增的令牌号(epoch / revision)**。 |
| 105 | +下游资源(数据库、存储系统)会**拒绝接受过期令牌号的写入请求**。 |
| 106 | + |
| 107 | +``` |
| 108 | +Node-A 获取锁 → 得到 Token #35 |
| 109 | +Node-B 获取锁 → 得到 Token #36 |
| 110 | +Node-A 醒来,拿着 Token #35 去写数据库 |
| 111 | +数据库: "我上次接受的是 #36,你的 #35 过时了,拒绝!" ← ✅ 安全 |
| 112 | +``` |
| 113 | + |
| 114 | +**实际应用**:etcd 的 Revision、Zookeeper 的 zxid、Google Chubby 的 sequencer。 |
| 115 | + |
| 116 | +--- |
| 117 | + |
| 118 | +### 3. Idempotency(幂等性设计) |
| 119 | + |
| 120 | +**解决的问题**:网络超时后重试、消息队列重复投递、用户狂点按钮。**不是阻止并发,而是让重复执行也不会出错**。 |
| 121 | + |
| 122 | +**核心思路**:每个操作携带一个**唯一的请求ID(幂等键)**,服务端记录已经处理过的 ID,重复请求直接返回上次的结果。 |
| 123 | + |
| 124 | +```python |
| 125 | +# Python 伪代码 |
| 126 | +def create_order(idempotency_key: str, data: dict): |
| 127 | + # 第一步:检查这个 key 是否已经处理过 |
| 128 | + existing = redis.get(f"idem:{idempotency_key}") |
| 129 | + if existing: |
| 130 | + return json.loads(existing) # 直接返回上次结果,不再执行 |
| 131 | + |
| 132 | + # 第二步:正常执行业务 |
| 133 | + result = do_create_order(data) |
| 134 | + |
| 135 | + # 第三步:记录已处理,设置过期时间 |
| 136 | + redis.set(f"idem:{idempotency_key}", json.dumps(result), ex=3600) |
| 137 | + return result |
| 138 | +``` |
| 139 | + |
| 140 | +**实际应用**: |
| 141 | + |
| 142 | +- 支付接口(支付宝、Stripe)都要求传 `out_trade_no` 作为幂等键 |
| 143 | +- 消息队列消费者的"去重表" |
| 144 | +- HTTP 的 `Idempotency-Key` Header 标准 |
| 145 | + |
| 146 | +--- |
| 147 | + |
| 148 | +### 4. Distributed Transaction(分布式事务) |
| 149 | + |
| 150 | +**解决的问题**:一个业务操作需要修改多个不同服务/数据库的数据,要保证**要么全成功,要么全回滚**。 |
| 151 | + |
| 152 | +| 方案 | 核心思路 | 一致性 | 常用框架 | |
| 153 | +| :--- | :--- | :--- | :--- | |
| 154 | +| **2PC (两阶段提交)** | 协调者先问所有参与者"能不能提交?",全部同意后再统一提交。 | 强一致 | MySQL XA、Seata AT模式 | |
| 155 | +| **TCC (Try-Confirm-Cancel)** | 业务层面拆成三步:预留资源 → 确认 → 取消。 | 强一致 | Seata TCC模式、Hmily | |
| 156 | +| **Saga (编排/协作)** | 把大事务拆成一系列小事务,每个小事务都有对应的**补偿回滚操作**。 | 最终一致 | Temporal、Cadence、自研 | |
| 157 | +| **本地消息表** | 业务操作和消息写入在同一个本地事务中,由后台任务异步投递消息。 | 最终一致 | 自研(非常实用) | |
| 158 | + |
| 159 | +``` |
| 160 | +Saga 举例 (下单流程): |
| 161 | + ┌────────────┐ ┌────────────┐ ┌────────────┐ |
| 162 | + │ 1.创建订单 │───►│ 2.扣减库存 │───►│ 3.扣减余额 │ |
| 163 | + │ 补偿:取消订单│◄───│ 补偿:恢复库存│◄───│ 补偿:退款 │ ← 任何一步失败, |
| 164 | + └────────────┘ └────────────┘ └────────────┘ 反向执行补偿操作 |
| 165 | +``` |
| 166 | + |
| 167 | +--- |
| 168 | + |
| 169 | +### 5. CAS (Compare-And-Swap / 乐观并发控制) |
| 170 | + |
| 171 | +**解决的问题**:无锁并发!不加锁,而是在**更新的瞬间检查数据是否被别人改过**。 |
| 172 | + |
| 173 | +这不仅仅用在数据库(乐观锁 `version`),在分布式协调系统中也是核心原语: |
| 174 | + |
| 175 | +| 系统 | CAS 的具体表现 | |
| 176 | +| :--- | :--- | |
| 177 | +| **etcd** | `Txn(If(key.Version == X).Then(Put(...)))` | |
| 178 | +| **Zookeeper** | `setData(path, data, expectedVersion)` | |
| 179 | +| **DynamoDB** | `ConditionExpression: "version = :v"` | |
| 180 | +| **Consul** | `PUT /kv/key?cas=<ModifyIndex>` | |
| 181 | + |
| 182 | +--- |
| 183 | + |
| 184 | +### 6. Watch / Pub-Sub(事件通知) |
| 185 | + |
| 186 | +**解决的问题**:不轮询,而是**让状态变更主动通知到相关方**。常与 Lease、Leader 选举搭配使用。 |
| 187 | + |
| 188 | +``` |
| 189 | +典型用法: |
| 190 | + Node-B watch "/election/leader" |
| 191 | +
|
| 192 | + [Leader Node-A 崩溃] |
| 193 | + → etcd 检测到 Lease 到期 |
| 194 | + → 删除 Key |
| 195 | + → 通知所有 Watcher |
| 196 | + → Node-B 收到通知,立刻发起竞选 |
| 197 | +``` |
| 198 | + |
| 199 | +| 系统 | 通知机制 | |
| 200 | +| :--- | :--- | |
| 201 | +| **etcd** | `Watch` API(长连接流式推送) | |
| 202 | +| **Zookeeper** | `Watcher`(一次性触发,需重新注册) | |
| 203 | +| **Redis** | `Pub/Sub` 频道 或 `Keyspace Notification` | |
| 204 | +| **Consul** | Long Polling(`?index=X&wait=30s`) | |
| 205 | + |
| 206 | +--- |
| 207 | + |
| 208 | +## 三、 全景总结 |
| 209 | + |
| 210 | +``` |
| 211 | +┌─────────────────────────────────────────────────────────────┐ |
| 212 | +│ 分布式协调机制全景图 │ |
| 213 | +│ │ |
| 214 | +│ ┌──────────┐ │ |
| 215 | +│ │ Lease │──── 最底层的原语,带 TTL 的临时"凭证" │ |
| 216 | +│ └────┬─────┘ │ |
| 217 | +│ │ 基于 Lease 构建 │ |
| 218 | +│ ├──► 分布式锁 (Lock) ← 独占资源 │ |
| 219 | +│ ├──► 领导者选举 (Election) ← 独占角色 │ |
| 220 | +│ └──► 服务注册/心跳 (Registry) ← 独占"我活着"的声明 │ |
| 221 | +│ │ |
| 222 | +│ ┌──────────┐ │ |
| 223 | +│ │ CAS │──── 无锁乐观并发,"先不锁,改的时候再校验" │ |
| 224 | +│ └──────────┘ │ |
| 225 | +│ │ |
| 226 | +│ ┌──────────┐ │ |
| 227 | +│ │ Fencing │──── 防止锁过期后的脑裂,用单调递增 Token 隔离 │ |
| 228 | +│ └──────────┘ │ |
| 229 | +│ │ |
| 230 | +│ ┌──────────┐ │ |
| 231 | +│ │ 幂等性 │──── 不阻止重复,而是让重复无害 │ |
| 232 | +│ └──────────┘ │ |
| 233 | +│ │ |
| 234 | +│ ┌──────────┐ │ |
| 235 | +│ │ 分布式事务 │──── 跨服务的"全成功或全回滚" │ |
| 236 | +│ │ 2PC/Saga │ │ |
| 237 | +│ └──────────┘ │ |
| 238 | +│ │ |
| 239 | +│ ┌──────────┐ │ |
| 240 | +│ │Watch/通知 │──── 状态变更的实时感知,驱动后续动作 │ |
| 241 | +│ └──────────┘ │ |
| 242 | +└─────────────────────────────────────────────────────────────┘ |
| 243 | +``` |
| 244 | + |
| 245 | +**一句话记忆**: |
| 246 | +> **Lease 是地基,Lock 和 Election 是房子,Fencing 是防盗门,CAS 是乐观派,幂等是佛系派,分布式事务是强迫症派,Watch 是通信兵。** |
0 commit comments