分布式系统设计基础
CAP 与一致性
CAP 的取舍
分布式系统最多只能同时保证三者中的两个:
- **一致性(C)**:所有节点在同一时刻看到相同数据
- **可用性(A)**:系统对请求的响应不会失败
- **分区容错性(P)**:节点间网络分区时系统继续运行
实际里 P 基本没法回避,所以讨论更多是在分区出现时,系统更偏向保一致还是保可用:
- CP系统:优先数据一致,网络分区时拒绝服务(ZK、etcd)
- AP系统:优先可用,允许临时不一致,最终一致(Dynamo、Cassandra)
大多数互联网业务不会把所有链路都做成强一致,常见做法是核心扣减、扣款类链路偏保守,外围状态同步和通知链路接受短暂不一致。
BASE 与最终一致性
BASE是对CAP的实用补充:
- **基本可用(BA)**:服务基本可用,可能存在性能下降
- **软状态(S)**:允许存在中间状态,不要求强一致
- **最终一致性(E)**:经过一段时间后,所有副本达到一致状态
落到工程实现里,通常就是主流程先落库,后续再通过消息、补偿任务和对账任务把状态收平。
分布式锁
Redis 锁
常见写法是用 SET NX EX 把加锁动作收成一步:
1 | // 加锁 |
优点:接入简单,延迟低,大部分业务系统都已经有 Redis
问题:
- Redis宕机时完全丢失锁
- 释放逻辑不原子(GET + DEL分成两步)
适用场景:更适合做业务侧互斥,尤其是活动、预占、短时挡重这类允许做兜底补偿的场景
Zookeeper 锁
ZK 方案的核心不是“锁”,而是它本身就提供了强一致的节点视图和临时节点机制:
1 | /locks/order_123 |
创建顺序临时节点,最小号获得锁。释放时节点自动删除,其他客户端被通知。
优点:一致性更强,客户端断开后节点能自动清理
问题:性能低于Redis、需要额外ZK集群、网络分区时可能阻塞
适用场景:更偏任务调度、主节点选举、全局串行这类对一致性更敏感的场景
数据库锁
很多业务最后兜底还是会落回数据库唯一键或行锁:
1 | SELECT * FROM lock_table WHERE resource_id = ? FOR UPDATE; |
优点:和业务数据放在一起,事务边界清楚
问题:吞吐上不去,热点竞争时容易把数据库拖成瓶颈
适用场景:并发不高、需要强一致且可持久化的场景
怎么选
| 并发量 | 一致性要求 | 场景 | 推荐 |
|---|---|---|---|
| 低 | 强 | 支付、订单扣款 | DB行锁 |
| 中 | 强 | 定时任务、库存扣减 | ZK |
| 高 | 弱 | 活动、优惠券 | Redis |
| 高 | 强 | 核心扣减链路 | 先拆业务,再决定是否局部DB兜底 |
分布式事务基础
2PC
协调者(Coordinator)分两个阶段处理事务:
第一阶段(投票):
- 协调者向所有参与者发送prepare请求
- 参与者执行业务逻辑,锁定资源,返回OK或失败
第二阶段(提交):
- 协调者收集投票结果
- 全部OK则发commit,任何一个失败则发rollback
- 参与者执行对应操作
问题:
- 同步阻塞,参与者长时间占用资源
- 协调者宕机时,参与者资源无法释放
- 网络分区时容易脑裂
严格意义上的 2PC 在业务系统里不常见,更多是理解它为什么重、为什么容易阻塞。
TCC
应用层实现的分布式事务:
- Try:业务逻辑检查 + 资源预留(冻结金额、库存锁定)
- Confirm:真正执行业务(扣款、减库存)
- Cancel:业务失败或超时时释放预留资源
1 | 订单系统 --Try(冻结100)--> 账户系统 |
优点:强一致性、资源及时释放
问题:业务代码侵入性强、需要为每个操作实现Try/Confirm/Cancel三个方法
适用:更适合金额、额度这类强一致要求高,且业务方愿意承担实现成本的场景
事件驱动最终一致
互联网系统里更常见的是把事务边界收在本地,然后靠消息和补偿把后续状态推平:
1 | 1. 订单确认 -> 写库 + 发消息 |
优点:吞吐更高,链路更松耦合
问题:数据有短期不一致窗口、需要对账和补偿机制
适用:订单通知、库存同步、发券、积分到账这类允许短时间不一致的链路
幂等
幂等说白了就是:同一笔请求因为重试、重复投递、超时回放又来一次时,系统别把结果做重了。
入口幂等
在业务逻辑前面拦截重复请求:
1 | // 客户端生成唯一的requestId |
缓存适合挡住短时间重复请求,真正长期可靠的判断通常还是要落到数据库唯一键或状态表。
操作幂等
对于无法在入口拦截的异步操作(消息消费、定时任务),在操作执行前检查状态:
1 |
|
共识算法
Raft
Raft 这类共识算法,业务开发平时不一定直接实现,但在理解 etcd、Consul、配置中心和注册中心行为时很有用。
核心是日志复制 + 领导者选举:
领导者选举:
- 集群中有一个Leader,其他是Follower
- 如果Follower在一段时间内没收到Leader的心跳,发起选举
- 获得多数票的服务器成为新Leader
日志复制:
- Leader接收客户端请求,写入日志
- Leader将日志复制到所有Follower
- 多数Follower确认后,Leader提交日志
- 提交后的日志才真正应用到状态机
优点:相比 Paxos 更容易讲清楚,也更容易映射到工程实现
如果不是做中间件,通常不用死抠推导过程。先理解“多数派提交”“Leader 负责推进日志”这两个核心点,已经够支撑大部分工程判断。



