抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

分布式系统不是“把单体拆成多个服务”,而是在不可靠网络、部分失败、无全局时钟、并发执行的前提下,仍然让系统对外表现出可预测的行为。架构师的工作不是消灭故障,而是设计故障发生时的行为、边界和恢复路径。

本文以资深分布式系统架构师视角重写,按「理论 → 复制/共识 → 数据分布 → 事务 → 通信 → 弹性 → 多地域 → 决策」展开,重点讲清 trade-off、失败模式和落地取舍。

分布式系统的本质

定义

分布式系统是若干独立计算机的集合,这些计算机对用户表现为一个系统。它通过消息传递协作,没有共享内存,也没有可靠的全局时钟。

架构上要接受四个事实:

  1. 网络不可靠:延迟无上限、会丢包、会分区、会重复、会乱序。
  2. 节点会部分失败:不是“全好/全坏”,而是部分功能异常、变慢、假死。
  3. 没有全局时钟:机器时间会漂移,NTP 也会跳变。
  4. 并发是常态:多个客户端、多个副本、多个区域同时操作。

分布式计算的八个谬误

  1. 网络是可靠的。
  2. 延迟为零。
  3. 带宽无限。
  4. 网络是安全的。
  5. 拓扑不会变化。
  6. 只有一个管理员。
  7. 传输成本为零。
  8. 网络是同构的。

几乎所有分布式事故都能映射到其中一条。

架构师关注什么

  • 不变量(Invariant):系统在任何故障下都不能破坏的约束。
  • 失败模式:超时、分区、脑裂、重复、乱序、时钟漂移、磁盘损坏。
  • 一致性语义:读到自己写?读到最新?跨对象事务?
  • 爆炸半径:一个组件/租户/区域故障影响多大。
  • 恢复路径:降级、重试、回滚、补偿、切流、恢复。
  • 可运维性:可观测性、容量、变更、演练、成本。

核心理论

CAP

CAP 三个字母:

  • C(Consistency):线性一致性(linearizability),不是 ACID 的 C。
  • A(Availability):每个非故障节点在合理时间内返回非错误响应。
  • P(Partition Tolerance):网络分区时系统仍能继续运行。

关键点:

  • 分布式系统必须容忍分区,所以不是“从 CAP 里挑两个”,而是“分区发生时,选 C 还是 A”。
  • CAP 不考虑延迟;PACELC 补充了这一点。
  • “CA 系统”通常指单机/共享磁盘,不是真正的分布式系统。
  • CP:分区时少数派拒绝服务,保证一致性。
  • AP:分区时仍可读写,接受冲突/最终一致。

PACELC

如果 Partition:在 Availability 和 Consistency 之间选
否则(Else):在 Latency 和 Consistency 之间选

例子:

  • Dynamo/Cassandra:PA/EL,分区时偏可用,正常时偏低延迟。
  • Spanner:PC/EC,分区时偏一致,正常时也偏一致(TrueTime + commit-wait)。
  • 大多数主从数据库:PC/EC 或 PC/EL,取决于同步复制和读策略。

FLP 不可能性

在异步系统中,即使只有一个节点崩溃,也不存在同时保证安全性和活性的确定性共识算法。

工程含义:

  • 共识需要超时、随机化、部分同步假设。
  • 超时会导致误判,可能引发不必要的选举/切换。
  • “理论不可能”不等于“工程做不到”;工程用假设和故障检测换取可用性。

ACID 与 BASE

  • ACID:原子性、一致性、隔离性、持久性。适合强事务场景。
  • BASE:基本可用、软状态、最终一致。适合高可用/大规模场景。
  • 两者不是非黑即白,现代系统通常是“局部强一致 + 全局最终一致”。

可用性数学

串联依赖:

A_total = A1 × A2 × ... × An

并联冗余:

A_total = 1 - (1-A1)(1-A2)...(1-An)

含义:

  • 同步调用链越长,整体可用性越低。
  • 冗余能提升可用性,但相关故障(同机房、同配置、同版本)会削弱效果。
  • 99.99% 的年度不可用约 52.6 分钟;99.999% 约 5.26 分钟。
  • 不要只看单组件可用性,要看端到端用户旅程。

一致性与隔离级别

一致性模型谱系

模型 保证 典型场景
线性一致(Linearizable) 所有操作像在单点上按实时顺序执行 分布式锁、元数据、强一致 KV
顺序一致 所有节点看到相同顺序,但不要求实时 某些复制日志
因果一致 尊重因果关系的操作顺序 协作编辑、消息
最终一致 无新写入时副本最终收敛 DNS、缓存、Cassandra
读己之写 客户端能读到自己的写 会话保证
单调读 不会读到更旧的数据 会话保证
单调写 同一客户端的写按顺序生效 会话保证

Linearizability vs Serializability

这两个词经常被混淆:

  • Linearizability:单对象/单操作的实时一致性;操作在调用和返回之间某个点原子生效。
  • Serializability:多对象事务的隔离性;事务等价于某个串行顺序。
  • Strict Serializability:两者结合,既实时顺序又事务串行。

CAP 的 C 是 linearizability;数据库隔离级别的 serializable 是事务语义。

隔离级别与异常

隔离级别 脏读 不可重复读 幻读 写偏斜
Read Uncommitted 可能 可能 可能 可能
Read Committed 否 可能 可能 可能
Repeatable Read 否 否 可能 可能
Snapshot Isolation 否 否 否 可能
Serializable 否 否 否 否

注意:

  • 不同数据库对 Repeatable Read 的实现不同(MySQL InnoDB 的 RR 也防幻读,但仍有写偏斜)。
  • Snapshot Isolation 不能防写偏斜(write skew)。
  • 跨服务事务通常没有数据库隔离级别,只能靠业务补偿。

Quorum 一致性

N = 副本数
W = 写成功所需副本数
R = 读所需副本数
R + W > N => 读写集合有交集

但 R+W>N 只是“有机会读到最新”,不自动等于线性一致:

  • 需要版本/冲突解决。
  • 需要 read repair 或 anti-entropy。
  • 需要处理 sloppy quorum/hinted handoff。
  • 需要处理并发写冲突(LWW 会丢数据)。

一致性是产品决策

架构文档必须写清楚:

  • 哪些读要求线性一致?
  • 哪些可以 stale?
  • 跨实体事务是否需要?
  • 冲突如何解决?
  • 失败时是拒绝写还是接受后补偿?

时间顺序与因果

时钟问题

  • 墙上时钟(wall clock):会漂移、会跳变,不能用于排序事件。
  • 单调时钟(monotonic):适合测持续时间,不表示绝对时间。
  • NTP 只能减小偏差,不能消除。
  • 时钟回拨会导致 ID 重复、租约误判、事件乱序。

逻辑时钟

  • Lamport clock:每个事件递增,消息携带时钟,接收方取 max+1;给出偏序,不区分并发。
  • Vector clock:每个节点维护向量,能检测因果关系和并发冲突;适合冲突检测,不适合大规模存储。
  • Hybrid Logical Clock(HLC):结合物理时间和逻辑计数,常用于分布式数据库。
  • TrueTime:Spanner 使用有界不确定性的时间区间,通过 commit-wait 实现外部一致性。

Happens-before

  • 同一进程内事件有序。
  • 发送消息 happens-before 接收消息。
  • 传递闭包。
  • 并发事件没有因果关系,需要冲突解决策略。

租约与 fencing

分布式锁/Leader 租约常见错误:

  • 持有者 GC 停顿,租约过期,另一个节点拿到锁。
  • 旧持有者恢复后继续写,造成双写。

解决:

  • Fencing token:每次拿锁获得单调递增 token,存储端拒绝旧 token 的写。
  • 租约时间要大于最大 GC/网络抖动,但不能无限大。
  • 不要只依赖“锁服务说我还持有锁”。

分布式 ID

  • UUIDv4:随机,无顺序。
  • UUIDv7:时间前缀 + 随机,趋势有序。
  • Snowflake:时间 + 机器 + 序列;依赖时钟,要处理回拨。
  • 数据库序列/号段:集中式但简单。
  • 不要用墙上时间做唯一性保证。

复制模型

单 Leader

最常见:

Client -> Leader -> Followers
  • 写走 Leader,读可走 Leader 或 Follower。
  • 同步复制:等所有/多数副本确认,延迟高、强一致。
  • 半同步:至少一个 Follower 确认。
  • 异步复制:Leader 本地提交即返回,故障可能丢数据。
  • Follower 读可能 stale;需要 read-your-writes 时读 Leader 或跟踪位点。

故障切换:

  • 选新 Leader 需要共识/仲裁。
  • 旧 Leader 可能还有未复制数据,需要日志比对/截断。
  • 脑裂需要 fencing/quorum。

多 Leader

  • 多个区域都可写,降低写延迟。
  • 冲突解决:LWW、版本向量、CRDT、业务合并。
  • LWW 依赖时钟,可能静默丢写。
  • 适合地理分布式、离线优先、协作编辑。

Leaderless

Dynamo 风格:

  • 客户端直接写多个副本,quorum 确认。
  • 读多个副本,版本/read repair 解决冲突。
  • hinted handoff:临时节点代收。
  • anti-entropy:Merkle tree 比较差异。
  • 最终一致;LWT(轻量事务)通常用 Paxos。

状态机复制

  • 所有副本按相同顺序应用相同日志。
  • 状态机必须确定性。
  • 日志复制 + 快照 + 成员变更。
  • Raft/Paxos/Zab 都是状态机复制的实现。

复制 lag 的后果

  • 读到旧数据。
  • 读己之写失败。
  • 单调读被破坏。
  • 故障切换丢最近写。
  • 跨区域延迟放大。

解决:

  • 读 Leader/同步副本。
  • 会话粘性 + 位点。
  • 版本号/时间戳。
  • 业务接受最终一致并设计补偿。

共识与协调

共识要解决什么

  • 多个节点对“下一个日志条目是什么”达成一致。
  • Leader 选举。
  • 成员变更。
  • 线性一致读。
  • 分布式锁/租约。

Paxos / Multi-Paxos

  • Paxos 解决单值共识。
  • Multi-Paxos 用稳定 Leader 复制日志。
  • 两阶段:prepare/promise、accept/accepted。
  • 工程实现复杂,Raft 更易理解。

Raft

  • 强 Leader:日志只能从 Leader 流向 Follower。
  • 任期 term + 日志 index。
  • 选举:随机超时、RequestVote、多数派。
  • 日志复制:AppendEntries、prevLogIndex/term、commitIndex。
  • 安全性:Leader 拥有所有已提交日志;多数派交集。
  • 成员变更:一次一个,joint consensus 或单步变更。
  • 快照:压缩日志,追赶慢节点。
  • 线性一致读:ReadIndex/Leader lease。

Zab

ZooKeeper 的原子广播:

  • Leader activation + active messaging。
  • zxid = epoch + counter。
  • 新 Leader 必须拥有最高 zxid。
  • 类似两阶段提交的广播。

线性一致读

  • ReadIndex:Leader 确认仍是多数派 Leader,等待 apply 到 read index。
  • Leader lease:租约内本地读;依赖时钟,租约过期/时钟漂移有风险。
  • Follower 读要转发或等待同步位点。
  • 不要在没有 quorum 确认的情况下声称线性一致。

脑裂与 fencing

  • 网络分区可能产生两个“自认为 Leader”的节点。
  • Quorum 保证只有一个多数派能提交。
  • 旧 Leader 必须被 fencing:拒绝其写、吊销租约、STONITH、token 校验。
  • 存储层要能拒绝旧 epoch/token 的写。

成员变更

  • 一次只加/删一个节点。
  • 新节点先 Learner/非投票,追上后再加入。
  • 成员变更期间要保持 quorum。
  • 变更要有审计和回滚方案。

故障检测

  • 心跳 + 超时。
  • Phi accrual failure detector:输出怀疑度,不是二元。
  • SWIM/gossip:大规模成员管理。
  • 超时太短误判,太长恢复慢;要按网络 RTT 和 GC 停顿调。

分区与数据分布

分区键

选择分区键的目标:

  • 均匀分布,避免热点。
  • 查询局部性,减少跨分区。
  • 业务边界清晰。
  • 可扩展,避免未来无法 rehash。

常见:

  • Range:有序扫描方便,容易热点。
  • Hash:均匀,范围查询差。
  • Consistent hashing:节点增减影响小,虚拟节点均衡。
  • Rendezvous hashing:无状态、易实现。
  • 目录式:查表定位,灵活但有元数据服务。

热点与倾斜

  • 大 V/明星用户/热门商品会造成 hot key。
  • 解决:加盐、拆分、二级分片、缓存、请求合并。
  • 监控每个分区的 QPS、大小、延迟。
  • 不要只看平均,长尾分区决定整体。

重分片

  • 预分片:一开始就分足够多,避免后期迁移。
  • 在线迁移:双写/回放/校验/切读/清理。
  • 迁移期间保持幂等、可回滚。
  • 跨分区查询用 scatter-gather,注意尾延迟和放大。

二级索引

  • 本地索引:按文档分区,查询要 fan-out。
  • 全局索引:按索引值分区,写入要维护多份。
  • 全局索引最终一致,读可能看不到最新写。
  • 需要按查询模式设计,不能事后随意加。

地理分区

  • 按用户/租户把数据放在最近区域。
  • 跨区域事务代价高,尽量本地化。
  • 数据驻留/合规要求可能强制分区。

分布式事务

本地事务

单库 ACID 最简单。优先把事务限制在单个分区/单库。

2PC

Coordinator -> Prepare -> Participants
Participants -> Yes/No
Coordinator -> Commit/Abort

问题:

  • 协调者故障导致参与者阻塞。
  • 网络分区时可能长时间锁资源。
  • 启发式提交/回滚导致不一致。
  • 性能/可用性差。

3PC

  • 增加 pre-commit 阶段,减少阻塞。
  • 仍不能很好处理网络分区,工程使用少。

Saga

  • 每个步骤有本地事务和补偿动作。
  • 编排(orchestration)或协同(choreography)。
  • 没有隔离性:中间状态对外可见。
  • 补偿必须幂等、可重试、可处理“补偿失败”。
  • 适合长事务、跨服务业务流程。

TCC

  • Try:预留资源。
  • Confirm:确认。
  • Cancel:取消。
  • 需要业务实现预留/释放,空回滚/悬挂/幂等要处理。

Outbox / Inbox

避免“写数据库 + 发消息”双写不一致:

本地事务:写业务表 + 写 outbox
后台/CDC:读取 outbox -> 发消息 -> 标记已发
消费者:inbox 去重 -> 处理
  • Outbox 保证“至少一次”。
  • Inbox/幂等键保证消费幂等。
  • 端到端 exactly-once 通常是“至少一次 + 幂等”。

Exactly-once 的真相

  • 网络无法保证“恰好一次”投递。
  • 工程上是 at-least-once + 去重/幂等。
  • Kafka 事务能保证 Kafka 内部的原子写和幂等生产,但跨外部系统仍需幂等。
  • 幂等键、唯一约束、版本号、去重表是常用手段。

分布式锁

  • Redis 锁:单实例 + 过期时间;主从切换/GC 停顿可能失效。
  • Redlock 在时钟/网络假设下有争议,不适合作为强正确性保证。
  • ZooKeeper/etcd 租约 + fencing token 更可靠。
  • 锁的正确性最终要靠存储端 fencing,而不是锁服务口头承诺。

通信与事件驱动

RPC 的坑

  • 超时不等于失败:请求可能已执行。
  • 重试可能重复执行。
  • 网络延迟、序列化、线程池、连接池都会放大故障。
  • 同步调用链越长,可用性越低。
  • 需要 deadline、幂等、重试预算、熔断、背压。

同步 vs 异步

模式 优点 缺点
同步 RPC 简单、实时 耦合、级联故障、尾延迟
消息队列 解耦、削峰、可重放 最终一致、重复/乱序、运维复杂
事件流 可回放、多消费者 schema/顺序/治理复杂

消息语义

  • at-most-once:可能丢。
  • at-least-once:可能重复。
  • exactly-once:通常是分区内 + 事务 + 幂等。
  • 顺序:通常只保证分区内有序。
  • 消费组:分区分配、rebalance、位点提交。
  • 死信队列、毒消息、重试主题、延迟队列。

事件设计

  • 事件是不可变事实,命名用过去式。
  • schema 演进要兼容:新增可选字段、避免破坏性修改。
  • 事件要有 ID、时间、版本、trace ID、幂等键。
  • 生产者/消费者契约要测试。
  • 不要用事件传递大对象;传引用/ID。

API 与事件

  • 命令(command):要求做某事,通常点对点。
  • 事件(event):已经发生的事实,可广播。
  • 查询(query):读。
  • 不要用事件模拟同步 RPC 的返回。

流量集群与会话

L4 与 L7 负载均衡

层 依据 特点
L4 IP/端口/协议 性能高、看不懂 HTTP;适合 TCP/UDP
L7 Host/Path/Header/Cookie 路由灵活、可做灰度/认证/限流;开销更大

常见算法:

  • Round Robin / Weighted Round Robin:简单,适合同质节点。
  • Least Connections:适合长连接/请求耗时差异大。
  • Least Response Time / EWMA:动态权重,适合异构节点。
  • IP Hash / Consistent Hash:会话粘性/缓存局部性,但节点变化会迁移。
  • Random:简单,大规模下近似均匀。

健康检查:

  • 主动探活:HTTP/TCP/gRPC 健康检查。
  • 被动摘除:连续失败/超时/5xx 触发 outlier detection。
  • 区分 liveness/readiness,避免把“正在启动”当故障。
  • 摘除/恢复要平滑,避免抖动导致流量反复切换。

HA 集群

模式 说明 代价
Active-Passive 主节点服务,备节点待命 资源利用率低,切换有停机
Active-Active 多节点同时服务 会话/数据一致性复杂
N+1 多一个冗余节点 成本可控
多活 多机房/多区域同时服务 数据复制/冲突/流量调度复杂

关键:

  • VIP + VRRP/keepalived 做入口漂移。
  • Quorum/witness 防止双主。
  • Fencing:旧主必须能被隔离/拒绝写。
  • 切换后容量是否足够?缓存是否冷?连接是否重建?
  • 定期演练切换,不能只靠“配置了 HA”。

会话

会话保持 vs 会话共享

  • 会话保持(sticky):同一客户端落到同一后端;实现简单,但节点故障会丢会话、扩缩容不均衡。
  • 会话共享:会话存外部存储(Redis/DB),后端无状态;更适合弹性扩缩容。

常见方案

方案 优点 缺点
Cookie 存 Session 无额外存储 大小/安全/泄漏风险
数据库 简单、持久 性能/扩展差
Session 复制 无需外部存储 网络放大、节点多时不可行
Redis/Memcached 性能好、集中 多一次依赖/网络调用
JWT/无状态 Token 无服务端会话 撤销/续期/泄露处理复杂

设计原则

  • 优先无状态服务,会话外置。
  • 会话 ID 要随机、不可预测,登录后轮换。
  • Cookie 设置 HttpOnly、Secure、SameSite。
  • 会话存储要有 TTL、容量、故障降级。
  • 不要用会话粘性掩盖有状态设计。

全局流量

  • DNS/GeoDNS:按地域/延迟调度,受 TTL 和缓存影响。
  • Anycast:同一 IP 多地域宣告,就近接入;故障切换快,但状态同步复杂。
  • Global Load Balancer:健康检查 + 地域路由 + 故障转移。
  • 多集群/多区域要考虑数据复制、冲突解决、容量和合规。

缓存与一致性

缓存模式

模式 说明
Cache-Aside 应用读缓存, miss 读 DB 回填
Read-Through 缓存层负责回源
Write-Through 写缓存同时写 DB
Write-Behind 写缓存异步落 DB
Refresh-Ahead 过期前异步刷新

失效与一致性

  • TTL 是最简单的最终一致。
  • 删除缓存 vs 更新缓存:删除更常见,避免并发写覆盖。
  • 延迟双删:写 DB 后删缓存,延迟再删一次;仍不是强一致。
  • CDC/binlog 订阅失效更可靠。
  • 缓存与 DB 双写不一致时,以 DB 为准 + 重试/补偿。

缓存故障

  • 穿透:查不存在的数据,打爆 DB;用空值缓存/布隆过滤器。
  • 击穿:热点 key 过期,大量请求回源;用 singleflight/互斥锁/逻辑过期。
  • 雪崩:大量 key 同时过期;TTL 加随机、分级缓存、限流。
  • 热 key:单 key QPS 过高;本地缓存、分片、请求合并。
  • 大 key:value 过大;拆分、压缩、异步加载。

CDN 缓存

  • 缓存键包含 host/path/query/Vary。
  • 不要缓存带用户信息的响应。
  • 缓存投毒/欺骗:未纳入 key 的 header、Host、路径归一化。
  • 失效/刷新要可控,避免全站 purge。

弹性与故障处理

超时

  • 每个远程调用都要有超时。
  • 超时要小于上游 deadline,留出处理时间。
  • 超时预算要逐层传递,不要每层都 30s。
  • 超时后要区分“未执行/已执行/未知”。

重试

  • 只重试幂等/可安全重试的操作。
  • 指数退避 + jitter,避免同步重试风暴。
  • 限制重试次数/总时间/重试预算。
  • 重试要考虑下游容量,不能把故障放大。
  • 熔断/半开避免持续打爆。

熔断、隔离、限流

  • Circuit Breaker:失败率高时快速失败,半开探测。
  • Bulkhead:线程池/连接池/租户隔离,防止一个依赖拖垮全部。
  • Rate Limit:保护自己和下游;按用户/租户/接口。
  • Load Shedding:过载时主动丢弃低优先级请求。
  • Backpressure:队列有界,生产者感知消费能力。
  • Graceful Degradation:关闭非核心功能,保核心链路。

故障模式

  • Gray failure:节点“看起来活着但功能异常”,最难发现。
  • Metastable failure:系统进入自我维持的坏状态(重试风暴、队列堆积)。
  • Retry storm:故障触发重试,重试放大故障。
  • Connection pool exhaustion:慢下游占满连接。
  • Thread pool exhaustion:阻塞调用占满线程。
  • Queue buildup:消费者跟不上,延迟/内存暴涨。
  • GC pause:租约过期、心跳超时、误判故障。

容量与排队

Little’s Law:

L = λ × W
  • L:系统中的请求数。
  • λ:到达率。
  • W:平均停留时间。

利用率接近 100% 时,排队时间非线性上升。生产要留 headroom,通常 70%-80% 是健康区间。

可观测性与运维

三支柱

  • Metrics:趋势、告警、容量。
  • Logs:细节、审计、排障。
  • Traces:跨服务调用链、延迟分解。

补充:事件、profiling、持续剖析、审计日志。

关键实践

  • 统一 trace ID / request ID,跨服务传播。
  • 结构化日志,避免高基数标签。
  • 指标用 RED/USE:Rate、Errors、Duration;Utilization、Saturation、Errors。
  • SLO/错误预算驱动告警,而不是所有指标都告警。
  • 采样要有策略,错误/慢请求优先保留。
  • 日志/指标/追踪要能关联到部署版本、区域、租户。

运维

  • 变更小步、可回滚、可灰度。
  • Runbook、值班、升级路径。
  • 演练:故障注入、混沌工程、Game Day。
  • 复盘:时间线、影响、根因、行动项。
  • 容量规划:峰值、增长、故障切换后的容量。
  • 成本:跨区域流量、存储、复制、观测数据。

架构模式

SOA、微服务与模块化

  • SOA:面向服务架构,强调服务复用、契约、企业级集成,常见 ESB/服务目录/集中治理。优点是复用和标准化,代价是 ESB 容易成为瓶颈、集中治理重、服务边界偏粗。
  • 微服务:SOA 的一种演进,强调按业务能力拆分、独立部署、去中心化治理、团队自治。代价是分布式事务、网络、可观测性、运维复杂度。
  • 模块化单体:单进程部署,但内部按模块/限界上下文隔离,事务简单、演进成本低。很多团队从模块化单体开始,比直接微服务更稳。
  • 架构风格不是成熟度标签;按业务边界、变更频率、团队规模、一致性要求选择。

单体、模块化单体、微服务

  • 单体:简单、事务一致、部署简单;适合早期/小团队。
  • 模块化单体:清晰边界、单进程事务;很多场景优于过早微服务。
  • 微服务:独立部署/扩展/团队自治;代价是分布式事务、网络、运维、观测复杂度。
  • 不要为了“架构先进”拆服务;按业务边界、变更频率、团队边界拆。

DDD 与边界

  • Bounded Context 是服务边界的候选。
  • 聚合定义事务边界。
  • 领域事件用于跨边界集成。
  • 共享数据库是反模式;每个服务拥有自己的数据。

CQRS / Event Sourcing

  • CQRS:读写模型分离,适合读多写多、复杂查询。
  • Event Sourcing:以事件为事实源,可回放/审计;代价是 schema、快照、重放、隐私删除。
  • 不是所有系统都需要;引入前评估复杂度和团队能力。

Saga / Outbox / CDC

  • Saga 处理跨服务长事务。
  • Outbox 解决数据库与消息双写。
  • CDC 从数据库变更日志派生事件,减少应用双写。
  • 三者常组合使用。

网关、BFF、Service Mesh

  • API Gateway:入口路由、认证、限流、WAF。
  • BFF:为前端定制聚合 API。
  • Service Mesh:mTLS、流量治理、可观测性,把通用能力下沉到 sidecar/数据面。
  • 控制面/数据面分离:控制面下发配置,数据面承载流量。

Cell-based 架构

  • 把系统分成多个独立 cell,每个 cell 自带完整依赖/数据分片。
  • 故障/升级限制在 cell 内,降低爆炸半径。
  • 代价是资源冗余、跨 cell 查询、路由复杂度。
  • 适合大规模多租户/高可用系统。

Serverless

  • 按需伸缩、免运维;冷启动、执行时长、状态、厂商锁定是代价。
  • 适合事件驱动、突发流量、胶水逻辑;不适合长事务/重计算/强一致本地状态。

多地域与容灾

RPO / RTO

  • RPO:能容忍丢失多少数据(时间)。
  • RTO:能容忍多长恢复时间。
  • 不同业务不同目标;先分级,再设计。

模式

模式 说明 RTO/RPO
Backup/Restore 备份到异地,故障后恢复 高/高
Pilot Light 核心数据/最小环境常备 中/中
Warm Standby 缩容但可用的备用环境 中低/低
Active-Passive 主备切换 低/低-中
Active-Active 多地域同时服务 低/取决于复制

数据复制

  • 同步复制:强一致但跨地域延迟高。
  • 异步复制:低延迟但可能丢数据。
  • 多主:冲突解决复杂。
  • 全局数据库(Spanner/CockroachDB)用共识 + 时钟/事务,代价高。

故障切换

  • DNS/Global LB 切流。
  • 数据提升/角色切换。
  • 防止脑裂:quorum、fencing、witness。
  • 切流后容量是否足够?缓存是否冷?连接是否重建?
  • 切换要演练,不能只写在文档里。

数据驻留

  • 合规要求数据留在特定区域。
  • 分区/租户路由要支持数据位置。
  • 跨区域备份也要考虑合规。

安全与多租户

零信任

  • 不默认信任内网。
  • 服务间 mTLS、身份认证、最小权限。
  • 短期凭据、密钥轮换、审计。
  • 供应链安全:镜像签名、SBOM、准入控制。

身份与授权

  • 用户认证:OIDC/OAuth2。
  • 服务身份:mTLS、SPIFFE、云 IAM。
  • 授权:RBAC/ABAC/策略即代码。
  • 每次请求都要授权,不能只靠网关。

多租户隔离

  • 数据隔离:行级/库级/实例级。
  • 资源隔离:配额、限流、优先级、节点池。
  • 故障隔离:cell、命名空间、租户分片。
  • 噪声邻居:CPU/内存/连接/IO 隔离。
  • 审计和计费。

加密

  • 传输 TLS/mTLS。
  • 静态加密 + KMS/HSM。
  • 密钥轮换、权限分离。
  • 不要把密钥放代码/镜像/Git。

架构决策方法

先定义 NFR

  • 延迟目标(P50/P95/P99)。
  • 可用性/SLO。
  • 一致性/持久性。
  • 吞吐/容量。
  • 成本预算。
  • 安全/合规。
  • 可运维性/团队能力。

Trade-off 矩阵

决策 收益 代价
强一致复制 数据正确 延迟/可用性
多区域 active-active 低延迟/容灾 冲突/复杂度/成本
微服务 自治/独立扩展 分布式事务/运维
事件驱动 解耦/削峰 最终一致/重复/顺序
缓存 性能 一致性/失效复杂
分片 扩展 跨片查询/事务

ADR

Architecture Decision Record 记录:

  • 背景/问题。
  • 选项和权衡。
  • 决策。
  • 后果/风险。
  • 复审条件。

架构不是一次决策,而是持续演进;ADR 让后人知道“为什么这样”。

设计评审问题

  • 故障时会发生什么?
  • 哪个组件是单点?
  • 数据一致性语义是什么?
  • 重试/重复是否安全?
  • 超时/背压/限流是否完整?
  • 如何观测、告警、回滚?
  • 爆炸半径多大?
  • 成本随规模如何增长?
  • 团队能否运维?

典型系统案例

etcd

  • Raft 复制日志,线性一致读写。
  • Lease/Watch 用于服务发现、选主。
  • 适合元数据/协调,不适合高吞吐业务数据。
  • 关键:quorum、compaction、defrag、备份恢复。

Kafka

  • 分区日志,分区内有序,跨分区无序。
  • ISR、leader epoch、副本拉取。
  • 幂等 producer + 事务支持 Kafka 内部 exactly-once。
  • 消费组 rebalance、位点提交、重复消费处理。
  • 关键:分区键、保留策略、消费者幂等。

Cassandra / Dynamo

  • Leaderless、quorum、hinted handoff、anti-entropy。
  • 可调一致性(ONE/QUORUM/ALL)。
  • LWW/冲突解决;LWT 用 Paxos。
  • 适合高写入/多地域,代价是查询受限、最终一致。

Spanner

  • Paxos 复制组 + TrueTime。
  • 外部一致性(external consistency)。
  • commit-wait 利用时间不确定性。
  • 代价:专用基础设施、延迟、成本。

ZooKeeper

  • Zab、znode、session、watch。
  • 临时/顺序节点做选主/锁/队列。
  • 适合协调,不适合大数据量。
  • 关键:quorum、watch 一次性、会话过期。

Redis

  • 单线程/多线程 IO、内存存储。
  • 主从复制、Sentinel、Cluster。
  • 缓存/锁/会话;持久化有 RDB/AOF。
  • 分布式锁要 fencing,不能只靠 Redis。

S3

  • 对象存储,现代 S3 对 PUT/DELETE/GET/LIST 提供强一致性;早期版本曾有最终一致语义。
  • 高持久性、按对象扩展。
  • 适合大对象/备份/数据湖,不适合低延迟事务。

反模式与检查清单

反模式

  • 分布式单体:多个服务共享数据库、同步调用链、一起发布。
  • 共享数据库:服务边界形同虚设,schema 耦合。
  • 没有超时/重试预算:故障放大。
  • 无幂等的重试:重复扣款/重复下单。
  • 双写数据库和消息:不一致。
  • 用分布式锁保证正确性:没有 fencing 的锁不可靠。
  • 无限队列:内存爆炸/延迟失控。
  • 缓存当数据库:丢数据/不一致。
  • 过度分片/过度微服务:复杂度超过收益。
  • 多区域 active-active 没有冲突策略:数据分叉。
  • 只做备份不做恢复演练:备份不可用。
  • 没有可观测性:故障靠猜。
  • 用墙上时钟排序事件:时钟回拨导致乱序。
  • 忽略 gray failure/metastable failure:重试风暴。

检查清单

  • 每个远程调用有超时、重试策略、幂等性说明
  • 明确一致性/隔离级别和读己之写语义
  • 关键路径有背压、限流、熔断、降级
  • 写操作有幂等键/去重/补偿
  • 数据库与消息使用 outbox/CDC,避免双写
  • 分片键均匀、可扩展、可重分片
  • 共识/选主有 quorum、fencing、脑裂防护
  • 时钟/租约/ID 不依赖不可靠的墙上时间
  • 多地域有 RPO/RTO、切流、回滚、演练
  • 缓存有穿透/击穿/雪崩/热 key 方案
  • 有 SLO、告警、追踪、日志、容量规划
  • 有爆炸半径评估和 cell/租户隔离
  • 有 ADR 记录关键 trade-off
  • 有故障演练和恢复演练
  • 团队能运维当前复杂度

参考资料