分布式应用架构 — 从 Fowler 的警告到十二个模式
分布式应用架构 — 从 Fowler 的警告到十二个模式
这一章回答一个"这和 Docker 有什么关系"的问题: 容器让多服务系统遍地都是, 而多服务系统就是分布式系统——它有全新的失败方式。作者的立场从一句 2002 年的 名言开始:"分布式对象设计第一定律:不要分布式你的对象!"(Martin Fowler)。 读完这章你会拿到一张模式清单:结构怎么切、服务怎么找、故障怎么挡、更新怎么不停机。
1. 开场:一句二十多年前的话为什么还管用
Fowler 这句话的背景书里讲得很清楚:上世纪 90 年代末的 CORBA、DCOM、RMI 承诺 "远程调用像本地方法一样透明",结果这个抽象严重漏底——网络延迟、部分失败、 版本、部署耦合,让看起来简单的设计变得难以理解、测试和演进1。
核心教训一句话:分布式不是技术细节,它从根本上改变了系统的失败方式。 技术栈换了几轮,教训没变;现代分布式应用的做法是拥抱这些现实, 而不是把它们藏起来1。
2. 顶层全景:单体世界 vs 分布式世界
主线例子是同一个宠物店应用,放在两种架构里对照2:
单体世界:
服务器 blue-box-12a(静态 IP 172.52.13.44)上跑一个 pet-shop
= 主模块 + 几个紧耦合的库,编译成一个二进制
│ 特征:机器有名字、地址固定、应用是一整块
▼
分布式世界:
一堆没有人类友好名字、只有 UUID 的服务器
pet-shop 拆成 pet-api、pet-web、pet-inventory 等松耦合服务
每个服务在集群里跑多个实例,位置随时可能变(B 服务可能从节点 X 挪到 Y)
│ 特征:谁在哪不确定、有几份不确定、失败是常态
▼
容器的位置:容器和编排引擎让"以前很难的分布式问题显著变简单——
但注意,是 significantly simpler(显著更简单),不是 simple(简单)"[^3]
图说:左边是"一台有名的机器",右边是"一窝没名字的实例"。
本章全部模式,都是在右边这个世界里活下来的手段。3. 核心原理:十二个模式,分四组
3.1 结构组:怎么切
松耦合组件。 复杂问题的解法永远是拆分——像盖房子不是一步整浇,而是零件拼装。 组件 A 不对 B、C 的内部做任何假设,只通过明确定义的公共接口通信。好处除了 独立开发,还有测试时别人可以用 stub/mock 顶替3。
无状态优先。 会产生或修改持久数据的服务叫有状态组件(数据库、写文件的服务), 反之是无状态组件。无状态组件可以随时扩缩、随手杀掉、在集群另一台机器上重启—— 因为它没有跟着它走的数据。设计原则:大部分服务做成无状态, 把有状态组件挤到系统的边缘,尽量少4。
3.2 寻址与分发组:怎么找到彼此、怎么分活
服务发现。 服务间调用不能靠配置文件里写死 IP(单机时代可以,分布式世界"手动追踪 一堆会搬家的组件是噩梦")。解法是引入一个外部权威(通常就是 DNS 服务): 它知道集群里所有节点、所有服务此刻跑在哪;调用方先问它要目标服务的 IP 和端口, 再建立通信。Docker 引擎内置 DNS,Kubernetes 也有——第 10、14 章落地5。
路由。 按 OSI 模型分层:第 2 层路由最底层(MAC 到 MAC),第 7 层路由最高层,
按 URL(如 https://acme.com/pets)把请求送到正确的组件——容器和编排语境里,
2/3/4/7 层都相关(第 10 章细讲)6。
负载均衡。 服务 B 有多个实例时,谁来决定请求发给谁?一个外部服务拦下调用, 替调用方做决定。最常见的算法是 round-robin:1、2、……n,再从头来。 负载均衡同时是高可用手段——它会定期检查每个实例的健康,只把流量转给 健康的实例7。
冗余。 关键系统的可用性问题是:不是某个组件会不会坏,而是什么时候坏。 每个部件(应用服务、边缘路由器、负载均衡器自己)都要跑多份。链条的强度取决于 最弱的一环——负载均衡器本身挂了,前面全是白搭8。
3.3 防御组:故障来了怎么办
防御式编程的前提: 你依赖的所有外部服务(别的微服务、第三方信用卡验证、 股票行情)都不归你管,正确性和可用性都不能假设——按最坏情况写代码9。
| 模式 | 做法 | 关键细节 |
|---|---|---|
| 重试+退避 | 调用失败或超时,等一会重试;再失败,等更久 | 每次等待加长(指数退避),到最大次数就放弃,提供降级服务(返回缓存旧数据或干脆没数据)10 |
| 错误处理 | fail fast(快速失败):不可恢复的错误尽早发现、立即失败 | 失败时往 STDERR/STDOUT 写有意义的日志、给调用方精确的错误原因;入参先校验11 |
| 熔断器 | 监视对 B 的调用,失败数到阈值就"跳闸";跳闸后所有调用直接报错,根本不再发起 | 类比电路熔断器:不让一个坏电器烧掉整栋房子。防的是级联失败12 |
| 限流器 | 限定单位时间内的请求数 | 削平突发流量,防后端被压垮;和熔断器配合13 |
| 舱壁(bulkhead) | 把资源(线程池、连接池)按依赖切开,一个依赖出问题只占用它那份 | 与熔断器合用,局部故障不拖垮整体14 |
日志的纪律单列:重要操作必须记;集中聚合到一处(别散落在各节点); 每条带上时间、组件名、所在节点。红线:绝不记录 PII(个人身份信息)、 PHI(健康信息)、SFI(金融敏感信息)——合规事故和安全事故都从这里来15。
健康检查与自愈。 负载均衡器(或某个系统服务)定期轮询每个实例:"你还在吗? 健康吗?"——回答 No 或超时,就杀掉这个实例、拉个新的顶替;全自动完成就叫 自愈(autohealing)。也可以反过来:组件定期发心跳,多久不发就判定死亡16。
3.4 可观测组:看不见就没有
- 日志(上面已述,生产环境它排第一:生产不能打断点,根因靠日志追)17;
- 追踪(tracing):一个请求穿过整个系统,在每个组件花了多少时间—— dashboard 的原料(第 06 章的 Jaeger 就是这事的工具)18;
- 监控:运维要的 dashboard,分两类指标——非功能指标(CPU/内存、崩溃次数、 节点健康)和功能指标(下单系统的结账数、库存服务的缺货件数), 原料都从日志里来19。
书里还有个成本提醒:日志多了会撑爆存储、抬高云账单,颗粒度要和资源约束 一起权衡20。
4. 生产环境:更新、数据、回滚
4.1 零停机更新的三种节奏
滚动更新:停 1 个旧实例 → 上新实例 → 观察 → 下一个
任何时刻都有实例在干活,全程不停机[^22]
蓝绿部署:blue(现版本)扛全部流量;green(新版本)装好但不接线
→ 对 green 做冒烟测试 → 路由器切流到 green → 观察
→ 达标就退役 blue;不对劲就切回 blue,green 拖回去修[^23]
金丝雀发布:和蓝绿类似,但流量是渐进的——先切 1% 到新版本,盯紧;
达标再 5%;再达标继续加,直到 100%,稳定后旧版退役。
名字来自煤矿工人带金丝雀下井:鸟对毒气最敏感,鸟死了人就得跑[^24]三者的选择是风险与速度的权衡:滚动最省事,蓝绿给了"秒级切回"的底气,金丝雀把 "用户替你踩雷"的量控制到 1%。
4.2 不可逆数据变更:三步走
如果更新包含不可逆的数据库 schema 变更,代码改动和数据改动绝不能同一时刻上。 书里的三步21:
- 先上向后兼容的 schema 和数据变更(新旧代码都能跑);
- 再上需要新数据结构的新代码;
- 成功后清理:删掉兼容层。
4.3 大规模数据结构变更与动态迁移
数据量大了之后,变更格式要当成一个项目做:分析、计划、测试、受控执行,备好 资源和备份22。动态迁移更难——迁移过程中数据还在实时更新:要持续跟踪源系统 的改动并复制到目标系统(复制/镜像软件),把停机压到最小,并且必须有回滚预案22。
4.4 回滚与滚前
分布式世界里,"会不会回滚"不是问题,"什么时候回滚"才是——回滚不能是 事后补救,必须是经过测试和验证的部署流程的一部分23。
- 蓝绿部署下回滚很简单:路由器从 green 切回 blue 就行;
- 如果践行持续交付(主干随时可部署),也可以考虑滚前(roll forward): 直接修好问题发新版——往往比回滚快,尤其当上一个变更引入了不兼容时23。
5. 现代微服务模式五个
章末的速写,每个一段,照原书的格局接住24:
| 模式 | 解决什么 |
|---|---|
| API 网关 | 客户端的单一入口:路由到各微服务,顺带管认证、限流、请求聚合。一个网关容器挡在一排后端服务前面,客户端变简单、攻击面变小 |
| Saga | 跨服务的分布式事务:一串本地事务用事件/命令协调;某步失败就用补偿动作回滚之前的步骤,最终一致(典型:电商下单) |
| CQRS | 命令查询职责分离:读模型和写模型分开,读多的服务用缓存视图,写更新唯一事实源,各自独立扩缩 |
| 事件溯源(Event Sourcing) | 变更存成不可变事件而非直接改状态:审计和重放能力极强,适合金融/审计密集型应用(常和 CQRS 搭配) |
| 绞杀者(Strangler) | 从单体渐进迁移:一圈一圈替换旧组件,"绞死"老系统,避免大爆炸式重写 |
收尾的告诫还是 Fowler 那句:警惕过度拆分——只有收益大于复杂度时才分布式; 而且这些模式要靠自动化测试和 CI/CD 才玩得转24。
6. 作者的判断与证据
- "分布式显著更简单,但不简单":这是全章的定调,也是对"容器=银弹"心态的 直接纠偏25。
- 十二个模式是行业共识(Fowler、Netflix、K8s 生态的公共词汇),书里做的是 筛选与排序,不是发明;每个模式都给了"解决什么问题"的动机,可信度高。
- 金丝雀 1%→5%→100% 的具体数字是经验值,书里没给依据,按惯例对待26。
- 案例缺位:本章没有任何真实公司案例,全部是概念推演。
判断(我们的,不是书里的): 这章是全书从"工具书"变"架构书"的转折点—— 它的真正用意是给第 14 章编排立论:编排器不是新玩具,是这十二个模式里 服务发现、负载均衡、健康检查、自愈、滚动更新这五个的自动化实现。 记住这个对应关系,后面 Kubernetes 的概念会一个词一个词地对上号。 如果错,会错在: 少数模式(如舱壁)在 K8s 时代更多由服务网格或应用框架实现, 而不是编排器;对应关系是"主要由谁实现"的模糊归属,不是一一映射。
7. 边界与局限
- 模式只到概念层:每个模式都值得一本书,本章(及原书)没有实现细节; 熔断/限流的具体库(Polly、Resilience4j)、Saga 的框架都没提。
- 没有反模式清单(除了 Fowler 那句总警告):分布式事务该躲、共享数据库该躲 这类具体反坑未展开。
- 数字是示意(1%、5%、round-robin),生产参数要按系统实测。
- 现代模式一节(Saga/CQRS/事件溯源)明显是"点到为止",任何一条展开都超出本书范围。
8. 可带走的
- Fowler 第一定律:不要分布式——收益必须大于复杂度才拆1。
- 无状态优先:状态挤到边缘;无状态组件才能随意扩缩和搬家4。
- 服务发现 = 外部权威(DNS);负载均衡 = round-robin + 健康检查57。
- 失败是常态:重试加指数退避、熔断器、限流器、舱壁,一套组合拳10121314。
- fail fast + 精确报错 + 日志写 STDOUT;绝不记 PII/PHI/SFI1115。
- 自愈 = 健康检查 + 自动替换实例,全自动才叫自愈16。
- 生产三件套:日志(排障)、追踪(看请求)、监控(dashboard)171819。
- 零停机更新三选:滚动 / 蓝绿(可秒切回)/ 金丝雀(1%→100% 渐进)272826。
- 不可逆 schema 变更三步走:兼容变更 → 新代码 → 清理21。
- 回滚是流程不是补救;持续交付下可以滚前23。
9. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| Fowler 第一定律与背景 | Chapter 9: Learning about Distributed Application Architecture | text/100-ch09-chapter-9-learning-about-distributed-application.txt:28(搜「First Law」) · text/100-ch09-chapter-9-learning-about-distributed-application.txt:32(搜「CORBA」) · text/100-ch09-chapter-9-learning-about-distributed-application.txt:36(搜「failure modes」) |
| 术语表 | Chapter 9: Learning about Distributed Application Architecture | text/100-ch09-chapter-9-learning-about-distributed-application.txt:43(搜「terminology」) |
| 单体示例(blue-box-12a) | Chapter 9: Learning about Distributed Application Architecture | text/100-ch09-chapter-9-learning-about-distributed-application.txt:98(搜「well-defined name」) · text/100-ch09-chapter-9-learning-about-distributed-application.txt:103(搜「blue-box-12a」) |
| 分布式示例(UUID、pet-api) | Patterns and best practices | text/101-fm-patterns-and-best-practices.txt:6(搜「unique IDs」) · text/101-fm-patterns-and-best-practices.txt:9(搜「pet-api」) |
| "显著更简单" | Patterns and best practices | text/101-fm-patterns-and-best-practices.txt:16(搜「significantly simpler」) |
| 松耦合 | Patterns and best practices | text/101-fm-patterns-and-best-practices.txt:31(搜「divide it into smaller」) · text/101-fm-patterns-and-best-practices.txt:40(搜「no assumptions」) |
| 有状态/无状态 | Patterns and best practices | text/101-fm-patterns-and-best-practices.txt:47(搜「state」) · text/101-fm-patterns-and-best-practices.txt:56(搜「stateless components are much simpler」) · text/101-fm-patterns-and-best-practices.txt:62(搜「boundaries」) |
| 服务发现 | Patterns and best practices | text/101-fm-patterns-and-best-practices.txt:69(搜「find each other」) · text/101-fm-patterns-and-best-practices.txt:81(搜「nightmare」) · text/101-fm-patterns-and-best-practices.txt:89(搜「DNS」) |
| 路由(OSI 2-7 层) | Patterns and best practices | text/101-fm-patterns-and-best-practices.txt:104(搜「Routing」) · text/101-fm-patterns-and-best-practices.txt:112(搜「layer 7」) |
| 负载均衡与 round-robin | Patterns and best practices | text/101-fm-patterns-and-best-practices.txt:117(搜「Load balancing」) · text/101-fm-patterns-and-best-practices.txt:132(搜「round-robin」) · text/101-fm-patterns-and-best-practices.txt:136(搜「healthy instance」) |
| 重试与指数退避 | Patterns and best practices | text/101-fm-patterns-and-best-practices.txt:150(搜「Retries」) · text/101-fm-patterns-and-best-practices.txt:159(搜「exponential backoff」) |
| 日志纪律与 PII 红线 | Patterns and best practices | text/101-fm-patterns-and-best-practices.txt:162(搜「always be logged」) · text/101-fm-patterns-and-best-practices.txt:166(搜「central log aggregation」) · text/101-fm-patterns-and-best-practices.txt:173(搜「PII」) |
| fail fast | Patterns and best practices | text/101-fm-patterns-and-best-practices.txt:176(搜「Error handling」) · text/101-fm-patterns-and-best-practices.txt:181(搜「fail fast」) |
| 冗余 | Patterns and best practices | text/101-fm-patterns-and-best-practices.txt:190(搜「Redundancy」) · text/101-fm-patterns-and-best-practices.txt:195(搜「when a failure will occur」) |
| 健康检查与自愈 | Patterns and best practices | text/101-fm-patterns-and-best-practices.txt:211(搜「Health checks」) · text/101-fm-patterns-and-best-practices.txt:225(搜「autohealing」) |
| 熔断器 | Patterns and best practices | text/101-fm-patterns-and-best-practices.txt:231(搜「Circuit breaker」) · text/101-fm-patterns-and-best-practices.txt:242(搜「trips」) |
| 限流器与舱壁 | Patterns and best practices | text/101-fm-patterns-and-best-practices.txt:252(搜「Rate limiter」) · text/102-fm-running-in-production.txt:1(搜「bulkheads」) |
| 生产日志与成本 | Running in production | text/102-fm-running-in-production.txt:11(搜「Logging」) · text/102-fm-running-in-production.txt:32(搜「Cost of logging」) |
| 追踪与监控 | Running in production | text/102-fm-running-in-production.txt:41(搜「Tracing」) · text/102-fm-running-in-production.txt:49(搜「dashboards」) · text/102-fm-running-in-production.txt:53(搜「checkouts」) |
| 滚动更新 | Running in production | text/102-fm-running-in-production.txt:68(搜「Rolling updates」) · text/102-fm-running-in-production.txt:74(搜「stops one instance」) |
| 蓝绿部署 | Running in production | text/102-fm-running-in-production.txt:81(搜「Blue-green」) · text/102-fm-running-in-production.txt:87(搜「smoke tests」) |
| 金丝雀(1%→5%→100%) | Running in production | text/102-fm-running-in-production.txt:100(搜「Canary」) · text/102-fm-running-in-production.txt:105(搜「1%」) · text/102-fm-running-in-production.txt:112(搜「coal miners」) |
| 不可逆数据变更三步 | Running in production | text/102-fm-running-in-production.txt:116(搜「irreversible」) · text/102-fm-running-in-production.txt:123(搜「three distinct steps」) |
| 动态数据迁移 | Running in production | text/102-fm-running-in-production.txt:145(搜「dynamic data migration」) · text/102-fm-running-in-production.txt:153(搜「rollback plan」) |
| 回滚与滚前 | Running in production | text/102-fm-running-in-production.txt:156(搜「Rollback」) · text/103-fm-modern-microservice-patterns.txt:1(搜「afterthought」) · text/103-fm-modern-microservice-patterns.txt:8(搜「rolling forward」) |
| 五个现代模式 | Modern microservice patterns | text/103-fm-modern-microservice-patterns.txt:13(搜「Microservices have evolved」) · text/103-fm-modern-microservice-patterns.txt:22(搜「API gateway」) · text/103-fm-modern-microservice-patterns.txt:25(搜「Saga」) · text/103-fm-modern-microservice-patterns.txt:28(搜「CQRS」) · text/103-fm-modern-microservice-patterns.txt:30(搜「Event Sourcing」) · text/103-fm-modern-microservice-patterns.txt:32(搜「Strangler」) · text/103-fm-modern-microservice-patterns.txt:37(搜「over-decomposition」) |
Footnotes
-
出处:「Chapter 9: Learning about Distributed Application Architecture」第 21–41 段(
text/100-ch09-chapter-9-learning-about-distributed-application.txt:28,搜「First Law」;:32,搜「CORBA」;:33,搜「transparency」;:36,搜「failure modes」)。 ↩ ↩2 ↩3 -
出处:「Chapter 9: Learning about Distributed Application Architecture」第 91–106 段(
text/100-ch09-chapter-9-learning-about-distributed-application.txt:95,搜「single, tightly coupled program」;:103,搜「blue-box-12a」)。 ↩ -
出处:「Patterns and best practices」第 30–46 段(
text/101-fm-patterns-and-best-practices.txt:34,搜「build a house」;:40,搜「no assumptions」;:45,搜「stubs or mocks」)。 ↩ -
出处:「Patterns and best practices」第 47–63 段(
text/101-fm-patterns-and-best-practices.txt:51,搜「stateful component」;:56,搜「much simpler」;:62,搜「boundaries」)。 ↩ ↩2 -
出处:「Patterns and best practices」第 65–103 段(
text/101-fm-patterns-and-best-practices.txt:69,搜「find each other」;:81,搜「nightmare」;:89,搜「DNS service」;:99,搜「registry service」)。 ↩ ↩2 -
出处:「Patterns and best practices」第 104–115 段(
text/101-fm-patterns-and-best-practices.txt:105,搜「packets of data」;:112,搜「application-level routing」)。 ↩ -
出处:「Patterns and best practices」第 117–137 段(
text/101-fm-patterns-and-best-practices.txt:128,搜「external service」;:132,搜「round-robin」;:136,搜「periodically checking」)。 ↩ ↩2 -
出处:「Patterns and best practices」第 190–210 段(
text/101-fm-patterns-and-best-practices.txt:195,搜「when a failure will occur」;:201,搜「multiple instances」;:205,搜「weakest link」)。 ↩ -
出处:「Patterns and best practices」第 139–149 段(
text/101-fm-patterns-and-best-practices.txt:146,搜「no control」;:147,搜「assume the worst」)。 ↩ -
出处:「Patterns and best practices」第 150–159 段(
text/101-fm-patterns-and-best-practices.txt:155,搜「repeated after a short wait time」;:158,搜「exponential backoff」;:158,搜「degraded service」)。 ↩ ↩2 -
出处:「Patterns and best practices」第 176–188 段(
text/101-fm-patterns-and-best-practices.txt:181,搜「fail fast」;:182,搜「STDERR or STDOUT」;:187,搜「checking the input values」)。 ↩ ↩2 -
出处:「Patterns and best practices」第 231–250 段(
text/101-fm-patterns-and-best-practices.txt:232,搜「cascading」;:236,搜「circuit breakers in an electrical system」;:243,搜「trips」)。 ↩ ↩2 -
出处:「Patterns and best practices」第 252–260 段(
text/101-fm-patterns-and-best-practices.txt:256,搜「time window」;:259,搜「integrating rate limiting」)。 ↩ ↩2 -
出处:「Running in production」开头(
text/102-fm-running-in-production.txt:1,搜「bulkheads」)。舱壁一节的正文因转码顺序位于文件 101 末尾与文件 102 开头之间,此处锚取文件 102 的起始句。 ↩ ↩2 -
出处:「Patterns and best practices」第 161–174 段(
text/101-fm-patterns-and-best-practices.txt:162,搜「always be logged」;:166,搜「central log aggregation」;:173,搜「PII」)。 ↩ ↩2 -
出处:「Patterns and best practices」第 211–229 段(
text/101-fm-patterns-and-best-practices.txt:221,搜「periodically polls」;:225,搜「autohealing」;:227,搜「live signals」)。 ↩ ↩2 -
出处:「Running in production」第 11–30 段(
text/102-fm-running-in-production.txt:12,搜「live debug」;:19,搜「STDOUT and STDERR」;:27,搜「exact time」)。 ↩ ↩2 -
出处:「Running in production」第 41–46 段(
text/102-fm-running-in-production.txt:42,搜「funneled through」;:44,搜「time is spent」)。 ↩ ↩2 -
出处:「Running in production」第 48–57 段(
text/102-fm-running-in-production.txt:51,搜「memory and CPU usage」;:53,搜「checkouts」)。 ↩ ↩2 -
出处:「Running in production」第 31–40 段(
text/102-fm-running-in-production.txt:33,搜「Cost of logging」;:38,搜「Balancing」)。 ↩ -
出处:「Running in production」第 116–132 段(
text/102-fm-running-in-production.txt:123,搜「three distinct steps」;:125,搜「backward-compatible」;:126,搜「clean up」)。 ↩ ↩2 -
出处:「Running in production」第 134–154 段(
text/102-fm-running-in-production.txt:145,搜「dynamic data migration」;:151,搜「replication or mirroring」;:153,搜「rollback plan」)。 ↩ ↩2 -
出处:「Running in production」第 156–164 段与「Modern microservice patterns」开头(
text/102-fm-running-in-production.txt:163,搜「not a question」;text/103-fm-modern-microservice-patterns.txt:1,搜「afterthought」;:8,搜「rolling forward」)。 ↩ ↩2 ↩3 -
出处:「Modern microservice patterns」第 12–39 段(
text/103-fm-modern-microservice-patterns.txt:18,搜「single business capability」;:22,搜「API gateway」;:25,搜「Saga」;:28,搜「CQRS」;:30,搜「Event Sourcing」;:32,搜「Strangler」;:37,搜「over-decomposition」)。 ↩ ↩2 -
出处:「Patterns and best practices」第 11–20 段(
text/101-fm-patterns-and-best-practices.txt:16,搜「significantly simpler」;:18,搜「not "simple"」)。 ↩ -
出处:「Running in production」第 100–115 段(
text/102-fm-running-in-production.txt:105,搜「1%」;:108,搜「5%」;:112,搜「coal miners」)。 ↩ ↩2 -
出处:「Running in production」第 68–79 段(
text/102-fm-running-in-production.txt:74,搜「stops one instance」;:76,搜「monitored for some time」;:79,搜「No downtime」)。 ↩ -
出处:「Running in production」第 81–97 段(
text/102-fm-running-in-production.txt:82,搜「called blue」;:87,搜「smoke tests」;:91,搜「reconfigured」)。 ↩