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

灰度、Feature Flag 与回滚

我的学习 →

正在读取学习记录…

资料助手增加“检索后再核对引用”的新路径。正常发布时,先让一小部分用户走新路径,确认回答质量、延迟和费用可接受后,再逐步扩大。灰度发布就是分阶段让部分流量使用变更;Feature Flag(功能开关)是在运行时控制某项功能是否启用的配置;回滚是将程序、配置或流量路由恢复到先前可用状态。

三者可以一起使用:部署含新旧两条路径的程序,开关先给5%的用户启用新路径,观测通过后扩到25%;发生问题时关闭开关,让后续请求回到旧路径。Google SRE 的灰度发布章节说明了通过少量真实流量评估发布风险的思路。

同一个用户怎样一直走同一条路径

如果每次请求都随机决定,用户可能一会儿看到新结果、一会儿看到旧结果,难以复现问题。示例给用户ID计算稳定哈希,映射到0–99号桶。桶号小于5时走新路径;扩大到25后,原来进入的用户仍在新路径。这里的5%是桶比例,小样本实际人数会波动,100名固定用户恰好有7名命中。

灰度、Feature Flag 与回滚机制图
09:00开启5%桶,100名固定用户有7人命中;09:05收集100请求后,3%失败率高于2%门槛,09:06关闭路由。

按图从左到右看:09:00开启5%桶,09:05观测新路径100次请求、失败3次,09:06将3%失败率与2%阈值比较,触发关闭及回滚路由。这100次请求来自新路径观测窗口,并非100名用户各一次请求。

控制操作对象生效位置对已经开始的动作
功能开关新路径是否开启业务分支入口已开始请求需要单独处理
灰度比例哪些用户进入新路径稳定分组/路由不会撤销已经生成的结果
程序回滚运行制品部署平台需排空或终止在途任务
数据恢复持久化状态数据库/日志需专门恢复与校验

用固定观测窗口决定下一步

建立新文件夹 l8-release,保存 canary_release.py 和 test_canary_release.py。程序执行分组和决策,观测窗口预设97次成功、3次失败。最少100个样本才判断;不足时保持灰度,以免几个请求就推动全量发布。

紧急关闭 kill=True 的优先级高于比例,任何用户都返回旧路径。决策函数给出动作建议,程序末行单独演示关闭后人数为0;实际发布系统需要按建议调用配置或路由平台,并确认生效。

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

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

macOS 终端:

Bash
mkdir -p l8-release
cd l8-release
Python
"""演示稳定分组、功能开关与固定观测窗口的发布决策。"""
from hashlib import sha256
from decimal import Decimal

MAX_BAD_RATE = Decimal("0.02")
# 观测窗口固定100次新路径请求,有3次业务失败。
WINDOW = [True] * 97 + [False] * 3

def enabled(user_id, percent, kill=False):
    if not 0 <= percent <= 100:
        raise ValueError("灰度比例必须在0到100之间")
    if kill:
        return False
    # 相同用户始终落入同一桶;加大比例保留已进入灰度的用户。
    bucket = int.from_bytes(sha256(user_id.encode()).digest()[:4], "big") % 100
    return bucket < percent

def decision(window, threshold, minimum=100):
    if not Decimal("0") <= threshold <= Decimal("1"):
        raise ValueError("失败率阈值必须在0到1之间")
    if len(window) < minimum:
        return "保持灰度", None
    rate = Decimal(sum(not good for good in window)) / len(window)
    return ("关闭开关并回滚路由" if rate > threshold else "扩大到25%"), rate

def main():
    users = [f"user-{i}" for i in range(100)]
    cohort = sum(enabled(user, 5) for user in users)
    print(f"09:00 开关5% / 固定100名用户进入新路径{cohort}名")
    print("09:05 新路径请求100次 / 失败3次")
    action, rate = decision(WINDOW, MAX_BAD_RATE)
    print(f"失败率: {rate * 100:.2f}% / 阈值: {MAX_BAD_RATE * 100:.2f}%")
    print(f"09:06 决策: {action}")
    print("紧急关闭后新路径人数:", sum(enabled(user, 5, kill=True) for user in users))

if __name__ == "__main__":
    main()

测试文件 test_canary_release.py:

Python
"""验证确定性分组、扩容单调性、关停优先与观测门槛。"""
from decimal import Decimal as D
import unittest
from canary_release import enabled, decision, WINDOW

class ReleaseTests(unittest.TestCase):
    def test_same_user_and_monotonic(self):
        for i in range(100):
            user = f"user-{i}"
            self.assertEqual(enabled(user, 5), enabled(user, 5))
            self.assertFalse(enabled(user, 5) and not enabled(user, 25))

    def test_kill_and_percentage_edges(self):
        self.assertFalse(enabled("alice", 100, kill=True))
        self.assertFalse(enabled("alice", 0))
        self.assertTrue(enabled("alice", 100))
        with self.assertRaises(ValueError):
            enabled("alice", 101)

    def test_insufficient_window(self):
        self.assertEqual(decision([False] * 99, D("0.02")), ("保持灰度", None))

    def test_failure_and_changed_threshold(self):
        self.assertEqual(decision(WINDOW, D("0.02"))[0], "关闭开关并回滚路由")
        self.assertEqual(decision(WINDOW, D("0.04"))[0], "扩大到25%")

    def test_exact_threshold(self):
        self.assertEqual(decision([True] * 98 + [False] * 2, D("0.02"))[0], "扩大到25%")

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

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

Bash
ls
python -X utf8 canary_release.py
python -X utf8 -m unittest -v test_canary_release.py

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

程序输出:

TXT
09:00 开关5% / 固定100名用户进入新路径7名
09:05 新路径请求100次 / 失败3次
失败率: 3.00% / 阈值: 2.00%
09:06 决策: 关闭开关并回滚路由
紧急关闭后新路径人数: 0

将失败率阈值从2%改到4%

在 canary_release.py 把唯一一行 MAX_BAD_RATE = Decimal("0.02") 改成 MAX_BAD_RATE = Decimal("0.04"),运行:

Bash
python -X utf8 canary_release.py

用户分组仍为7名,新路径失败率仍为3.00%,阈值变为4.00%,09:06决策变成“扩大到25%”。末行紧急关闭示例仍为0,因为它独立展示kill开关。恢复 MAX_BAD_RATE = Decimal("0.02") 后再运行,决策回到关闭并回滚。

阈值应在发布前确定。提高阈值改变的是允许风险,实际失败数量没有减少。比较新旧路径时,还要用相同用户类型、请求类型和统计窗口,避免把灰度用户的特殊流量误认成版本变化。

Agent发布需要一起管理哪些版本

提示词、工具Schema、模型别名、检索索引和权限策略都会影响行为。发布记录应把它们作为一组配置追踪,观察业务成功、来源正确率、延迟、费用与安全拒绝。新路径若让费用翻倍,即使可用性通过,也可能触发成本门槛。

回滚之前确认旧程序能读取新写入的数据。例如先增加可选字段、让新旧代码都兼容,再逐步启用新行为;删除字段或改变不可逆结构应单独规划迁移。对发送邮件、下单等外部副作用,需要幂等ID、结果查询与补偿,关闭开关无法撤回已发生动作。

时间点发布者动作可检查的状态
发布前固定旧制品、关停动作与门槛旧路径可用,开关可关闭
开始灰度5%稳定分组,观察新旧路径用户分组与配置一致
窗口不足继续收集,保持比例样本量未达100
门槛失败停止扩大,关闭开关,核对路由新请求回到旧路径
恢复后检查在途任务与数据兼容外部副作用单独对账

逐步发布的价值来自可观察、可停止、可恢复。分组算法、阈值判断和关闭优先级可在本地验证,真实平台的配置传播和在途请求处理应通过部署演练确认。

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

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

正在读取本地记录…

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

练习 1 / 4 · v1.0.1

选择题 · 先判断边界

关于“灰度、Feature Flag 与回滚”,哪种做法更稳妥?

对应知识点:L8-release

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

简答题 · 解释失败

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

对应知识点:L8-release

练习 3 / 4 · v1.0.0

代码题 · 边界测试

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

对应知识点:L8-release

练习 4 / 4 · v1.0.0

场景题 · 结果未知

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

对应知识点:L8-release

接着学习备份、恢复与事件响应,处理需要恢复状态的故障。

图片查看器
100%

文章配图 · 按 Esc 关闭