架构与权衡
架构决策没有"正确答案",只有权衡。本章讲模块化单体、微服务的收益与运维税、同步调用与消息队列、事件驱动与最终一致——并给出选择架构的决策依据(团队规模、变更频率、一致性需求),而非流行度。
先把模块边界做好,再考虑是否拆进程:单体不等于大泥球——模块化单体(清晰的模块边界、依赖方向单向)在团队小、变更集中的阶段是最优架构:
- 部署一次(无分布式问题)、事务简单(本地事务)、调试直观(一个进程、一个栈);
- 微服务解决的是组织问题,代价是分布式系统的所有问题——团队 < 20 人时,单体 + 模块边界几乎总是更快。
模块边界的纪律(→ 设计原则与模式):模块间只走公开接口、依赖单向、数据库不共享表(将来拆分的预演)。
微服务:收益与运维税
Section titled “微服务:收益与运维税”微服务的收益:
- 独立部署:各服务独立发版(解耦发布节奏);
- 独立扩展:热点服务单独扩容(→ 系统设计工具箱);
- 团队自治:康威定律的顺势利用(团队边界 = 服务边界)。
运维税(分布式系统的全部问题):
- 网络不可靠:超时、重试、幂等、熔断(→ 协调服务与实践模式);
- 最终一致:跨服务事务只能 Saga(→ Saga 模式);
- 可观测性:跨服务追踪、日志聚合(→ 性能工程与可观测性);
- 部署与基础设施:服务发现、配置、版本兼容。
拆分的时机判断:不是"什么时候该微服务",而是"单体什么时候疼"——部署互相阻塞(一个团队的改动阻塞所有发布)、扩展需求分化(某模块要扩 10 倍)、团队规模超沟通上限。先疼再拆,拆的是已经清楚的边界。
同步调用与消息队列
Section titled “同步调用与消息队列”服务间通信的两条路:
| 同步(HTTP/RPC) | 异步(消息队列) | |
|---|---|---|
| 语义 | 请求-响应,立刻知道结果 | 发布-订阅,最终送达 |
| 耦合 | 时间耦合(对方必须活着) | 时间解耦(对方可以离线) |
| 失败传播 | 级联(下游挂拖垮上游) | 隔离(队列缓冲) |
| 一致性 | 简单(同步链路) | 最终一致 |
消息队列解耦时间,也引入重复与乱序:至少一次投递让消息可能重复(消费端幂等,→ 协调服务与实践模式)、乱序(按 key 分区保序)。
选择:需要"立刻知道结果"(查询、扣款)→ 同步;需要"事后处理"(通知、统计、派生数据)→ 异步。混合是常态:核心链路同步,旁路异步。
事件驱动与最终一致
Section titled “事件驱动与最终一致”事件驱动架构(EDA):状态变化发布为事件,其他服务订阅派生:
- 事件溯源(event sourcing):事件是真相源,状态是重放——审计免费(历史完整),代价是查询要投影与版本演化复杂;
- CQRS:写模型与读模型分离(写优化与读优化各自的存储)——读模型由事件投影,最终一致;
- 派生数据:搜索索引、缓存、报表都由事件流派生(Kafka 的设计哲学)。
事件驱动与最终一致绑定:派生数据滞后于真相源——业务要接受"读到几分钟前的投影"。回报是扩展性与解耦(新增消费者不动生产者)。
选择架构的标准不是流行度,是四个变量:
- 团队规模:< 20 人单体优先;更大按团队边界拆(康威定律);
- 变更频率:模块变更节奏分化 → 拆分解耦发布;
- 扩展需求:负载不均(某模块 10 倍流量)→ 独立扩展;
- 一致性需求:强一致链路长 → 收窄事务边界(按聚合拆)。
先做好模块边界,再考虑是否拆进程——边界清楚的模块化单体可以逐块演进成微服务("巨石渐进拆分"),边界不清楚的直接拆微服务是分布式大泥球。
- 微服务解决的是组织问题,代价是分布式系统的所有问题;小团队单体优先
- 拆分时机:部署阻塞、扩展分化、团队超上限——先疼再拆
- 消息队列解耦时间,也引入重复与乱序;核心链路同步、旁路异步
- 事件驱动绑定最终一致:派生数据滞后,回报是扩展性与解耦
- 决策依据:团队规模、变更频率、扩展需求、一致性需求——不是流行度