分布式系统设计方法论

87 阅读 1994 字 · 约 7 分钟

设计分布式系统,最难的不是学概念,是拿到一个需求知道从哪下手。

概念是答案——分区、复制、共识、事务——它们都在告诉你「这个问题怎么解决」。但没人告诉你「这些问题哪个该先问」。这篇给一个决策链路。不是最优解,每一步只有最不坏的方案。

设计分布式系统不是找最优解,是排着问四个问题:单机够用吗、能接受旧数据吗、操作跨服务吗、要显式谁说了算吗——每步都附赠代价

起点:先别急着分布式

第一个问题应该是:能不能不搞分布式?

单机够用就别拆。单机数据库能撑住的业务量,硬上微服务加消息队列是给自己找麻烦。分布式系统解决的是单机解决不了的问题,不是让系统更高级。

判断线:数据量、请求量、可用性要求。单机能扛住这三样,就别拆。拆了要面对网络延迟、部分失败、数据不一致——这些是单机没有的代价。分布式是手段,不是目的。

第一问:数据量大到单机放不下吗

如果数据量超过了单机磁盘,就得分区。分区把数据切成片段,分布到多台机器上。

分区策略选哪种?按范围切(Range)好做范围查询,但写入集中在热点分区。按哈希切(Hash)数据均匀,但范围查询废了——相邻 key 被打散到不同分区。Cassandra 用一致性哈希,HBase 用范围分区。不存在哪个「更好」,只有你的查询模式更偏什么。

分区完了还要不要复制?要。分区的每个片段是单点——挂了就丢了。通常每个分区有主从复制保证高可用。主从复制最简单:指定一个主节点负责写,从节点只读。多主和无主是应对更复杂场景的升级选项——跨数据中心写、去中心化读写。

第二问:能接受读到旧数据吗

有复制就有延迟——从节点追主节点需要时间。这个窗口里读到的数据可能不是最新的。

核心问题:你的业务能接受吗?转账不能——查到旧余额是事故。用户昵称可以——晚几秒看到新昵称没人在意。广告展示无所谓——有没有一致性都不影响业务。

这个问题的答案直接决定你选哪档一致性模型。从强到弱:线性一致性(像只有一个副本,etcd 用 Raft 实现)、顺序一致性(全局顺序一致但允许延迟,Zookeeper)、因果一致性(有因果关系的保序,无关系的认并发,DynamoDB)、最终一致性(过一阵子总一致,DNS、Cassandra 默认)。

不是「越强越好」。强的代价是延迟——写要等多数节点确认。弱的代价是应用要处理边缘——读到不一致怎么办。一致性模型是刻度盘,不是开关。

第三问:操作跨多个服务吗

单服务里的事务靠数据库的 ACID。跨了服务,没有全局事务管理器。

如果只是改一个服务里的数据,不用分布式事务。如果要跨多个服务、多个数据库——下单要扣库存、扣钱、生成订单——就得选事务方案。

2PC 最直接,协调者先问所有人能不能提交,再统一通知。问题:协调者单点,参与者 prepare 后就锁住资源,协调者挂了全体阻塞。3PC 加了个超时但引入了脑裂,生产环境很少用。

Saga 把长事务拆成小事务,每个独立提交,失败靠补偿链倒着退。代价:中间状态暴露——步骤之间数据不一致,别人能看到中间态。适合长流程、跨服务,但要求你接受「中间数据会短暂不一致」。

TCC 比 Saga 严格——Try 阶段预留资源,Confirm 阶段真正执行,Cancel 阶段释放。比 Saga 多写两个接口,但中间状态不会暴露脏数据。银行转账、支付场景里用 TCC,因为钱不能短暂不一致。

第四问:需要明确指定「谁说了算」吗

很多场景不需要显式锁。主从复制里主节点天然是唯一写入口,就是隐式的「主说了算」。但有些场景需要显式——多个客户端同时操作同一资源,或者集群需要选主。

分布式锁的实现从轻到重:MySQL 锁(最简单,SELECT FOR UPDATE 或表记录当锁,但没有自动过期)、Redis 锁(SET NX EX,单节点快但主从切换可能丢锁,Redlock 尝试补上)、etcd/Zookeeper 锁(基于共识,正确性最高)。

领导者选举也一样——Raft 自带选举(协议内部解决),Zookeeper 临时顺序节点(依赖外部协调服务),Kafka 的 controller 是两级选举。

关键不是选哪个实现——是拿到锁/选主之后,怎么保证持有者失联了不破坏数据。fencing token:每次获取锁/当选带个递增编号,存储端只认最新的。没这个,锁和选主都是定时炸弹。

每一步的代价

每个决策都附赠代价:

  • 分区 → 数据要搬家,再平衡是运维成本
  • 复制 → 延迟窗口,读到旧数据要处理
  • 强一致 → 延迟增加,网络抖动能把性能打回原形
  • 分布式事务 → 要么锁资源(2PC),要么暴露中间状态(Saga),要么多写两个接口(TCC)
  • 锁/选主 → 过期了要 fencing token,否则脑裂

没有免费的方案,只有最不坏的。步骤是:先问需不需要,再问选哪个,最后问能不能承受代价。

小结

分布式系统设计不是找最优解——最优解不存在。是排着问四个问题:数据量能单机吗,读到旧数据能接受吗,操作跨服务吗,需要显式指定谁说了算吗。每个问题回答了,技术选项自然就收敛了。

最终拼的不是「知道多少概念」,是「清楚每个方案的代价」。选哪个,不是因为哪个更好,是因为你的业务能承受哪种代价。