分布式系统不是“把单体拆成多个服务”,而是在不可靠网络、部分失败、无全局时钟、并发执行的前提下,仍然让系统对外表现出可预测的行为。架构师的工作不是消灭故障,而是设计故障发生时的行为、边界和恢复路径。
本文以资深分布式系统架构师视角重写,按「理论 → 复制/共识 → 数据分布 → 事务 → 通信 → 弹性 → 多地域 → 决策」展开,重点讲清 trade-off、失败模式和落地取舍。
- 分布式系统的本质
- 核心理论
- 一致性与隔离级别
- 时间顺序与因果
- 复制模型
- 共识与协调
- 分区与数据分布
- 分布式事务
- 通信与事件驱动
- 流量集群与会话
- 缓存与一致性
- 弹性与故障处理
- 可观测性与运维
- 架构模式
- 多地域与容灾
- 安全与多租户
- 架构决策方法
- 典型系统案例
- 反模式与检查清单
- 参考资料
分布式系统的本质
定义
分布式系统是若干独立计算机的集合,这些计算机对用户表现为一个系统。它通过消息传递协作,没有共享内存,也没有可靠的全局时钟。
架构上要接受四个事实:
- 网络不可靠:延迟无上限、会丢包、会分区、会重复、会乱序。
- 节点会部分失败:不是“全好/全坏”,而是部分功能异常、变慢、假死。
- 没有全局时钟:机器时间会漂移,NTP 也会跳变。
- 并发是常态:多个客户端、多个副本、多个区域同时操作。
分布式计算的八个谬误
- 网络是可靠的。
- 延迟为零。
- 带宽无限。
- 网络是安全的。
- 拓扑不会变化。
- 只有一个管理员。
- 传输成本为零。
- 网络是同构的。
几乎所有分布式事故都能映射到其中一条。
架构师关注什么
- 不变量(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 之间选 |
例子:
- 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 = 副本数 |
但 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 |
问题:
- 协调者故障导致参与者阻塞。
- 网络分区时可能长时间锁资源。
- 启发式提交/回滚导致不一致。
- 性能/可用性差。
3PC
- 增加 pre-commit 阶段,减少阻塞。
- 仍不能很好处理网络分区,工程使用少。
Saga
- 每个步骤有本地事务和补偿动作。
- 编排(orchestration)或协同(choreography)。
- 没有隔离性:中间状态对外可见。
- 补偿必须幂等、可重试、可处理“补偿失败”。
- 适合长事务、跨服务业务流程。
TCC
- Try:预留资源。
- Confirm:确认。
- Cancel:取消。
- 需要业务实现预留/释放,空回滚/悬挂/幂等要处理。
Outbox / Inbox
避免“写数据库 + 发消息”双写不一致:
本地事务:写业务表 + 写 outbox |
- 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
- 有故障演练和恢复演练
- 团队能运维当前复杂度