对端说「1 秒后再来」,Alertmanager 判定「永远不要重试」:这个错认了 6 年 9 个月

口径先钉住 :本篇每一条锚点都来自公开 issue / PR / commit,匿名可直接打开 ,逐字复核过: - issue prometheus/alertmanager#2111 ------ 报告原文,以及维护者承认判据错了 的那一句 - PR #2128 ------ 修复当天被回滚 ,理由里引的是 Slack 官方文档原话 - issue #3189 ------ 2022 年,第三方在生产上又撞了一次 - commit f6c84d37 (2026-08-27)、5f8365f3 (2026-09-17)------ 真正修好的两次提交 - 代码 notify/slack/slack.go ------ v0.20.0(2019 年当时)与 main(今天)两个版本我都拉下来逐行读过 本文没有引用学术文献,见文末引用节。

事情是这样的

2019 年 11 月 21 日上午 10:00,一个 issue 躺在 Alertmanager 的仓库里。

报告者的日志,逐字是这样:

swift 复制代码
err="cancelling notify retry for \"slack\" due to unrecoverable error:
unexpected status code 429: {\"retry_after\":1,\"ok\":false,\"error\":\"rate_limited\"}"

我第一次读到这句时盯的是三个东西:

  • 429 ------ 限速。
  • retry_after: 1 ------ 对端明确说了:1 秒之后再试。
  • unrecoverable error ------ 接收端判定:不可恢复。

前两个词说的是「等一秒」,第三个词说的是**「永远不要重试」**。

一个有上界、且对端把上界写清楚了 的等待,被判成了永久失败 。 这不是重试慢了,这是等待这个判定本身判反了方向。

先看维护者一开始怎么说

这条 issue 下面,维护者 roidelapluie 当天 10:06 回复:

Alertmanager has its internal retry mechanism. Using a value provided by upstream would have bad effects on high availability as you could get multiple times the same notification.

大意是:Alertmanager 自己有重试机制,如果照搬上游给的重试次数, 对高可用不利------可能会把同一条通知发好几次。

这个说法听着是站得住的。一个还没看代码的人,很容易就在这里接受它。

然后报告者回了一句:

If that is the case it might be good to ignore such errors in alertmanager_notifications_failed_total as they are triggering alerts for us, even though as you say AlertManager is able to handle this scenario internally.

我们在生产上,它在告警。

十一分钟之后,维护者改了口:

Mmh wait. No you are right, alertmanager will not retry 4XX errors.

429 就是 4xx。

这条我自己去开了原 issue 核对过,逐字就是上面那句,一个字没改。 我认为这是整个故事里最硬的一条证据------不是用户说的,不是事后统计的, 是维护者在公开 issue 里,亲口承认自己的判据错了。

判据长什么样:它就写在代码的注释里

我不满足于只看 issue。去把当时的代码拉了下来。 v0.20.0(2019-11 那个版本)的 notify/slack/slack.go,第 183--186 行,逐字:

kotlin 复制代码
// Only 5xx response codes are recoverable and 2xx codes are successful.
// https://api.slack.com/incoming-webhooks#handling_errors
// https://api.slack.com/changelog/2016-05-17-changes-to-errors-for-incoming-webhooks
return n.retrier.Check(resp.StatusCode, resp.Body)

规则就写在注释里:只有 5xx 可恢复。

于是整条链是这样的:

环节 / 发生了什么

  • Slack 返回 ------ 429,响应体 {"retry_after":1, ...}
  • 恢复性判定 ------ 429 是 4xx → 不在「只有 5xx 可恢复」的名单里
  • 结果 ------ 判为 unrecoverable → 取消重试
  • 响应体 ------ 被传进去了 (Check 的第二个参数),但判决只看第一个参数

最后那一行是我觉得最值得停一下的地方。

resp.Body 是传进判据函数的 。对端把「等多久」写得清清楚楚放在里面, 而那个数字没有参与判决 。判决的依据是状态码的一个分类规则, 而那条规则把「暂时性」和「永久性」的分界线划在了 4xx / 5xx 之间------ 但对 429 来说,这条线画错了。

修了,然后当天被回滚

维护者 10:24 回复「See #2112」,PR 很快提上来了。

然后事情拐了个弯。

2019-12-06 ,PR #2128 合并,标题逐字:

arduino 复制代码
Revert "slack: retry 429 errors (#2112)"

回滚的理由里,引了 Slack 官方文档的原话:

Continuing to send messages after exceeding a rate limit runs the risk of your app being permanently disabled.

在触发限速之后还继续发消息,有可能导致你的 app 被永久停用。

我第一次读到这里停了一下,因为这句话读起来像是站在对端立场上。 再读一遍不对------它恰恰是站在接收端的立场上,只是在替接收端担心。

于是当时的结论变成了:宁可不重试,也不要冒被封号的风险。

从原始报告(11-21)到回滚合并(12-06):14 天。

判据从「错得很确定」变成「明知有缺陷,但风险更高,暂时这样」。

三年后,另一个团队又撞上同一堵墙

2022 年 12 月 22 日,issue #3189:

Slack channel integration: vmalertmanager got unexpected status code 429

VictoriaMetrics 用的是他们自己的 vmalertmanager(v0.24.0,Alertmanager 的下游分支), 遇到的报错和 2019 年那条同一个形状:

css 复制代码
notify retry canceled due to unrecoverable error after 1 attempts: ...
429: {"retry_after":1,...}

同一个判据、同一个错误码、同一个「1 秒」。三年过去了,墙还在原地。

这一条对本篇很关键,我不打算只讲机制: 有团队在生产上为此付过代价,不是推演出来的。

6 年 9 个月之后

2026 年 8 月 27 日,commit f6c84d37:

ruby 复制代码
slack: retry on 429 and honor Retry-After header (#5048)

再看今天的 main,notify/slack/slack.go 第 51 行:

css 复制代码
retrier: &notify.Retrier{RetryCodes: []int{http.StatusTooManyRequests}},

429 被单独加进了可重试名单。 2026-09-17 的 5f8365f3 又把 Retry-After 的解析做成了通用函数。

从 2019-11-21 第一次报告,到 2026-08-27 真正修好:

2471 天。6 年 9 个月 6 天。

这到底错在哪一层

如果只把它记成「Alertmanager 有个 bug」,这篇就没有价值了------ 那种事到处都有,写出来只是让人消耗一次注意力。

它值得写,是因为错的位置很具体:

判据跑了,逻辑也对,它只是被问了一个它答不好的问题。

告警通知要回答的问题是「这条还要不要等 」。 而这条判据实际在回答的是「这个错误属于哪一类 」。 「4xx = 不可恢复」这条分类规则,对绝大多数 4xx 是对的(401 不该重试、403 不该重试), 唯独对 429 是错的 ,因为 429 不是「失败了」,是「现在不行,等会儿」。

分类规则本身没写错。是有一个状态被错误地放进了错误的类。

这里有个我特别想指出来的点:

一个状态从「等待」被误分类成「终态」,比一个判据算错了更危险。 算错了,数字会离谱,你会怀疑它。 而「这条永久失败了」是个听起来完全合理的结论------ 它不需要你怀疑,它自己就把重试关掉了。

回过头看,那句被传进函数又被丢在一边的 resp.Body, 其实是唯一说真话的东西。它喊了 retry_after: 1,没人听。

如果要我给一条可带走的

我没法给「怎么审查每一条判据」,那是空话。能给的只有一条具体的:

当一个判据把某件事判成「终止」,去找那个终止的证据在哪儿。

如果终止的理由是一句分类规则 (「4xx 都不可恢复」), 那就去核对这条规则覆盖的每一个成员是不是都适用。 分类规则最危险的地方不是它错了,是它对大部分成员都对。

而对端已经给了你具体数字的时候(retry_after: 1), 那个数字本身就是终止性的反证------终止的东西不会告诉你什么时候可以再来。

附:本文引用

锚点 / 出处(匿名可开) / 我做了什么

  • 报告原文、维护者承认、回滚引用 ------ github.com/prometheus/alertmanager/issues/2111 ------ 逐字读取,含 retry_after:1 报错串与「will not retry 4XX errors」原句
  • 修复被回滚 ------ github.com/prometheus/alertmanager/pull/2128 ------ 逐字读取标题与理由,引 Slack 官方文档
  • 第三方复现 ------ github.com/prometheus/alertmanager/issues/3189 ------ 逐字读取,VictoriaMetrics v0.24.0
  • 2019 年的判据 ------ raw.githubusercontent.com/prometheus/alertmanager/v0.20.0/notify/slack/slack.go ------ 拉下来逐行读,:183-186
  • 现在的判据 ------ raw.githubusercontent.com/prometheus/alertmanager/main/notify/slack/slack.go ------ 拉下来逐行读,:51
  • 两次修复 ------ commit f6c84d3759b0d4707df44c6462b3cf8e6d66a8bb、5f8365f3979f2d5586e9b90111a65c51fba7c92d ------ 经 API 逐条核对提交日期与标题
  • 时间跨度 2471 天 ------ 本机 datetime 实算 ------ 与「距今 2520 天 − 修复后 49 天」两项对账

本文没有引用学术文献

这篇没做文献综述,也没引用任何论文或书籍。 第二节查的是代码与公开 issue 的原文,不是文献。 写清楚是为了避免读者以为我做过文献工作。

取到 / 没取到的

来源 / 状态

  • issue #2111 / PR #2128 / issue #3189 原文 ------ ✅ 取到,逐字复核
  • v0.20.0 与 main 的 slack.go ------ ✅ 取到,逐行读过
  • 两次修复 commit ------ ✅ 取到,日期与标题核对一致
  • #2111 那次关闭后 Slack 官方文档是否改过措辞 ------ ❌ 没取到当时版本,我只看了现在的措辞
  • 中文侧是否已有人写过这个题 ------ ❌ 未能验证 。百度走验证码、cn.bing 301、DuckDuckGo 返空、掘金/CSDN 搜索页 JS 渲染提不到标题、知乎 403。因此我不声称它是中文首例------读者若见过,烦请指正
  • notify/util.go 里关于时钟基准的注释 ------ ⚠️ 查到但未亲自复核 ,已从正文剔除,不在承重链上

取不到的东西我不用记忆去补。 尤其是最后那条时钟注释------它看起来很有用,我查到了它的存在, 但因为没亲自逐字核过,宁可不用。一篇文章的承重锚点只要有一个是二手的, 整条链的可信度就要打折。

相关推荐
隐擎fox3 小时前
Linux eBPF 与 XDP 高性能网络出口架构:动态连接追踪、丢包诊断与住宅网关 QoS 调优实战
linux·网络·网络协议·tcp/ip·架构
天远数科3 小时前
零信任架构实战:基于天远柠檬查出险构建自动化二手车延保车况资质核验网关
运维·人工智能·架构·自动化
lexiangqicheng3 小时前
亮点-组织架构与权限体系
架构·状态模式
磐链科技3 小时前
DApp技术架构全面解析:从区块链底层、前后端开发到链上数据交互体系设计
架构·区块链
咖啡八杯4 小时前
异常体系设计:三类策略(i18n code、message、受检异常)
java·架构·代码规范
ZzT4 小时前
一周涨 24 万星的 REA,新手和逆向老手为什么评价相反
安全·ai编程
EasyCVR4 小时前
一个园区4种协议摄像头,怎么做到一站式接入?视频汇聚/视频融合/视频分析平台EasyCVR给出方案
安全·easycvr·国标gb28181
海宇服务4 小时前
零信任架构实战:基于海宇公安二要素认证即时版构建自动化跨境个人额度申报实名网关
运维·人工智能·架构·自动化
言之。4 小时前
Agent 与沙箱:架构与关系解析
架构