博客

告警只触发一次,而不是每台服务器各触发一次

我们的一条运维告警——针对已发布应用的“积分不足”——本应每天最多送达一次。但有一天下午,它却来了五次。原因是:API 的每个副本都在自己的内存中记录已经发送过哪些告警,因此两台服务器各发了一次;而每次部署都会清空这份记忆,让它们再次发送。同样的模式还导致一个后台任务重复执行。现在,两者都通过共享存储进行协调;并且,如果该存储不可用,两者都会优先选择安全的一侧失败。

症状

当某件事需要我们关注时,Mythex 会向团队发送告警:服务宕机、任务停滞,或者某个构建者已发布应用的积分余额即将耗尽。每条告警都有一个去重窗口,即同一条告警在该时间段内只会发送一次。对于“积分不足”告警,这个窗口是 24 小时。

同一条告警在一个下午里出现五次,说明去重窗口没有生效。更糟的是,这些告警成对出现,每对之间相隔约 11 分钟。

原因

我们的告警包在内存中保存去重信息:一张“告警键 → 上次发送时间”的表,存在于正在运行的进程里。这在只有一个进程、且该进程从不重启的情况下是可行的。但生产环境两者都不满足:

  • 两个 API 副本。 生产环境运行着两份 API。每份都有自己的表,因此各自发送了一次告警。这就是成对出现的原因。
  • 部署。 每次部署都会用全新的进程和空表替换这两个副本。部署之后,告警就会再次发出,仿佛从未发送过一样。

每对告警之间约 11 分钟的间隔,来自触发该告警的任务。这个任务在两个副本上都会运行,并且会获取一把锁,因此两个副本轮流抢到锁,轮到哪个副本,哪个副本就触发一次告警。

同一段代码里还有一个更隐蔽的问题。为了防止表无限增长,它在表中有 500 个键时就会自我清理,删除所有超过 15 分钟的条目,而不管这些条目的去重窗口有多长。在高负载下,原本 24 小时的去重,可能只持续了四分之一小时。

修复方案

每条告警共享一条记录

现在,API 在发送告警之前,会先在 Redis 中认领该告警的去重键。Redis 是一个共享的内存存储,两个副本都会与它通信,并且不会随着 API 的重启而丢失数据。认领操作是一条原子指令:“仅当该键不存在时才设置,并在窗口结束后过期”。哪个副本先认领成功,哪个副本就发送告警;另一个副本发现该键已被占用,便保持沉默。部署之后的新进程也能找到这个键,因为它仍然存在。

键和窗口的计算规则,现在与告警包共用同一份代码,因此两层对于“什么才算同一条告警”不会产生分歧。

失败时选择放行,而不是拦截

最重要的决定是:当 Redis 无法连接时该怎么办。我们选择照常发送告警。如果 Redis 宕机,或者在 1.5 秒内没有响应,告警仍会发出。重复的告警只是噪音,而被丢弃的告警则意味着有人没有收到通知。

最严重的 P0 告警,则完全不会因为去重而被拦下。

每个条目保留自己的窗口

在告警包自身的内存去重中,每个条目现在都会记录其窗口结束的时间,清理时只删除窗口已经结束的条目。一个 24 小时的键会保留满 24 小时,无论有多少其他告警涌入。

同样的问题,出现在别处

同样的模式也让一项后台工作重复执行了:一个对账任务,负责让我们对各工作区状态的记录与沙盒提供商保持一致。每次运行都会列出账户下的每个沙盒——生产环境中有数千个,大约耗时五秒——以及每个正在运行或已暂停的项目。两个副本每两分钟都会运行一次。

它的写入是幂等的,也就是说运行一次和运行两次结果相同,所以没有造成任何故障,只是白白多做了一倍的工作。现在,每个两分钟的时间窗口都会像我们的用量计量那样,在 Redis 中被认领,只有认领成功的副本才会执行这次对账。如果无法连接 Redis,对账仍然会执行:重复做同一件事是无害的,而不做则不行。

测试方法

测试模拟了真实的部署环境。它们把告警模块加载两次,模拟两个独立的“副本”,并让两者都指向同一个共享存储,然后各自触发相同的告警;接着再加载第三个全新的副本,模拟“部署之后”的情况,并再次触发该告警。最终只发送一条告警。其他测试则检查:不同构建者的告警依然会发出,P0 告警会从两个副本各自发送,以及当存储宕机或超过超时时间无响应时,告警仍会被发送。清理修复也有对应的测试:一个 24 小时的键,在 20 分钟后又涌入 600 条其他告警时,依然保持有效。

对于对账任务,两个副本同时执行,再加上同一窗口内的第三次触发,最终只产生一次列表查询。

我们的收获

  • 内存中的状态是按进程划分的。 任何需要在多台服务器之间保持一致的事情——“只发送一次”“只运行一次”——都必须存放在它们共享的地方。
  • 部署就是重启。 只能存活到下一次重启为止的状态,撑不过一个正常的工作日。
  • 明确故障时的处理方向。 对于告警来说,共享存储宕机应当意味着多发告警,而不是少发告警。
  • 寻找模式,而不仅仅是单个实例。 告警是那个最显眼的案例;而重复执行的对账任务,是同一个问题,只是在悄悄地浪费资源。

这些内容在 Mythex 内部通常看不到。想了解我们如何保持平台稳定,请参阅我们如何把部署期间的 502 错误降到零,以及缺失的 swap 文件。如果你刚接触运行多个服务副本的概念,我们的什么是服务器指南会介绍基础知识。

Mythex · Pricing · 用 AI 构建任何东西 · 网站和应用模板 · Mythex 对比 · AI 应用构建术语表 · 免费的 AI 应用构建工具 · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 繁體中文(香港) 繁體中文(台灣)