通过仿真量化展示:有限容量 + 瞬时触发 + 全量重试 = metastable failure,正确设置重试预算可将故障请求从 4543 降至 0。
Level 0 of Arc Ops 让重试工具调用变得可以安全地重复。这个级别讲的是接下来会发生什么——也就是它会被重复执行——所有客户端在同一时刻、依赖最无力应答的那一刻发起重试。重试是失败制造出的请求,而亚稳态失败并不需要什么特殊条件:有限的容量、短时的触发源、加上会重试的客户端。
Repo: https://github.com/dev48v/arc-ops - PUBLIC, MIT,依赖 = [],238 个 pytest(其中 114 个在新 L1 文件中),无网络依赖、无模型依赖。Level 1 在一个真正自包含的页面中计算:没有 CDN、没有字体托管、页面本身不获取任何外部资源 - https://dev48.infy.uk/arcops/level1-retry-budgets.html
每个 tick 稳定 100 个 intent,容量 120,然后连续 10 个 tick 跌至 40。四个 fleet,流量相同,每个面板都附带对照组(budget.NoBudget,可选 use: none)。
这个 dip 最多只能导致 600 个请求失败。对照组实际失败了 7.57 倍,达到 2,443 个请求——这些请求是在依赖已经恢复健康之后才到达的——并把一个 10 tick 的 dip 演变成了 39 tick 的故障。
差距不在于 max_retries,三行数据的第一个 row 都是 3。按调用次数的限制会乘以一个没人选择的客户端数;而表示为成功流量比例的 budget 则无论存在多少客户端都将 fleet 限制在 1.1 倍正常水平。重试由成功来资助,所以在完全宕机的情况下没有成功:仅计数策略在 1,000 次调用中仍允许 3,000 次重试,令牌桶允许 10 次——这是 min_tokens 下限,也是该方案中刻意留出的空洞。
l1_retry_budgets:
use: token_bucket
settings:
budget_ratio: 0.1 # 没有默认值——必须显式选择
jitter: full
max_retries: 3 # 保留最后:每个代码库都有的配置,
# 但在四个配置中恰恰是最不重要的
权衡指向了与我最初想说的相反方向
我原本预期 budget 会让故障规模变小。它并没有。无 budget 的对照组最终救出所有请求——永久性地零丢失——而 budget 组则丢失了 594 个。它之所以能救出这些请求,是以给从未卷入故障的流量额外施加大约 3,800 次失败为代价的。重试 budget 不会缩小故障规模。它决定谁来为故障买单,而这必须用两列来表述,永远不能只用一列。
还有两个意外的结果。全量 jitter 不够:200 个客户端在同一时刻失败,在 60 的备用容量中塞入 78 的到达峰值,其中 23 个再次失败。固定退避和指数退避的峰值完全相同,都是 200——对同步群体施加确定性延迟会产生同步群体,只有去相关的 jitter(峰值 33)才能被吸收。
而关于熔断器误触的担忧在很大程度上是多余的。一个基于连续失败的熔断器在依赖健康且错误率为 2% 时触发零次;一个阈值为 3 的熔断器需要 10% 的背景错误率才会打开。它的真正代价在部分故障:熔断器只有一个全开或全关的杠杆,所以一个坏了 30% 的依赖会 100% 地丢弃其流量,114 个本可以返回的答案被丢弃——计数而非估算,因为真实结果为每个请求都画了,包括熔断器拒绝发送的那些请求。
173 个页内断言。九个级别,循序渐进:https://dev48.infy.uk/arcops.php