194|高可用设计:冗余、故障转移、无单点、超时、降级和恢复

架构设计与系统设计约 20 分钟阅读11,440 字符
知识库文档 195 / 251

阅读时间:约 40-50 分钟 | 适用方向:Java 技术专家 / 架构师 / 分布式系统设计


目录

  1. 问题定义:什么是高可用
  2. 可用性度量:从 99.9% 到 99.999%
  3. 冗余设计:主备、多活、N+1 冗余
  4. 故障检测:心跳、健康检查与超时
  5. 故障转移:自动切换、选举与脑裂防护
  6. 无单点设计:消除 SPOF 的系统性方法
  7. 超时控制:连接超时、请求超时、超时传播
  8. 降级设计:优雅降级、功能降级与熔断
  9. 恢复机制:自动恢复、手动恢复与数据恢复
  10. 高可用架构实战:从单机房到多活
  11. 常见陷阱与故障案例
  12. 面试官提问

1. 问题定义:什么是高可用

文章配图
文章配图

高可用(High Availability, HA) 是指系统在面对部分组件故障时,仍能持续对外提供服务的能力。它的核心目标不是"不出故障",而是"出了故障用户无感知或感知极小"。

核心公式:

code
可用性 = MTTF / (MTTF + MTTR)
术语 含义
MTTF(Mean Time To Failure) 平均无故障时间
MTTR(Mean Time To Repair) 平均修复时间

从这个公式可以看出,提高可用性有两条路:延长 MTTF(减少故障发生) 和 缩短 MTTR(加快故障恢复)。在实际工程中,缩短 MTTR 往往比延长 MTTF 更可控、更经济——因为故障不可避免,但恢复速度可以优化。

高可用 ≠ 高可靠:可靠性关注的是"不出错",可用性关注的是"能用"。一个系统可能每次请求都返回错误信息(不可靠),但只要它返回了响应,它就是"可用"的。当然,实际工程中我们两者都追求。

高可用的核心支柱:

  • 冗余(Redundancy):多副本,一个挂了另一个顶上
  • 故障转移(Failover):自动检测并切换到健康副本
  • 无单点(No SPOF):任何组件故障不影响整体服务
  • 超时控制(Timeout):不无限等待,快速失败
  • 降级(Degradation):核心功能优先,非核心功能可牺牲
  • 恢复(Recovery):故障后快速回到正常状态

2. 可用性度量:从 99.9% 到 99.999%

文章配图
文章配图

2.1 九个九的标准

可用性 俗称 年停机时间 典型架构
99% 两个九 3.65 天 单机 + 备份
99.9% 三个九 8.76 小时 主备切换
99.99% 四个九 52.56 分钟 同城双活
99.999% 五个九 5.26 分钟 异地多活
99.9999% 六个九 31.5 秒 全自动容灾

为什么每提高一个九,成本呈指数级增长? 因为每增加一个九,你需要消除的故障模式就更多——从硬件故障到网络分区,从人为误操作到软件 Bug,从机房断电到地震洪水。每个九的背后都是巨大的基础设施投入、自动化工程和运维能力。

2.2 SLA、SLO 与 SLI

  • SLI(Service Level Indicator):服务等级指标,如请求成功率、P99 延迟
  • SLO(Service Level Objective):服务等级目标,如"99.9% 的请求在 200ms 内完成"
  • SLA(Service Level Agreement):服务等级协议,违反后有赔偿条款

关键区别:SLO 是内部目标,SLA 是对外承诺。SLO 应比 SLA 更严格,留出缓冲空间。

2.3 错误预算

错误预算 = 1 - SLO。例如 SLO 是 99.9%,则错误预算为 0.1%。

错误预算的工程价值:

  • 当错误预算充足时,可以大胆发布新版本
  • 当错误预算耗尽时,冻结发布,优先修复稳定性问题
  • 这是 Google SRE 的核心实践之一

3. 冗余设计:主备、多活、N+1 冗余

文章配图
文章配图

3.1 冗余的本质

冗余就是用资源的冗余换取故障时的容错能力。冗余不是简单的"多放几台机器",而是一个需要精心设计的系统工程。

3.2 冗余模式

(1)主备模式(Active-Passive)

  • 一个主节点提供服务,一个或多个备节点待命
  • 备节点不处理请求,只同步数据
  • 主节点故障时,备节点接管
  • 优点:简单,数据一致性好
  • 缺点:资源利用率低(备节点闲置),切换有窗口

(2)主主模式(Active-Active)

  • 多个节点同时提供服务
  • 需要解决数据冲突和一致性
  • 优点:资源利用率高,无切换窗口
  • 缺点:数据一致性复杂,需要冲突解决机制

(3)N+1 冗余

  • N 个节点满足业务需求,额外 1 个节点做冗余
  • 任意一个节点故障,系统仍能满足 N 个节点的能力
  • 常用于应用服务器、缓存集群

(4)2N 冗余

  • 两套完全独立的系统,互为主备
  • 成本最高,可靠性也最高
  • 常用于核心数据库、支付系统

3.3 冗余的代价

  • 硬件成本:直接的资源翻倍
  • 同步成本:数据同步消耗网络和 CPU
  • 一致性成本:CAP 理论告诉我们,分区时要在一致性和可用性间取舍
  • 运维复杂度:更多节点意味着更复杂的运维

4. 故障检测:心跳、健康检查与超时

故障检测是高可用的眼睛——你必须在用户感知到故障之前,先发现故障。

4.1 心跳机制

心跳是分布式系统中最基础的故障检测手段:

  • 固定频率心跳:每隔 T 秒发送一次心跳
  • 超时判定:连续 N 次心跳未收到,判定节点故障
  • φ 失败检测器:基于心跳间隔的动态概率模型,自适应网络抖动

心跳设计的陷阱:

  • 心跳间隔太短 → 网络开销大,误判多
  • 心跳间隔太长 → 故障发现慢,恢复慢
  • 心跳和业务流量走同一通道 → 网络拥塞时心跳也丢,产生误判

4.2 健康检查

健康检查比心跳更全面,不仅检查"是否活着",还检查"是否健康":

检查类型 检查内容 示例
Liveness 进程是否存活 端口是否监听
Readiness 是否准备好接收流量 依赖服务是否就绪
Startup 是否启动完成 预热是否完成

Kubernetes 中的三种探针就是这三种健康检查的典型实现。

4.3 故障检测的工程实践

  • 多级检测:进程级 → 机器级 → 服务级 → 业务级
  • 去中心化检测:不要依赖单一检测节点,避免检测节点本身成为单点
  • 假阳性处理:网络抖动导致的心跳丢失不应直接触发切换,需要多次确认

5. 故障转移:自动切换、选举与脑裂防护

文章配图
文章配图

5.1 故障转移的基本流程

code
故障检测 → 确认故障 → 选择新主 → 切换流量 → 数据同步 → 旧主恢复

每一步都有陷阱:

  • 确认故障:如何区分"真挂"和"网络慢"?
  • 选择新主:谁来做决定?多个候选者如何协调?
  • 切换流量:如何保证流量不丢失?
  • 数据同步:新主的数据是最新的吗?
  • 旧主恢复:旧主回来后如何避免数据冲突?

5.2 选举机制

分布式系统中,选举是确定"谁做主"的核心机制:

(1)Bully 算法

  • 节点 ID 最大的当选
  • 简单但不够可靠

(2)Raft 选举

  • 基于任期(Term)的选举
  • 候选人请求投票,获得多数票则成为 Leader
  • 随机选举超时避免活锁

(3)基于外部协调器的选举

  • 使用 ZooKeeper/etcd 的临时顺序节点
  • 序号最小的节点为 Leader
  • Watch 机制实现自动切换

5.3 脑裂问题

文章配图
文章配图

脑裂(Split-Brain) 是分布式系统中最危险的故障模式之一:网络分区导致集群分裂成多个子集群,每个子集群都认为自己是"主",对外提供服务,产生数据冲突。

防护手段:

手段 原理 典型实现
Quorum(法定人数) 必须获得多数节点同意才能成为主 Raft、ZAB
Fencing Token 每次切换递增一个单调递增的令牌 分布式锁的 fencing
STONITH 强制关闭旧主,物理隔离 Shoot The Other Node In The Head
租约(Lease) 主节点持有租约,到期自动释放 etcd Lease

6. 无单点设计:消除 SPOF 的系统性方法

6.1 什么是 SPOF

单点故障(Single Point of Failure, SPOF) 是指系统中某个组件一旦故障,整个系统就无法提供服务。

6.2 常见单点

层级 单点 消除方案
DNS 单一 DNS 服务商 多 DNS 服务商
负载均衡 单台 LB LB 集群 + VIP 漂移
应用服务器 单台应用 多实例 + 无状态
缓存 单节点 Redis Redis Cluster / Sentinel
数据库 单库 主从 / 集群 / 分库
消息队列 单节点 MQ 集群部署
配置中心 单节点 集群 + 本地缓存
机房 单机房 多机房 / 多活
网络 单链路 双链路 / 多运营商

6.3 消除 SPOF 的系统方法

第一步:画出系统架构图,标注每个组件。

第二步:逐个问自己:"如果这个组件挂了,系统还能提供服务吗?"

第三步:对于每个 SPOF,设计冗余方案。优先从低成本、高收益的开始。

"无状态化"是消除 SPOF 最有效的手段:将有状态的数据下沉到专门的存储层(数据库、缓存),让应用层完全无状态,这样应用层可以任意扩缩容,任何实例挂了流量自动切到其他实例。


7. 超时控制:连接超时、请求超时、超时传播

文章配图
文章配图

7.1 超时的重要性

超时是分布式系统中最容易被忽视但最关键的保护机制。没有超时保护的系统,一个慢依赖可以拖垮整个调用链。

超时设得太长:资源被长时间占用,故障扩散 超时设得太短:正常请求被误杀,可用性反而下降

7.2 超时类型

超时类型 作用 典型值
连接超时(Connect Timeout) 建立 TCP 连接的最大等待时间 200ms - 1s
读取超时(Read Timeout) 等待响应的最大时间 1s - 30s
请求超时(Request Timeout) 整个请求的生命周期 调用方设置
空闲超时(Idle Timeout) 连接空闲多久后关闭 几分钟到几小时

7.3 超时传播(Deadline Propagation)

在微服务调用链中,如果服务 A 调用服务 B,服务 B 调用服务 C,A 设置了 3 秒超时,那么 B 调用 C 时应该使用剩余的可用时间,而不是重新设置一个 3 秒超时。

code
A 设置 3 秒超时 → 调用 B
   B 收到请求,已过去 200ms → 剩余 2.8 秒
   B 调用 C,设置超时 = 2.8 秒
      C 收到请求,已过去 500ms → 剩余 2.3 秒
      C 调用 D,设置超时 = 2.3 秒

gRPC 的 deadline 机制就是超时传播的标准实现。

7.4 超时与重试的配合

超时和重试是一对矛盾体:超时太短触发大量重试,加重系统负担;超时太长又影响用户体验。实践中需要:

  • 设置合理的超时时间(基于 P99 延迟)
  • 使用指数退避重试
  • 设置最大重试次数
  • 引入抖动(Jitter)避免惊群效应

8. 降级设计:优雅降级、功能降级与熔断

8.1 降级的本质

降级(Degradation)是在系统部分功能不可用时,主动牺牲非核心功能,保障核心功能的策略。这是一种"有损服务"的取舍——不是所有功能都同等重要。

8.2 降级策略

(1)功能降级

场景 降级策略
推荐服务不可用 返回默认推荐列表
评论服务不可用 隐藏评论区,不阻塞主流程
搜索服务响应慢 返回缓存结果
图片服务不可用 显示占位图

(2)质量降级

  • 降低图片分辨率
  • 减少返回的数据量
  • 关闭非核心的实时计算

(3)读降级

  • 从主库读降级为从库读
  • 从实时数据降级为缓存数据
  • 从精确结果降级为近似结果

8.3 降级的开关设计

降级需要可控,不能"自动降级之后恢复不了":

  • 手动降级开关:配置中心控制,运维可手动切换
  • 自动降级触发:基于错误率、延迟、资源使用率自动触发
  • 降级恢复:故障恢复后自动或手动恢复降级功能
  • 降级分级:L1(非核心功能)→ L2(次要功能)→ L3(核心功能的部分特性)

8.4 熔断器(Circuit Breaker)

文章配图
文章配图

熔断是降级的一种自动化实现:

code
关闭状态(Closed)→ 错误率超过阈值 → 打开状态(Open)
打开状态(Open)→ 等待一段时间 → 半开状态(Half-Open)
半开状态(Half-Open)→ 试探请求成功 → 关闭状态(Closed)
半开状态(Half-Open)→ 试探请求失败 → 打开状态(Open)

三个关键参数:

  • 错误阈值:触发熔断的错误率或错误数
  • 熔断时长:熔断状态持续多久后进入半开
  • 半开试探数:半开状态允许通过的请求数

9. 恢复机制:自动恢复、手动恢复与数据恢复

9.1 恢复的目标

恢复不是"把系统修好",而是在最短时间内把服务恢复到可用状态。根因分析可以等系统恢复之后再做。

9.2 自动恢复机制

机制 恢复时间 适用场景
进程重启 秒级 进程 Crash
容器重启 秒级 容器异常退出
实例替换 分钟级 机器故障
故障转移 秒-分钟级 主节点故障
自动扩容 分钟级 流量突增
数据回滚 分钟-小时级 数据错误
机房切换 分钟级 机房故障

9.3 Kubernetes 的自愈能力

Kubernetes 内置了完善的自愈机制:

  • Pod 重启:liveness 探针失败 → kubelet 重启容器
  • 节点故障:node controller 检测到节点不可达 → 在其他节点重建 Pod
  • 副本保持:ReplicaSet 持续确保指定数量的 Pod 在运行

9.4 数据恢复的挑战

数据恢复是恢复中最复杂的部分:

  • 数据一致性:恢复后的数据是否与故障前一致?
  • 数据丢失量:RPO(Recovery Point Objective)= 能容忍丢失多少数据?
  • 恢复时间:RTO(Recovery Time Objective)= 多快能恢复?

RPO vs RTO:

  • RPO = 0:不能丢任何数据 → 需要同步复制
  • RTO = 30 秒:30 秒内必须恢复 → 需要自动故障转移
  • 更低的 RPO 和 RTO 意味着更高的成本

10. 高可用架构实战:从单机房到多活

文章配图
文章配图

10.1 第一阶段:单机房主备

code
[用户] → [DNS] → [LB 主] → [应用集群] → [DB 主]
                       ↓                    ↓
                  [LB 备]              [DB 备]
  • 可用性级别:99.9%
  • 故障转移时间:分钟级
  • 单点:DNS、LB 主备切换、DB 主备切换

10.2 第二阶段:同城双活

code
[用户] → [GSLB] → [机房 A: LB → 应用集群 → DB 主]  ←→  [机房 B: LB → 应用集群 → DB 备]
  • 可用性级别:99.99%
  • 应用层双活,数据库主备
  • 机房 A 故障时,机房 B 的数据库需要提升为主

10.3 第三阶段:异地多活

code
[北方用户] → [GSLB] → [华北机房: 全量服务]
[南方用户] → [GSLB] → [华南机房: 全量服务]
         ↓
[数据同步] ← 双向复制 / 消息队列
  • 可用性级别:99.999%
  • 每个机房都是全量服务
  • 核心挑战:数据冲突解决、流量调度、全局 ID

10.4 单元化架构

单元化是异地多活的一种高级形式:

code
[用户 ID 路由] → [单元 1: 完整服务链路]  → [全局 DB]
              → [单元 2: 完整服务链路]  → [全局 DB]
              → [单元 N: 完整服务链路]  → [全局 DB]
  • 每个单元是一个完整的、自包含的服务栈
  • 用户通过 ID 哈希路由到固定单元
  • 单元内闭环,减少跨单元调用
  • 故障隔离:一个单元故障只影响该单元的用户

11. 常见陷阱与故障案例

陷阱 1:伪冗余

一个典型的伪冗余场景:主备数据库部署在同一台物理机上,或者共享同一个电源。物理机故障时,主备一起挂。

教训:冗余必须考虑故障域隔离——物理机、机架、电源、网络交换机、机房。

陷阱 2:故障转移不测试

很多团队的故障转移只是"配置了",从来没真正测试过。等到真正故障时,发现切换脚本有 Bug、数据不一致、DNS 没生效。

教训:定期进行故障演练(Chaos Engineering),在可控环境中验证故障转移流程。

陷阱 3:超时不合理

"超时设长一点就不容易超时了"——这是最常见也最危险的误解。长超时意味着:线程池被占满、连接池耗尽、级联故障扩散。

教训:超时应该基于 P99 延迟设置,通常设为 P99 * 2,并配合超时传播。

陷阱 4:降级逻辑不验证

降级开关配了,但降级逻辑从来没执行过。故障时降级开关打开,才发现降级代码有 Bug,反而引发更严重的问题。

教训:降级逻辑必须经过测试和演练,确保降级后系统行为符合预期。

故障案例:某电商大促的级联故障

code
推荐服务响应变慢(依赖的算法服务超时)
→ 调用推荐服务的商品详情页线程池占满
→ 商品详情页不可用
→ 用户无法下单
→ 整个交易链路瘫痪

根因:推荐服务没有设置合理的超时和熔断,商品详情页没有对推荐服务做降级。

修复:推荐服务增加超时和熔断,商品详情页对推荐服务做降级(返回默认推荐)。


12. 面试官提问

以下问题从面试官视角出发,考察对高可用设计的深度理解和实际工程经验。

Q1:你们系统现在是什么级别的可用性?如果需要从 99.9% 提升到 99.99%,你会怎么规划?

参考答案:

从 99.9%(年停机 8.76 小时)提升到 99.99%(年停机 52.56 分钟),核心是把"人工恢复"变成"自动恢复",同时消除关键路径上的单点。

具体规划分四步:

第一步:消除基础设施单点。 应用服务器做多实例无状态部署,数据库从单主升级为主从+自动切换(如 MySQL MHA/Orchestrator),负载均衡从单台 LB 升级为 LB 集群(Keepalived + VIP 漂移或云 LB)。

第二步:建立自动化故障转移。 应用层通过健康检查 + 自动摘流实现秒级切换;数据层配置自动主从切换,切换时间控制在 30 秒内;DNS 层面配置多解析 + 健康检查,实现入口层自动容灾。

第三步:引入降级和熔断。 核心链路逐个梳理依赖,对非强依赖做降级开关;引入熔断器(如 Hystrix/Sentinel/Resilience4j),防止慢依赖拖垮主链路;建立降级预案和演练机制。

第四步:建立度量体系。 定义 SLI/SLO,建立错误预算机制;搭建全链路监控和告警;定期做故障演练(Chaos Engineering),验证切换流程的有效性。

关键认知:从 99.9% 到 99.99% 不是加机器能解决的,本质是运维自动化 + 架构改造 + 组织能力的提升。


Q2:假设你们的数据库主库宕机,描述一下从故障发生到服务恢复的完整时间线,每个环节分别需要多少时间?哪些环节可以优化?

参考答案:

一个典型的自动故障转移时间线(以 MySQL + MHA/Orchestrator 为例):

阶段 时间 操作
故障检测 3-30s 监控系统通过心跳检测到主库不可达,连续 N 次确认后触发告警
确认故障 0-5s 从多个检测点交叉验证,排除网络抖动假阳性
选主 1-5s 从所有从库中选出数据最新的作为新主
数据补齐 0-30s 如果旧主有已提交但未同步到从库的 binlog,尝试补齐(取决于网络和日志量)
切换拓扑 1-5s 修改其他从库的复制源指向新主,更新 VIP/Proxy 指向新主
应用层感知 1-30s 连接池重建连接,DNS/VIP 生效
业务恢复 总计 30s-120s 服务完全恢复

可优化的环节:

  • 故障检测:从 30s 优化到 3-5s,使用更激进的心跳策略(但要配合假阳性过滤)
  • 数据补齐:使用半同步复制(semi-sync replication)可减少甚至消除这个阶段
  • 应用层感知:连接池配置合理的超时和重试策略,避免长时间等待失效连接;使用数据库 Proxy(如 ProxySQL)可以做到对应用透明的切换
  • DNS/VIP:如果使用 DNS 做切换,TTL 设为 30s 以内

最容易忽视的优化点:切换后的预热。新主库刚接管时 Buffer Pool 是冷的,查询性能会骤降,需要提前做预热或使用从库的热 Buffer Pool 迁移。


Q3:Raft 协议中,如果网络分区导致一个节点被隔离,它发现自己不再是 Leader 后还能继续响应读请求吗?为什么?

参考答案:

不能。 被隔离的旧 Leader 一旦发现自己不再能收到多数派的响应(心跳超时),会立即降级为 Follower,停止响应所有读写请求。

原因在于 Raft 的安全性保证——Leader Completeness Property(领导人完全特性):一旦一个日志条目被提交(被多数派确认),所有后续的 Leader 都会包含这个条目。如果旧 Leader 在网络分区后继续响应读请求,它可能返回过期的数据,因为新的 Leader 可能已经提交了它不知道的日志条目。

具体来说:

  1. 网络分区后,被隔离的旧 Leader 无法收到多数派的 AppendEntries 响应
  2. 选举超时到期后,旧 Leader 变成 Follower(不再认为自己有 Leader 身份)
  3. 此时客户端如果向旧 Leader 发请求,旧 Leader 会拒绝(返回 "not leader" 错误)
  4. 客户端需要重定向到新的 Leader

延伸问题:Raft 允许 Leader 从本地状态读取吗?是的,Raft 允许 Leader 在确认自己仍然是 Leader 后(通过 heartbeat 确认自己仍能和多数派通信)从本地读取,但这只适用于正常运行的 Leader,不适用于被网络分区隔离的情况。


Q4:你们系统的超时时间是怎么设置的?有没有遇到过因为超时设置不合理导致的问题?

参考答案:

超时设置的方法论:

  1. 基于 P99 延迟设置:超时 = P99 延迟 × 2 ~ 3。例如某依赖的 P99 延迟是 200ms,超时设为 500ms。
  2. 区分连接超时和读取超时:连接超时通常设 200ms-1s(建连很快),读取超时基于业务延迟。
  3. 超时传播:在微服务调用链中,下游的超时必须小于上游的剩余时间。使用 gRPC deadline 或自定义 trace header 传递 deadline。
  4. 分级设置:核心链路的超时可以适当放宽,非核心链路收紧。

常见问题案例:

  • 问题 1:超时与重试的叠加效应。 一同事把超时从 3s 改成 1s,"优化"响应速度。结果 1s 内没返回的请求全部超时+重试,下游收到 3 倍请求,直接被打挂。教训:缩短超时必须同时减少重试次数或增加退避。
  • 问题 2:连接池超时设得太长。 数据库连接池的 connectionTimeout 设了 30s,当数据库慢查询占满连接时,新请求排队 30s 才超时,整个线程池耗尽。教训:获取连接的超时应远小于请求超时(通常 200ms-1s)。
  • 问题 3:没有超时传播。 服务 A 设 5s 超时,内部调用 B(3s 超时),B 调用 C(3s 超时)。某次 C 变慢,A 等 5s 超时,但 B 的线程已经被 C 占满 3s 才释放,导致 B 的线程池快速耗尽。教训:必须做超时传播,B 调用 C 的超时 = A 剩余时间 - 自身处理时间。

Q5:设计一个秒杀系统的降级方案:当流量远超预期时,哪些功能可以降级?降级的优先级怎么排?

参考答案:

秒杀场景的降级核心原则:保障交易闭环(下单→支付),其他都可以牺牲。

降级优先级(从高到低,越靠前越先降级):

L1 — 立即降级(非核心功能,不影响交易):

  • 推荐/猜你喜欢 → 返回静态缓存列表
  • 商品评论/评分 → 隐藏模块
  • 浏览历史/足迹 → 关闭记录
  • 营销弹窗/优惠券推荐 → 全部关闭
  • 实时数据大屏 → 关闭或降低刷新频率

L2 — 流量过大时降级(提升性能):

  • 商品详情页 → 静态化,CDN 缓存,去掉动态库存查询
  • 购物车 → 限制购物车商品数量,关闭非核心计算
  • 用户画像/个性化 → 使用默认策略
  • 日志 → 采样或异步批量写入,关闭 DEBUG 日志

L3 — 极端情况降级(保障核心链路):

  • 验证码 → 关闭或降低难度
  • 风控 → 切换到轻量级规则引擎
  • 库存扣减 → 从实时扣减改为异步扣减(有超卖风险,需权衡)

不可降级(核心功能):

  • 用户登录/鉴权
  • 下单(创建订单)
  • 支付
  • 库存扣减(至少保证最终一致性)

技术实现:

  • 配置中心集中管理降级开关,支持一键降级和一键恢复
  • 客户端也做降级:前端隐藏不可用模块,而非等待接口超时
  • 降级后要有监控:确认降级生效,且核心链路 RT 回到正常范围

Q6:为什么 Kubernetes 的 etcd 集群推荐使用奇数个节点(3 或 5)?如果是偶数个节点会有什么问题?

参考答案:

核心原因:Quorum(法定人数)机制。

etcd 使用 Raft 共识算法,写操作需要获得 多数派(majority) 节点的同意才算提交。多数派 = N/2 + 1(向下取整后加 1)。

节点数 多数派 容忍故障数 效率
1 1 0 —
2 2 0 浪费
3 2 1 最优
4 3 1 浪费
5 3 2 推荐
6 4 2 浪费
7 4 3 规模大

偶数节点的问题:

以 4 节点为例——容忍故障数仍然是 1(和 3 节点一样),但多了一台机器的成本、网络开销和运维复杂度。4 节点集群的写操作延迟略高于 3 节点(因为需要 3 个节点确认而非 2 个),但可用性没有提升。

更关键的是,4 节点在某些网络分区场景下反而更脆弱:如果分区成 2+2,没有一方达到多数派(3),集群完全不可用。而 3 节点分区成 2+1 时,2 节点那方可以继续工作。

工程建议: 生产环境推荐 3 节点或 5 节点。3 节点足以应对大多数场景,5 节点在需要更高容错(容忍 2 台故障)时使用。超过 7 节点反而会因为 Raft 日志复制的开销降低写性能。


Q7:你们系统中有没有"伪冗余"的情况?比如看起来有备份,但实际共享了某个故障域?

参考答案:

这道题考察的是对故障域(Failure Domain)的理解。伪冗余的典型场景:

场景 1:同物理机部署。 主备数据库部署在同一台物理机的不同虚拟机上,物理机宕机 → 主备一起挂。正确做法:主备必须在不同物理机,甚至不同机架。

场景 2:同机架/同电源。 两台服务器做了主备,但插在同一个 PDU(电源分配单元)上。PDU 故障 → 两台一起断电。正确做法:关键服务的主备跨机架、跨电源部署。

场景 3:同交换机。 主备节点连在同一台交换机上,交换机故障 → 主备同时断网。正确做法:网络双上联,主备连接不同交换机。

场景 4:云服务的"伪多 AZ"。 在云平台上,两个可用区(AZ)可能在同一个物理园区内,共享电力、冷却等基础设施。遇到区域性灾害(如断电、火灾)时两个 AZ 可能同时受影响。正确做法:了解云厂商的 AZ 物理隔离程度,关键业务考虑跨 Region 部署。

场景 5:同配置中心。 主备集群的切换逻辑依赖同一个配置中心(如 ZooKeeper),配置中心本身挂了 → 主备切换机制失效。正确做法:切换逻辑要有本地兜底,不能完全依赖外部协调器。

场景 6:同发布系统。 主备环境通过同一套 CI/CD 发布,发布系统推送了有 Bug 的版本 → 主备环境同时受影响。正确做法:灰度发布,先发备环境验证,再发主环境(或反过来)。

自查方法: 画一张故障域树,从物理机 → 机架 → 电源 → 交换机 → 机房 → 城市 → 国家,逐层检查每个关键组件的主备是否落在不同故障域上。


Q8:CAP 理论中,高可用设计在遇到网络分区时,如何在 C 和 A 之间做取舍?举例说明。

参考答案:

CAP 理论的核心:当网络分区(Partition)发生时,你必须在一致性(Consistency)和可用性(Availability)之间做选择。没有分区时,C 和 A 可以兼得。

选择 CP(一致性优先)的场景:

  • 金融交易系统:宁可暂时不可用,也不能出现账户余额不一致。例如银行转账,网络分区后如果两个机房各处理了一半转账,恢复后余额对不上是灾难性的。
  • 库存扣减:秒杀场景,宁可告诉用户"系统繁忙",也不能超卖。
  • 分布式锁:ZooKeeper/etcd 的锁服务,分区时宁可锁不可用,也不能两个客户端同时获得锁。

选择 AP(可用性优先)的场景:

  • 社交媒体的点赞数:短暂的不一致(一条朋友圈显示 100 赞,另一个用户看到 99 赞)完全可接受,但服务不可用不可接受。
  • CDN 内容分发:节点间内容短暂不一致(某个节点上的图片还是旧版本)可以接受,因为最终会同步。
  • DNS 解析:DNS 天然是 AP 系统,各地 DNS 缓存可能不一致,但最终会收敛。

工程中的实际取舍——不是二选一:

实际系统很少做绝对的 C 或 A 选择,而是在同一个系统中对不同数据做不同选择:

  • 支付宝转账:金额走 CP(强一致),转账附言走 AP(最终一致)
  • 电商订单:订单状态走 CP,商品浏览记录走 AP
  • 实时性要求高的用 CP,实时性要求低的用 AP

BASE 理论就是 AP 系统的实践指导:Basically Available(基本可用)、Soft state(软状态)、Eventually consistent(最终一致)。


下一篇:195|可扩展架构:无状态化、水平扩展、分片、异步化和热点治理