高可用不等于少故障:用指标、冗余与恢复机制设计分布式系统

2026-07-21 20 预计阅读时间: 1 分钟
来源: my.oschina.net AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:9 分钟

分布式系统讨论“稳定性”时,经常把高可用与高可靠混在一起。两者确实共享不少指标和工程手段,但关注点并不相同:可用性强调系统有多少时间能够提供服务,可靠性更关注故障发生得是否频繁。一个系统可能每小时故障一次,却能在一秒内恢复,因此可用率看起来很高,但用户仍会持续遇到请求失败。

同样是 99.9%,故障体验可能完全不同

可用性通常可以近似表示为:

Availability = MTBF / (MTBF + MTTR)

其中:

  • MTBF 是平均无故障时间,即两次故障之间平均运行多久。
  • MTTR 是平均恢复时间,即故障发生后平均多久恢复服务。

这个公式揭示了两条提高可用性的路径:减少故障频率,或者缩短恢复时间。前者更接近可靠性建设,后者则是高可用架构经常重点解决的问题。

例如,下面两个系统在一天内都中断了约 86 秒:

系统 故障次数 单次持续时间 日可用率
A 1 次 86 秒 约 99.9%
B 86 次 1 秒 约 99.9%

单看日可用率,两者几乎相同;从可靠性和用户体验看,系统 B 明显更糟。频繁抖动会导致连接重建、请求重试、消息重复消费和告警风暴,还可能把局部故障放大成级联故障。

因此,生产环境不能只保留一个可用率数字。至少还应观察故障次数、故障持续时间、错误率,以及受影响请求数。

高可用战术要同时覆盖检测、隔离和恢复

分布式系统不会因为部署了多个副本就自动获得高可用。完整的故障处理链路通常包括四个动作:

  1. 检测故障:通过健康检查、超时、心跳和业务指标判断实例是否还能服务。
  2. 隔离故障:把异常实例移出流量路径,利用熔断或舱壁限制影响范围。
  3. 恢复服务:重启进程、切换副本、重新调度实例或执行主备切换。
  4. 修复原因:排查资源耗尽、代码缺陷、依赖抖动和容量不足,降低故障频率。

前三项主要缩短 MTTR,第四项主要提升 MTBF。只做自动重启,系统可能拥有不错的可用率,却长期处于“不断失败、不断拉起”的状态;只追求代码零缺陷,又可能因为缺少自动切换而让一次普通故障持续数小时。

冗余也有明确边界。多个副本如果位于同一台宿主机、共享同一个数据库,或者依赖同一条网络链路,就仍然存在共同故障点。设计时需要按故障域检查副本是否真正独立,包括进程、节点、可用区和外部依赖。

可以这样实践:分别计算可用率与故障频率

下面的 Python 脚本读取一组故障事件,同时计算观察窗口内的可用率、故障次数、平均恢复时间和近似 MTBF。它只使用标准库,可以直接运行。

将以下内容保存为 availability.py,再执行 python3 availability.py

from dataclasses import dataclass


@dataclass(frozen=True)
class Incident:
    started_at: float
    recovered_at: float

    @property
    def duration(self) -> float:
        return self.recovered_at - self.started_at


def report(window_seconds: float, incidents: list[Incident]) -> None:
    downtime = sum(item.duration for item in incidents)
    uptime = max(window_seconds - downtime, 0)
    count = len(incidents)

    availability = uptime / window_seconds if window_seconds else 0
    mttr = downtime / count if count else 0
    mtbf = uptime / count if count else uptime

    print(f"availability: {availability:.5%}")
    print(f"incident count: {count}")
    print(f"total downtime: {downtime:.1f}s")
    print(f"MTTR: {mttr:.1f}s")
    print(f"approximate MTBF: {mtbf:.1f}s")


DAY = 24 * 60 * 60

print("System A: one longer incident")
report(DAY, [Incident(10_000, 10_086)])

print("\nSystem B: many short incidents")
report(DAY, [Incident(i * 900, i * 900 + 1) for i in range(86)])

两个样例的总停机时间和可用率接近,但系统 B 的故障次数远高于系统 A。实际接入监控平台时,可以沿用同样的思路,把事件数据替换为告警记录、探针状态变化或服务端错误窗口。

需要注意,这个脚本假设故障区间互不重叠。生产统计应先合并重叠区间,否则多个实例同时告警可能造成停机时间重复计算。系统级可用性也不应直接由单实例存活时间推导,而应根据用户是否能完成关键请求来判断。

从指标到部署策略

以 Kubernetes 为例,可以这样实践服务副本、健康探针和滚动更新。下面的配置假设应用在 8080 端口提供 /live/ready 接口;部署前需要把镜像地址替换为自己的镜像。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-api
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0
      maxSurge: 1
  selector:
    matchLabels:
      app: order-api
  template:
    metadata:
      labels:
        app: order-api
    spec:
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: kubernetes.io/hostname
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchLabels:
              app: order-api
      containers:
        - name: api
          image: registry.example.com/order-api:1.0.0
          ports:
            - containerPort: 8080
          readinessProbe:
            httpGet:
              path: /ready
              port: 8080
            periodSeconds: 5
            failureThreshold: 2
          livenessProbe:
            httpGet:
              path: /live
              port: 8080
            periodSeconds: 10
            failureThreshold: 3
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              memory: 256Mi
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: order-api
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: order-api
kubectl apply -f deployment.yaml
kubectl rollout status deployment/order-api
kubectl get pods -l app=order-api -o wide
kubectl get poddisruptionbudget order-api

这里的就绪探针负责阻止未准备好的实例接收流量,存活探针用于触发失效进程重启,拓扑分布约束减少副本集中在同一节点的风险,PodDisruptionBudget 则约束自愿中断期间的可用副本数量。

这些配置不能单独证明系统已经高可用。探针路径如果依赖所有下游服务,短暂的依赖抖动可能让全部副本同时退出流量;存活探针过于敏感,也会制造重启循环。探针阈值必须结合启动时间、请求延迟和依赖特征通过故障演练校准。

落地时检查这五件事

高可用建设不应止于“部署三个副本”。上线前可以逐项确认:

  • SLO 是否基于用户关键请求,而不是仅统计进程存活时间。
  • 是否同时记录可用率、错误率、故障次数、MTTR 和受影响请求数。
  • 副本是否跨越真实故障域,并识别数据库、缓存和网络等共同依赖。
  • 超时、重试、熔断和限流是否配套,重试是否带退避与随机抖动。
  • 是否定期执行实例退出、节点故障和依赖超时演练,并验证告警与恢复链路。

高可用与可靠性可以使用相同的冗余、监控和故障处理手段,但不能只用一个百分比评价。缩短恢复时间能减少停机,减少故障次数才能降低系统抖动。成熟的分布式系统需要同时改善 MTTRMTBF,并用用户请求的实际结果验证这些改进。


相关推荐