3. Error Budget 核心公式:Error Budget = 1 – SLO,消耗速率与剩余预算的计算模型

Error Budget(错误预算)是 SRE 实践中最核心的量化杠杆。它将抽象的“可靠性”转化为一个可消耗、可监控、可触发自动化动作的数值指标。理解其公式与计算模型,是实现熔断降级阈值自动调优的前提。

3.1 核心公式:Error Budget = 1 – SLO

错误预算的数学定义极其简洁:

Error Budget = 1 – SLO

其中 SLO 以小数形式表示。例如:

  • SLO = 99.9% → Error Budget = 0.1%
  • SLO = 99.99% → Error Budget = 0.01%
  • SLO = 99.5% → Error Budget = 0.5%

这个公式的含义是:系统在 SLO 承诺之上,允许出现一定比例的“不可用”或“错误”事件。这些允许的“错误配额”就是错误预算。它不是用来“浪费”的,而是用来平衡可靠性与迭代速度的。

关键认知: Error Budget 不是“失败指标”,而是“创新燃料”。只要预算未耗尽,团队可以放心地进行灰度发布、功能实验甚至性能压测,而不必担心触发过激的熔断。

3.2 消耗速率与剩余预算的计算模型

错误预算的消耗不是静态的,而是随时间窗口动态累积的。我们需要建立消耗速率和剩余预算的实时计算模型。

3.2.1 基础计算参数

参数 符号 说明 典型值
服务等级目标 SLO 承诺的可靠性目标(小数) 0.999
总事件数 Total 窗口内的总请求数 动态
错误事件数 Errors 窗口内失败的请求数 动态
错误率 ER Errors / Total 动态
时间窗口 W 滚动计算窗口(秒) 3600s(1小时)

3.2.2 消耗速率公式

消耗速率表示单位时间内错误预算被“烧掉”的速度:

消耗速率 = (当前错误率 – SLO) × 总请求数 / 时间窗口

更直观的工程实现方式:

消耗速率 = max(0, ER – SLO) × Total / W

当 ER ≤ SLO 时,消耗速率为 0,预算不减少甚至可能恢复(如果采用滑动窗口累积模型)。

3.2.3 剩余预算公式

剩余预算表示当前时间窗口内,系统还剩下多少“错误配额”:

剩余预算 = 初始预算 – 已消耗预算

其中:

初始预算 = Total × (1 – SLO)
已消耗预算 = Total × ER

因此:

剩余预算 = Total × (1 – SLO) – Total × ER
         = Total × (1 – SLO – ER)

当剩余预算 ≤ 0 时,表示错误预算已耗尽,系统处于 SLO 违规状态。

工程陷阱: 不要使用“剩余预算百分比”作为唯一判断依据。在低流量时段,少量错误可能导致百分比剧烈波动。建议同时监控剩余预算值和消耗速率趋势。

3.3 滑动窗口累积模型

实际生产环境中,错误预算的计算通常基于滑动时间窗口,而非固定周期。这能更平滑地反映系统状态变化。

# 伪代码:滑动窗口错误预算计算
class ErrorBudgetCalculator:
    def __init__(self, slo: float, window_seconds: int = 3600):
        self.slo = slo
        self.window = window_seconds
        self.events = deque()  # 存储 (timestamp, is_error)

    def record(self, timestamp: float, is_error: bool):
        self.events.append((timestamp, is_error))
        # 清理过期事件
        while self.events and self.events[0][0] < timestamp - self.window:
            self.events.popleft()

    def remaining_budget(self) -> float:
        total = len(self.events)
        if total == 0:
            return 0.0
        errors = sum(1 for _, e in self.events if e)
        error_rate = errors / total
        # 剩余预算 = 总请求 × (1 - SLO - 错误率)
        return total * (1 - self.slo - error_rate)

    def is_budget_exhausted(self) -> bool:
        return self.remaining_budget() <= 0

3.4 消耗速率与剩余预算的关系图

下图展示了在 1 小时滑动窗口内,错误率变化如何影响消耗速率和剩余预算的实时状态:

Error Budget 消耗速率与剩余预算实时计算模型 实时请求流 错误事件流 滑动窗口引擎 窗口大小: 3600s SLO: 99.9% Total, Errors 累积 消耗速率 max(0, ER - SLO) × Total / W 剩余预算 Total × (1 - SLO - ER) 熔断/降级决策 剩余预算 ≤ 0 → 触发 关键说明: • 消耗速率反映错误预算“燃烧”的快慢,用于预测何时耗尽。 • 剩余预算反映当前“安全余量”,直接决定是否触发熔断。 • 两者结合使用:消耗速率高但剩余预算充足时,可提前降级而非熔断。 滑动窗口时间轴示例(1小时) T-3600s 当前时间 T+0s ● 实时计算点

3.5 多窗口策略:短窗口熔断 + 长窗口调优

单一窗口无法同时满足快速响应和稳定调优的需求。推荐采用双窗口策略:

窗口类型 窗口大小 用途 触发阈值
短窗口 60s – 300s 快速熔断,防止雪崩 剩余预算 ≤ 0 或 消耗速率 > 阈值
长窗口 3600s – 86400s 稳态调优,降级阈值自适应 剩余预算百分比 < 安全水位(如 20%)
实践建议: 短窗口负责“刹车”,长窗口负责“调整刹车力度”。在下一章中,我们将基于此双窗口模型,设计熔断降级阈值的自动调优算法。

3.6 常见误区与避坑

  • 误区一: 错误预算耗尽才触发熔断。→ 正确做法: 在预算剩余 20%-30% 时提前降级,避免熔断的“硬着陆”。
  • 误区二: 使用固定窗口(如每小时重置)。→ 正确做法: 使用滑动窗口,避免边界效应导致误判。
  • 误区三: 忽略消耗速率,只看剩余预算。→ 正确做法: 消耗速率是“加速度”,剩余预算是“剩余里程”,两者缺一不可。
  • 误区四: 所有服务使用相同的 SLO 和窗口。→ 正确做法: 核心服务用 99.99% + 长窗口,边缘服务用 99.5% + 短窗口。