系统设计面试:如何设计高可用微服务系统(快速容灾与流量切换)

高可用微服务是每一场系统设计面试的必考题。本文以”设计一个可以快速容灾的微服务系统”为主线,完整梳理面试官期待听到的答案框架:如何检测故障、如何切换流量、如何恢复,以及背后的关键权衡。


1. 面试题还原与破题

面试官常见问法:

“请设计一个微服务系统,要求当某个服务或整个可用区出现故障时,能在 30 秒内完成流量切换,并且用户感知的错误率控制在 0.1% 以下。”

破题三步走

  1. 澄清需求:RTO(恢复时间目标)是多少?RPO(恢复点目标)是多少?是单服务故障还是整个 AZ/Region 级故障?
  2. 拆解子问题:故障检测 → 决策 → 流量切换 → 验证 → 回滚
  3. 分层设计:基础设施层(DNS/LB)、服务层(熔断/限流)、数据层(主从/多活)

2. 整体架构图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
     ┌─────────────────────────────┐
│ Global Load Balancer │
│ (Anycast IP / GeoDNS) │
└────────┬────────────┬────────┘
│ │
┌─────────────▼──┐ ┌───▼──────────────┐
│ Region A │ │ Region B │
│ ┌──────────┐ │ │ ┌────────────┐ │
│ │ API GW │ │ │ │ API GW │ │
│ └────┬─────┘ │ │ └─────┬──────┘ │
│ ┌────▼─────┐ │ │ ┌─────▼──────┐ │
│ │ Service │ │ │ │ Service │ │
│ │ Mesh │ │ │ │ Mesh │ │
│ └────┬─────┘ │ │ └─────┬──────┘ │
│ ┌────▼─────┐ │ │ ┌─────▼──────┐ │
│ │ DB (P) │◄─┼─────┼─►│ DB (S) │ │
│ └──────────┘ │ │ └────────────┘ │
└───────────────-┘ └──────────────────-┘
▲ ▲
│ │
┌─────────┴──────────────────────┴──────────┐
│ Observability Platform │
│ (Metrics / Tracing / Alerting / On-call) │
└────────────────────────────────────────────┘

关键组件:Global Load Balancer(流量入口)、双 Region 部署、跨 Region 数据同步、统一的可观测平台。


3. 故障检测

容灾的第一步是”知道坏了”。检测越快,RTO 越低。

3.1 分层检测体系

层次 检测手段 典型延迟
基础设施 Cloud Provider Health Check、BGP 路由收敛 1–5s
负载均衡 LB Active Health Check(HTTP 200 + 延迟阈值) 2–10s
服务网格 Envoy/Istio Outlier Detection(连续错误率) 秒级
业务指标 成功率 / P99 / 业务转化率 秒-分钟级
综合判断 告警聚合 + 自动决策引擎 数秒

3.2 Active Health Check vs Passive Detection

Active Health Check

  • LB 或 Sidecar 定期发探针请求(/healthz),连续 N 次失败则摘除
  • 优点:主动发现、可精确控制阈值
  • 缺点:探针不代表真实流量路径,可能漏报”局部故障”

Passive Detection(Outlier Detection)

  • 观测真实请求的错误率/连接超时,动态驱逐异常实例
  • Envoy 配置示例:
    1
    2
    3
    4
    5
    outlier_detection:
    consecutive_5xx: 5 # 连续 5 次 5xx 触发驱逐
    interval: 10s
    base_ejection_time: 30s
    max_ejection_percent: 50 # 最多驱逐 50% 的实例,防止雪崩

最佳实践:两者结合,Active 负责”快速摘除死实例”,Passive 负责”识别抖动实例”。

3.3 关键 SLI 指标

面试时需要说清楚监控什么:

1
2
3
4
成功率    = (成功请求数) / (总请求数)            目标 > 99.9%
P99 延迟 = 第 99 百分位响应时间 目标 < 500ms
错误预算 = 1 - SLO 用来触发告警阈值
饱和度 = CPU / Memory / Queue Depth 提前预警

告警策略建议用 多窗口告警(如同时满足 5min 窗口和 1min 窗口),避免瞬时抖动触发误报:

1
2
3
4
5
# 伪代码:多窗口 SLO 告警
def should_alert(metric):
w1 = success_rate(window="1m") < 0.99
w5 = success_rate(window="5m") < 0.995
return w1 and w5 # 两个窗口同时超阈值才告警

4. 流量切换

检测到故障后,核心问题是”怎么切”。面试官会追问:会不会切出新的故障?

4.1 流量切换层次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Layer 1: DNS 切换(分钟级)
└─ GeoDNS 将域名解析指向健康 Region
└─ TTL 需提前设低(30–60s),但生效仍需等 TTL 传播

Layer 2: Global LB 切换(秒级)
└─ Anycast IP 不变,BGP 路由调整权重
└─ 云厂商 Global LB(AWS GLB / GCP GCLB / 阿里 GA)原生支持

Layer 3: 服务网格层(毫秒级)
└─ Istio VirtualService / Envoy xDS 更新流量权重
└─ 灰度切量:10% → 50% → 100%

Layer 4: 客户端熔断(本地,无需等待)
└─ Hystrix / Resilience4j / Sentinel
└─ 请求直接 fail-fast 或走 fallback,不依赖服务端

4.2 金丝雀切流程序

永远不要一刀切,切流应该是渐进式的:

1
2
3
4
5
6
T=0s   检测到 Region A 错误率 > 5%
T=5s 自动告警 + 触发切流流程
T=10s 将 10% 流量切到 Region B,观察 B 的错误率
T=20s B 错误率正常 → 切 50%
T=30s B 错误率正常 → 切 100%(Region A 流量清零)
T=60s Region A 进入隔离,等待人工确认

关键判断:在每个阶段都要检查目标 Region 是否能承受新增流量(容量预留)。

4.3 熔断器(Circuit Breaker)

熔断器是服务层最常用的自保手段,三种状态:

1
2
3
4
5
6
7
8
9
10
Closed(正常)
│ 错误率超阈值

Open(熔断)── 等待 reset_timeout ──► Half-Open(探测)
│ │
│ ┌──────┴──────┐
│ │ 成功 │ 失败
│ ▼ ▼
│ Closed Open
◄──────────────────────────────────────────────────

配置参数(以 Resilience4j 为例):

1
2
3
4
5
6
7
resilience4j.circuitbreaker:
instances:
orderService:
slidingWindowSize: 100 # 最近 100 次请求
failureRateThreshold: 50 # 50% 错误率触发熔断
waitDurationInOpenState: 30s # 熔断持续 30s 后进入半开
permittedNumberOfCallsInHalfOpenState: 5

4.4 限流与降级

切流过程中,目标 Region 会承接额外流量,必须有限流兜底:

令牌桶 vs 漏桶

令牌桶(Token Bucket) 漏桶(Leaky Bucket)
允许突发 ✅ 是 ❌ 否
输出速率 不均匀 匀速
适用场景 API 限流(允许短时突发) 后端保护(平滑流量)

降级策略(按优先级):

  1. 返回缓存数据(Cache Fallback)
  2. 返回默认值(Default Response)
  3. 调用降级服务(Simplified Service)
  4. 直接拒绝并返回 429(最后手段)

5. 数据层容灾

流量切换了,数据怎么办?这是面试最容易被追问的地方。

5.1 RPO vs RTO 权衡

策略 RTO RPO 成本
冷备(Backup & Restore) 小时级 数小时
温备(Warm Standby) 分钟级 秒级
热备(Active-Passive) 秒级 近零
多活(Active-Active) 毫秒级 极高

5.2 主从切换(Active-Passive)

适合大多数互联网场景,要点:

  1. 异步复制:主库写入后异步同步到从库,RPO < 1s 可接受
  2. 自动 Failover:使用 MySQL MHA / PostgreSQL Patroni / Redis Sentinel 做自动主从切换
  3. 防止脑裂:引入外部仲裁(如 etcd / ZooKeeper),确保同一时刻只有一个主库
1
2
3
4
5
6
7
Primary (Region A)
│ binlog/WAL 复制

Replica (Region B)

├─ 监控复制延迟(Replication Lag)
└─ 延迟 > 阈值时告警,Failover 前确认数据完整性

5.3 多活数据库(Active-Active)

适合对 RTO/RPO 极致要求的场景(如支付、交易):

  • 分片(Sharding):按用户 ID / Region 分片,每个 Region 只写自己的分片,避免跨 Region 写冲突
  • 冲突解决:Last-Write-Wins(LWW)或 CRDT(Conflict-Free Replicated Data Types)
  • 全局事务:使用分布式事务(2PC / Saga),但需接受延迟升高

5.4 缓存层容灾

Redis 常见容灾方案:

1
2
3
4
5
单 Region:Redis Sentinel(1主2从+哨兵)
跨 Region:Redis Cluster + 跨 Region 复制(Redis Enterprise / 自建异步复制)

注意:跨 Region Redis 复制有延迟,切流时可能出现缓存击穿
防护:在切流前预热目标 Region 缓存(Cache Warming)

6. 自动化容灾决策

6.1 容灾编排流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 容灾决策引擎伪代码
class DisasterRecoveryOrchestrator:
def on_alert(self, alert):
region = alert.region
severity = alert.severity

# 1. 确认(避免误报触发)
if not self.confirm_failure(region, duration=10s):
return

# 2. 评估目标 Region 容量
target = self.select_healthy_region(exclude=region)
if not self.has_capacity(target, additional_load=self.current_traffic(region)):
self.trigger_scale_out(target) # 先扩容再切流
self.wait_for_scale_ready(timeout=60s)

# 3. 渐进切流
for pct in [10, 50, 100]:
self.shift_traffic(from_=region, to=target, percent=pct)
self.observe(duration=10s)
if self.error_rate(target) > threshold:
self.rollback()
raise DisasterRecoveryFailed()

# 4. 通知 On-call
self.notify_oncall(f"DR complete: {region}{target}")

6.2 人工干预节点

自动化容灾中必须保留人工干预点:

  • 切流到 100% 之前需要人工确认(防止自动化把坏流量带到健康 Region)
  • 数据库主从切换 建议半自动(系统检测 + 人工执行,防脑裂)
  • 回滚操作 必须随时可执行,且有明确的 Rollback Runbook

7. 混沌工程验证

容灾方案设计完不等于能用,需要通过混沌工程持续验证。

7.1 常见注入故障类型

故障类型 注入方式 验证目标
服务实例宕机 kill -9 进程 Outlier Detection + 服务发现
网络延迟 tc netem / toxiproxy 超时配置、熔断
网络丢包 iptables drop 重试策略
磁盘满 fallocate 写满 错误处理、告警
AZ 故障 关闭整个 AZ 路由 跨 AZ 切流
Region 故障 全 Region 流量归零 跨 Region DR

7.2 GameDay 演练

定期(每季度)进行 GameDay:

  1. 提前通知相关团队,确定演练时间窗口
  2. 注入故障,观察系统自动响应
  3. 如超过 RTO 未恢复,人工接管
  4. 演练后写 Post-Mortem,更新 Runbook

8. 面试加分项

8.1 回答框架

面试时按 STAR 结构回答:

  • Situation:说清楚规模(QPS、节点数、数据量)
  • Task:明确 RTO/RPO 目标
  • Action:逐层讲解检测、切流、数据容灾
  • Result:量化结果(切流时间、错误率)

8.2 常见追问和参考答案

Q: 如何防止切流时造成新的故障(切流雪崩)?

A: 切流前检查目标 Region 容量,先扩容再切流;切流过程中持续监控目标 Region 的 CPU/内存/队列深度;设置最大切流速度,允许随时 Rollback。

Q: DNS 切换很慢,怎么加速?

A: 提前将 TTL 降到 30–60s(正常时是 300s);使用 Anycast + BGP 路由调整代替 DNS,秒级生效;在客户端 SDK 中绕过 DNS 缓存,直接刷新 IP 列表。

Q: 多活架构下如何解决数据一致性?

A: 按照数据的写入来源分片,避免同一数据在多个 Region 同时写入;对于必须跨 Region 的写(如全局库存),使用分布式事务(Saga 补偿模式),并接受最终一致性;对强一致场景(支付),只在主 Region 写,多活退化为读多活。

Q: 如何区分”局部抖动”和”真实故障”,避免误切流?

A: 使用多窗口告警(短窗口触发预警,长窗口触发切流);引入多个独立的探测源(LB 健康检查 + 业务成功率 + 外部探测),至少两个来源同时异常才触发切流;设置最小观测时间(如连续 10s 异常)。

8.3 设计权衡总结

决策点 选项 A 选项 B 推荐
检测速度 激进阈值(快但误报多) 保守阈值(慢但准确) 多窗口结合
切流速度 立即全切(快但风险大) 渐进切流(慢但安全) 渐进切流
数据一致 强一致(延迟高) 最终一致(延迟低) 按业务选择
自动化程度 全自动(快但风险高) 全手动(安全但慢) 半自动+人工确认关键步骤

总结

设计快速容灾的微服务系统,核心是快速检测 + 安全切流 + 数据保障三位一体:

  1. 检测:分层检测(LB 探针 + Outlier Detection + 业务指标),多窗口告警减少误报
  2. 切流:Global LB + 服务网格实现秒级切流,渐进式(10%→50%→100%)避免雪崩,配合熔断限流做自保
  3. 数据:根据 RTO/RPO 选择主从热备或多活,缓存提前预热,防止切流后缓存击穿
  4. 验证:混沌工程 + 定期 GameDay,让容灾能力持续可信

面试核心:不要只说”我会用 Kubernetes 部署多副本”,而是要展示你对故障检测延迟、切流安全性、数据一致性权衡的深入思考。