AgentDepthAgent 工程学习指南
输入概念或问题,检索正文和待编写目录。
第一次学习?从 L0 总览开始 →

SLO、SLA 与错误预算

我的学习 →

正在读取学习记录…

用户希望资料助手在2秒内给出可用答案。运营团队需要知道过去一天是否做到了,并决定今天能否发布新版本。SLI(服务水平指标)是实际测出来的质量比例;SLO(服务水平目标)是希望该比例达到的目标;SLA(服务水平协议)是对外约定的承诺,以及未达到时怎样处理。

正常例子中,1000次有效请求有990次业务成功且在2秒内完成,SLI为99%;目标99%时刚好达到SLO。SLA还需要约定统计窗口、适用范围和违约后果,例如服务抵扣,不能从一个内部指标自动推导合同承诺。Google SRE 的服务目标章节对这三个概念及目标选择有系统说明。

先决定一次请求怎样算好

“HTTP返回200”与“用户得到可用答案”之间还有距离。这里把好请求定义为:业务成功完成,并且端到端耗时不超过2000毫秒。有效请求包括超时、下游失败和容量不足的业务请求;无效输入如何排除,要预先定义并固定,避免为提高指标临时改分母。

固定窗口包含950次成功400毫秒、30次成功800毫秒、20次失败3000毫秒。这1000条是教学数据,程序负责按统一判据统计。

指标公式及代入结果
好请求比例SLI980 ÷ 100098%
目标SLO给定目标0.9999%
允许坏请求1000 × (1 − 0.99)10次
实际坏请求1000 − 98020次
剩余错误预算10 − 20−10次

错误预算是服务目标允许的失败空间。剩余为负表示已超出窗口预算;团队可约定暂停增加风险的发布,优先恢复可靠性。预算是运营策略输入,并不会自动修复故障。

SLO、SLA 与错误预算机制图
上方按比例表示1000次请求,其中980次满足要求;下方比较20次实际失败与10次允许预算。

图上方按1000条固定记录画出980个好请求占98%、20个坏请求占2%的比例;下方单独比较允许10次与实际20次,帮助看出预算超支。

计算预算并检查延迟分位数

p95表示排序后覆盖95%样本的延迟。示例使用最近秩法:位置 ceil(0.95 × 1000) = 950,第950项是400毫秒。因此p95看起来很快,但20次失败仍消耗预算。可用性与延迟指标要分别观察,不能只选最好看的那个。

建立新文件夹 l8-slo,保存 error_budget.py 和 test_error_budget.py。程序使用固定记录与十进制目标,直接输出预算和发布策略。

先在 PowerShell 创建并进入目录,再保存下方两份文件:

PowerShell
New-Item -ItemType Directory -Force l8-slo
cd l8-slo

macOS 终端:

Bash
mkdir -p l8-slo
cd l8-slo
Python
"""从预设请求记录计算用户可感知成功率和错误预算。"""
from decimal import Decimal
from math import ceil

TARGET = Decimal("0.99")
# 每项为(服务是否成功完成、端到端毫秒数);共1000条固定记录。
REQUESTS = [(True, 400)] * 950 + [(True, 800)] * 30 + [(False, 3000)] * 20

def summarize(requests, target):
    if not Decimal("0") < target <= Decimal("1"):
        raise ValueError("目标必须大于0且不超过1")
    if not requests:
        raise ValueError("窗口内没有有效请求")
    total = len(requests)
    # 业务成功且在2秒内完成才算好请求,超时也消耗错误预算。
    good = sum(ok and elapsed <= 2000 for ok, elapsed in requests)
    bad = total - good
    allowed = Decimal(total) * (1 - target)
    remaining = allowed - bad
    # 用最近秩定义p95:排序后第 ceil(0.95*N) 项。
    latencies = sorted(elapsed for _, elapsed in requests)
    p95 = latencies[ceil(0.95 * total) - 1]
    return total, good, bad, allowed, remaining, p95

def main():
    total, good, bad, allowed, remaining, p95 = summarize(REQUESTS, TARGET)
    print(f"窗口: {total} / 好请求: {good} / 坏请求: {bad}")
    print(f"SLI: {Decimal(good) / total * 100:.2f}% / SLO: {TARGET * 100:.2f}%")
    print(f"允许坏请求: {allowed:.0f} / 剩余预算: {remaining:.0f}")
    print(f"p95: {p95}毫秒")
    print("发布策略:", "冻结非修复发布" if remaining < 0 else "允许受控发布")

if __name__ == "__main__":
    main()

测试文件 test_error_budget.py:

Python
"""检验窗口、成功判据、最近秩和预算耗尽的边界。"""
from decimal import Decimal as D
import unittest
from error_budget import REQUESTS, summarize

class BudgetTests(unittest.TestCase):
    def test_baseline(self):
        self.assertEqual(summarize(REQUESTS, D("0.99")),
                         (1000, 980, 20, D(10), D(-10), 400))

    def test_revised_target(self):
        self.assertEqual(summarize(REQUESTS, D("0.97"))[4], D(10))

    def test_budget_exactly_zero(self):
        self.assertEqual(summarize([(True, 1)] * 99 + [(False, 1)], D("0.99"))[4], D(0))

    def test_latency_and_business_success(self):
        self.assertEqual(summarize([(True, 2000), (True, 2001), (False, 10)], D("0.9"))[1:3],
                         (1, 2))

    def test_empty_and_invalid(self):
        with self.assertRaises(ValueError):
            summarize([], D("0.99"))
        for target in (D(0), D("1.1")):
            with self.assertRaises(ValueError):
                summarize(REQUESTS, target)

if __name__ == "__main__":
    unittest.main()

文件保存后,在同一窗口核对文件名并运行:

Bash
ls
python -X utf8 error_budget.py
python -X utf8 -m unittest -v test_error_budget.py

dir应显示这两个文件。运行程序后得到下面输出;测试末尾应显示 OK。

程序输出:

TXT
窗口: 1000 / 好请求: 980 / 坏请求: 20
SLI: 98.00% / SLO: 99.00%
允许坏请求: 10 / 剩余预算: -10
p95: 400毫秒
发布策略: 冻结非修复发布

改变目标会改变哪一个数字

在 error_budget.py 把 TARGET = Decimal("0.99") 改成 TARGET = Decimal("0.97"),保存后运行:

Bash
python -X utf8 error_budget.py

SLI仍为98.00%,p95仍为400毫秒;SLO变为97.00%,允许坏请求30,剩余预算10,发布策略变为“允许受控发布”。恢复 TARGET = Decimal("0.99") 并重新运行,回到冻结策略。

这个动作帮助区分测量与目标:数据未改变,目标降低并没有改善用户体验。实际调整SLO应基于业务需求与服务成本,经约定后作用于明确窗口;不能用临时放宽目标掩盖当前事故。

预算如何进入值班和发布

生产统计需要固定窗口,例如滚动30天;滚动表示每天移除最早一天、加入最新一天。一次1000请求样本的计算不能直接代表整月预算。对于按请求比例计算的目标,允许失败次数随有效请求数量变化;按时间可用性计算的目标,则用总时长乘允许失败比例,两种分母应明确选择。

消耗速率也有用。若目标允许1%坏请求,而最近10分钟出现5%坏请求,预算燃烧率为 5% ÷ 1% = 5:以长期允许速率的5倍消耗。短窗口帮助发现快速故障,较长窗口帮助过滤噪声;低流量时一两个请求就可能产生大幅比例变化,需要同时观察样本量。

现象判断动作
p95达标但好请求比例不足尾部失败影响用户查超时和下游错误
剩余预算恰好为0当前窗口刚好用完按策略限制下一步风险
预算为负且故障仍增长继续消耗,目标已失守先止损,再定位
没有有效请求指标缺少数据返回无数据,不能记100%

Agent还可设置质量SLI,如“回答有可验证来源的比例”,但需要稳定的判定规则和样本。把可用性、延迟、质量及成本分开定义,发布时检查它们的组合,才不会因一个状态码成功而忽视业务失败。

课后练习展开习题收起习题

4 道练习 · 客观题规则评分 · 其余题目待批改

正在读取本地记录…

不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。

练习 1 / 4 · v1.0.1

选择题 · 先判断边界

关于“SLO、SLA 与错误预算”,哪种做法更稳妥?

对应知识点:L8-slo

选择一个答案
练习 2 / 4 · v1.0.0

简答题 · 解释失败

写出一个失败输入并说明记录字段。

对应知识点:L8-slo

练习 3 / 4 · v1.0.0

代码题 · 边界测试

补充正常、边界、失败三组输入和预期结果。

对应知识点:L8-slo

练习 4 / 4 · v1.0.0

场景题 · 结果未知

证据不足时如何设计状态和人工接管?

对应知识点:L8-slo

下一篇灰度、Feature Flag与回滚用观测结果决定发布方向。

图片查看器
100%

文章配图 · 按 Esc 关闭