灰度、Feature Flag 与回滚
正在读取学习记录…
资料助手增加“检索后再核对引用”的新路径。正常发布时,先让一小部分用户走新路径,确认回答质量、延迟和费用可接受后,再逐步扩大。灰度发布就是分阶段让部分流量使用变更;Feature Flag(功能开关)是在运行时控制某项功能是否启用的配置;回滚是将程序、配置或流量路由恢复到先前可用状态。
三者可以一起使用:部署含新旧两条路径的程序,开关先给5%的用户启用新路径,观测通过后扩到25%;发生问题时关闭开关,让后续请求回到旧路径。Google SRE 的灰度发布章节说明了通过少量真实流量评估发布风险的思路。
同一个用户怎样一直走同一条路径
如果每次请求都随机决定,用户可能一会儿看到新结果、一会儿看到旧结果,难以复现问题。示例给用户ID计算稳定哈希,映射到0–99号桶。桶号小于5时走新路径;扩大到25后,原来进入的用户仍在新路径。这里的5%是桶比例,小样本实际人数会波动,100名固定用户恰好有7名命中。
按图从左到右看: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 创建并进入目录,再保存下方两份文件:
New-Item -ItemType Directory -Force l8-release
cd l8-releasemacOS 终端:
mkdir -p l8-release
cd l8-release"""演示稳定分组、功能开关与固定观测窗口的发布决策。"""
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:
"""验证确定性分组、扩容单调性、关停优先与观测门槛。"""
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()文件保存后,在同一窗口核对文件名并运行:
ls
python -X utf8 canary_release.py
python -X utf8 -m unittest -v test_canary_release.pydir应显示这两个文件。运行程序后得到下面输出;测试末尾应显示 OK。
程序输出:
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"),运行:
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 |
| 门槛失败 | 停止扩大,关闭开关,核对路由 | 新请求回到旧路径 |
| 恢复后 | 检查在途任务与数据兼容 | 外部副作用单独对账 |
逐步发布的价值来自可观察、可停止、可恢复。分组算法、阈值判断和关闭优先级可在本地验证,真实平台的配置传播和在途请求处理应通过部署演练确认。
课后练习展开习题收起习题
正在读取本地记录…
不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。
选择题 · 先判断边界
关于“灰度、Feature Flag 与回滚”,哪种做法更稳妥?
对应知识点:L8-release
简答题 · 解释失败
写出一个失败输入并说明记录字段。
对应知识点:L8-release
代码题 · 边界测试
补充正常、边界、失败三组输入和预期结果。
对应知识点:L8-release
场景题 · 结果未知
证据不足时如何设计状态和人工接管?
对应知识点:L8-release
接着学习备份、恢复与事件响应,处理需要恢复状态的故障。
描述会发送给本站维护者,用于修订文章,保存90天。请只填写文章问题,省略个人信息、密钥和内部资料。