分布式系统与高可用
系统变大后,多出来的不是“更多服务器”,而是更多失败方式。一次本地函数调用变成网络请求,一次数据库事务变成多个服务之间的状态协调,一台机器故障可能影响整条链路。架构设计的目标不是消灭故障,而是让系统在局部失败时仍然可预测、可恢复。
1. 什么时候需要改变架构
跳转到“1. 什么时候需要改变架构”单体不是落后,微服务也不是成熟标志。选择取决于:
- 团队规模和所有权边界。
- 模块是否需要独立发布或扩缩容。
- 故障是否需要隔离。
- 数据量、请求量和延迟目标。
- 部署、观测和运维能力。
- 变更频率与回滚成本。
如果一个系统只有一个团队、一个数据库、业务边界还不稳定,模块化单体通常比拆成十几个服务更容易演进。拆服务会把本地调用、事务和代码依赖变成分布式问题。
1.1 单体、模块化单体与微服务
跳转到“1.1 单体、模块化单体与微服务”| 形态 | 优势 | 代价 |
|---|---|---|
| 单体 | 部署简单、调用直接、事务容易 | 模块边界易腐化,整体扩缩容 |
| 模块化单体 | 保留单部署优势,边界更清楚 | 仍共享进程与数据库,隔离有限 |
| 微服务 | 独立部署、按需扩容、团队自治 | 网络、数据一致性、运维和追踪复杂 |
拆分应按业务能力和数据所有权,而不是按技术层拆成“用户服务、日志服务、工具服务”。一个服务应能独立回答一组业务问题。
1.2 绞杀者模式
跳转到“1.2 绞杀者模式”从单体迁移时,不必一次性重写。可以在边界处建立路由,把一部分功能逐步迁移到新服务,验证稳定后再扩大范围。旧的实现继续运行,直到新实现完全替代。这样能控制风险,但短期内需要双系统一致性与流量切换。
2. 分布式系统的基本事实
跳转到“2. 分布式系统的基本事实”本地调用通常假设:
- 方法会返回。
- 返回只有成功或异常。
- 调用耗时稳定。
- 双方看到同一份内存。
- 时钟和顺序一致。
跨网络后,这些假设都不再可靠:
- 网络可能延迟、丢包、重复或分区。
- 请求可能到达但响应丢失,调用方无法确定结果。
- 对方可能很慢,而不是直接失败。
- 多个服务可能看到不同的数据版本。
- 机器时钟可能漂移。
- 节点可能重启,内存状态丢失。
这就是为什么远程调用必须有超时、幂等、重试和可观测性。一个“没有响应”的请求可能根本没到,也可能已经成功执行。
3. CAP 与一致性
跳转到“3. CAP 与一致性”CAP 常被简化成“一致性、可用性、分区容忍三选二”。更准确地说:网络分区无法避免,分区发生时,系统必须在一致性和可用性之间做取舍。
- CP:分区时拒绝部分请求,保证不返回可能错误的数据。
- AP:分区时继续响应,但可能返回旧数据,之后通过复制收敛。
正常网络下,系统可以同时追求较好的可用性和一致性;CAP 讨论的是分区期间的保证。
3.1 一致性模型
跳转到“3.1 一致性模型”| 模型 | 含义 |
|---|---|
| 强一致 | 读取总能看到最新已提交写入 |
| 线性一致 | 所有操作像在单一副本上按实时顺序执行 |
| 顺序一致 | 所有节点看到相同顺序,但不一定与真实时间一致 |
| 最终一致 | 没有新写入后,副本最终收敛到相同状态 |
| 读己之写 | 用户能读到自己刚写入的结果 |
| 单调读 | 用户不会先看到新值又读回旧值 |
业务通常不需要所有数据都强一致。账户余额、库存扣减和权限可能要求更强保证;头像、统计和推荐可以接受短暂陈旧。关键是逐项声明,而不是给整个系统贴一个标签。
3.2 复制
跳转到“3.2 复制”复制提高可用性和读能力,也带来延迟:
- 主从复制:写主库,异步或同步复制到从库。
- 多主复制:多个节点都可写,冲突解决更复杂。
- 无主复制:客户端向多个副本读写,用 quorum 判断一致性。
同步复制延迟更高但减少丢失,异步复制响应更快但主库故障时可能丢最近写入。副本延迟还会导致读到自己刚写数据失败。
3.3 共识
跳转到“3.3 共识”当多个节点需要对同一顺序达成一致,需要共识算法,例如 Raft、Paxos。共识能处理领导者选举和日志复制,但需要多数节点可用。三个节点通常能容忍一个故障;节点越多,写协调成本越高。
应用开发者不一定要实现共识,但要理解:依赖的数据库、配置中心或协调服务可能因为失去多数派而拒绝写入。
4. 分布式事务
跳转到“4. 分布式事务”4.1 两阶段提交
跳转到“4.1 两阶段提交”协调者先让所有参与者准备,再统一提交或回滚。它能提供较强原子性,但协调者故障、锁持有时间和网络分区会让系统阻塞,跨组织系统尤其困难。
4.2 Saga
跳转到“4.2 Saga”把长事务拆成一系列本地事务,每一步都有补偿动作:
创建订单 → 预占库存 → 扣款 → 发货 ↘ 失败补偿 ← 释放库存 ← 退款补偿不一定等于“恢复原状”。例如短信已经发送,无法真正撤回。Saga 需要幂等、可重试、可观测,并明确中间状态对用户意味着什么。
4.3 Outbox
跳转到“4.3 Outbox”数据库写入和消息发送无法简单放进同一个事务。Outbox 模式先在业务数据库的 outbox 表写入事件,再由后台进程读取并发布:
业务写入 + outbox 写入(同一本地事务) ↓ 投递者读取 outbox ↓ 消息队列/事件总线消费者仍需幂等,因为投递通常是至少一次。
5. 高可用
跳转到“5. 高可用”“几个 9”表示允许的不可用时间比例:
| 可用性 | 年停机时间(约) |
|---|---|
| 99% | 3.65 天 |
| 99.9% | 8.77 小时 |
| 99.99% | 52.6 分钟 |
| 99.999% | 5.26 分钟 |
指标必须对应服务、地区和测量方式。一个 API 返回 500 很快,不代表用户实际可用;前端依赖第三方失败也可能让整页不可用。
5.1 冗余与故障转移
跳转到“5.1 冗余与故障转移”- 多实例:单机故障时由其他实例接管。
- 多可用区:机房级故障时跨区切换。
- 多地域:地理灾害或区域网络故障时的容灾。
故障转移要回答:
- 健康检查怎样判断“可用”?
- 切换需要多久?
- 新节点是否有足够容量接管全部流量?
- 配置、密钥、数据复制是否同步?
- 切回来是否会引发二次故障?
5.2 RPO 与 RTO
跳转到“5.2 RPO 与 RTO”- RPO(恢复点目标):最多能接受丢失多长时间的数据。
- RTO(恢复时间目标):故障后多久必须恢复服务。
备份频率决定 RPO,恢复流程和基础设施决定 RTO。只备份不演练,无法证明 RTO。恢复演练应包含数据库、对象存储、配置、密钥和应用版本。
5.3 健康检查
跳转到“5.3 健康检查”健康检查不能只看进程是否活着。一个进程可能还在,但数据库连接池耗尽、队列堵塞或依赖不可用。同时,健康检查也不能过度依赖外部系统,否则依赖抖动会把所有实例同时标记为不健康。
常见拆分:
- 存活检查:进程是否需要重启。
- 就绪检查:实例是否能接收流量。
- 启动检查:慢启动应用是否完成初始化。
6. 韧性模式
跳转到“6. 韧性模式”| 模式 | 目的 | 主要风险 |
|---|---|---|
| 超时 | 防止无限等待 | 设得太短会误杀正常请求 |
| 重试 | 处理瞬时故障 | 放大流量、重复副作用 |
| 退避与抖动 | 避免同时重试 | 需要最大次数和截止时间 |
| 熔断 | 依赖持续失败时停止调用 | 恢复时机和误判 |
| 舱壁 | 隔离资源池 | 容量划分与利用率 |
| 限流 | 控制进入速率 | 误伤、公平性和重试 |
| 降级 | 保留核心功能 | 用户是否知道结果不完整 |
| 负载卸载 | 主动拒绝超额请求 | 阈值和优先级 |
重试必须结合幂等和预算。一个请求最多重试几次、每次多久、整体截止时间是多少,应在入口统一设计。各层都重试三次,最终可能把一次调用放大成几十次。
6.1 有序降级
跳转到“6.1 有序降级”高负载时优先级低的请求先失败:
- 保障登录、支付、核心读取。
- 降低非核心刷新和统计频率。
- 暂停推荐、导出、批量重算。
- 对非关键操作返回稍后重试。
降级要可观察、可恢复。不能悄悄返回空数据让用户以为真的没有内容。
7. 服务通信与治理
跳转到“7. 服务通信与治理”- 同步调用适合需要立即结果的请求,但会把上游延迟和故障传下去。
- 异步消息适合解耦、削峰和长任务,但增加最终一致与重复处理。
- API 网关负责统一入口、认证、路由、限流和协议转换,不应承载所有业务。
- 服务发现让调用方找到实例,但注册中心故障也要有缓存和降级。
- 配置中心统一管理动态配置,必须有校验、版本、审计和回滚。
同步链路越长,尾部延迟越明显。一个请求调用 20 个服务,即使每个服务 p99 都很好,整体仍可能出现长尾。能并行的调用并行,能异步的任务异步,必要的调用设置独立超时。
8. 容量与故障演练
跳转到“8. 容量与故障演练”容量规划至少估算:
- 峰值 QPS 与并发请求数。
- 单请求 CPU、内存、数据库和外部依赖消耗。
- 数据增长与保留期。
- 故障时剩余实例能否承接全部流量。
- 扩容需要多久,冷启动和预热成本多大。
压测应使用接近真实的请求比例、数据规模和依赖延迟。只压一个简单健康接口,无法证明业务链路可用。
混沌演练可以主动注入延迟、实例故障、依赖错误和网络分区,但应从受控环境、小范围和小流量开始,有停止条件和监控。
9. 架构决策记录
跳转到“9. 架构决策记录”架构选择应记录:
背景与约束:考虑的方案:决定:理由:代价与风险:验证指标:回滚或退出条件:没有记录的决策会被记忆美化。未来看到“为什么用消息队列”时,至少能知道当时的目标和数据。
检查清单
跳转到“检查清单”- 当前复杂度是否由真实约束驱动,而不是跟风。
- 服务边界是否对应业务能力和数据所有权。
- 每个远程调用是否有超时、重试、幂等和失败语义。
- 一致性要求是否按数据逐项定义。
- 复制延迟、分区和共识失去多数派时行为是否清楚。
- 跨服务流程是否有补偿、中间状态和可查询操作 ID。
- 可用性目标是否有指标、测量范围和实际用户视角。
- RPO/RTO、备份恢复和故障转移是否演练。
- 是否有熔断、限流、降级和按优先级卸载。
- 架构决策是否留下背景、代价和退出条件。