系统学习 学习路径总入口:先选领域,再建立模型,最后用故障演练验证

一次小规模扩容把 Kinesis 前端推过操作系统线程上限,分片映射构建失败,连锁影响 CloudWatch、Cognito、Lambda 等多项服务。 本文是公开报告的摘要与解读,事实以原始报告为准

本文是公开报告的摘要与解读,事实以原始报告为准。

事件概要

2020 年 11 月 25 日,AWS 在弗吉尼亚北部区域(US-EAST-1)发生 Kinesis 事件。官方报告《Summary of the Amazon Kinesis Event in the Northern Virginia (US-EAST-1) Region》记载,当天 2:44 至 3:47(PST),团队给 Kinesis 前端机群做了一次“相对较小”的扩容。报告强调,这次扩容是触发因素,不是根因。

扩容完成后,前端服务器开始越过操作系统配置的线程数上限。Kinesis 前端负责认证、限流和把请求路由到后端分片集群;前端之间按每个对端服务器创建一个操作系统线程,因此机群越大,每台现有服务器需要的线程越多。现有成员最多可能需要一小时才能获知新服务器,扩容因此让所有前端同时逼近上限。线程耗尽后,分片映射无法构建完成,服务器只剩不可用的映射,无法路由请求。

5:15,首批 Kinesis 读写告警触发。9:39,AWS 确认根因是操作系统线程限制,而不是内存压力。Kinesis 直到 22:23 才完全恢复正常,CloudWatch 指标和告警在 22:31 恢复,回填还持续数小时。报告没有给出正式总时长;按时间戳计算,从扩容开始到恢复约 19 小时 39 分,从首次告警到恢复约 17 小时 8 分。

时间线

以下时间均为 2020 年 11 月 25 日 PST。

时间 事件
2:44–3:47 对 Kinesis 前端机群做小规模扩容
5:15 首批 Kinesis 读写告警;CloudWatch 错误/延迟与 EventBridge 延迟出现
5:15 后 Kinesis 在调查期间移除新增容量,线程使用降回上限以下
6:15 Lambda 对 CloudWatch 指标的本地缓冲造成调用主机内存竞争
7:01 Cognito 错误率显著上升
7:51 Kinesis 将问题收敛到需要完整重启前端的候选原因
9:39 确认根因:前端超过操作系统线程限制
10:07 第一批重启后的前端开始接收流量
10:15 Cognito 开始部署降低 Kinesis 依赖的变更
10:36 Lambda 内存竞争缓解,调用错误回落
12:15 Cognito 错误显著减少
14:18 Cognito 恢复正常
16:15 ECS/EKS 的大部分问题解决
17:47 CloudWatch 开始恢复;依赖它的反应式 Auto Scaling 延迟持续到此时
22:23 Kinesis 完全恢复正常
22:31 CloudWatch 指标与告警完全恢复,回填继续数小时

影响面

直接受影响的是 Kinesis Data Streams 的写入和读取:请求可能失败,前端无法把请求路由到正确的后端分片。依赖 Kinesis 的服务随后出现连锁故障:

报告没有给出受影响客户数量。更值得警惕的是,事故同时削弱了可观测性和状态沟通:团队要在监控数据不完整、告警不可靠的情况下定位一个“没有崩溃、只是无法路由”的故障,这比服务器直接宕机更难判断。

根因

根因是前端机群的线程模型与容量规划不匹配,扩容只是把它推过临界点。每台前端为每个对端维护一个线程;新增前端会增加“所有现有前端”的对端数量,而不是只增加新服务器自己的线程。报告称现有服务器最多要一小时才能学习到新成员,这意味着扩容期间线程数会持续上升。

当线程数超过操作系统上限,分片映射的缓存构建无法完成。服务器没有崩溃,仍能接受连接,却没有可用的分片映射来路由请求,表现为持续的读写错误。AWS 曾把内存压力作为主要候选,最终确认问题在操作系统线程限制;在未充分测试前,AWS 也没有直接提高该限制。

反直觉之处在于:增加容量本应提升冗余,但在“每台服务器都要为每个对端创建线程”的网状模型里,扩容会把整个机群一起推向资源上限。前端机群有“数千台”服务器,分片映射又需要时间传播,因此一次看似平缓的变更可以造成全机群同时失效。

处置与恢复

AWS 先移除了刚增加的容量,使线程使用降回上限以下;但所有候选修复都需要修改并重启每台前端服务器,因此仍决定做完整的前端重启。重启速度被有意限制在“每小时几百台”,因为重启期间缓存重建会与请求处理竞争资源;返回过快会让服务器被判定为不健康,反而延长恢复。

10:07,第一批重启后的前端开始服务流量,之后逐步扩大。与此同时,Cognito 部署降低 Kinesis 依赖的变更,Lambda 的内存竞争在 10:36 缓解,CloudWatch 在 17:47 开始恢复。Kinesis 到 22:23 才完全正常,CloudWatch 指标和告警在 22:31 恢复,回填继续数小时。

后续改进包括:改用更大 CPU 和内存的服务器,减少服务器数量与每台线程数;为线程消耗增加细粒度告警;完成更高操作系统线程上限的测试;显著改善前端冷启动;把前端缓存迁到专用机群;把 CloudWatch 等大型消费者分到独立的前端分区;加速前端蜂窝化(cellularization);让 Cognito Web 服务器能在 Kinesis API 出错时不耗尽缓冲区;在 CloudWatch 本地保存三小时指标数据;并让支持工程师定期演练备用状态页工具。

教训

第一,容量变更要按“每节点资源随集群规模增长”的模型评估,而不只看总容量。线程、文件描述符、连接数和内存都可能随对端数量上升;扩容前应模拟新规模下每台服务器的资源曲线,并预留余量。

第二,操作系统限制必须被监控,而不是只在故障时才发现。线程数、打开文件数、socket 数和进程数都应设置接近上限的告警,并区分“缓慢逼近”和“瞬间越界”。

第三,静默降级比崩溃更难发现。服务器没有宕机,却无法路由请求;从首次告警到确认根因花了数小时。分片映射构建成功率、路由失败率和每前端健康状态应成为一等指标,而不是只看进程存活。

第四,监控不能是唯一的排障入口。CloudWatch 本身受 Kinesis 影响,状态页工具又依赖 Cognito;本地缓冲、独立遥测通道和备用状态发布工具应在平时演练。

第五,重启是一种稀缺的恢复资源。数千台服务器只能每小时恢复几百台,冷启动和缓存重建速度直接决定恢复时间;蜂窝化、分区和专用缓存机群的价值,在事故中体现得最清楚。

第六,下游客户端要能优雅降级。Cognito 的缓冲 bug 说明“依赖失效时继续攒请求”可能把上游故障变成自身资源耗尽;重试、缓冲和熔断都需要上限,并经过故障注入验证。

对本站读者的检查清单

恢复手册与同类复盘

相关命令

参考链接

读完自测

合上页面,先回答,再展开对照。

  1. 为什么一次小规模扩容会让所有 Kinesis 前端同时达到线程上限?
  2. 为什么服务器没有崩溃,却无法处理 Kinesis 请求?
  3. 恢复时为什么限制每小时重启的服务器数量?
答案
  1. 每台前端为每个对端创建一个线程,扩容增加了现有前端的对端数量;新增成员又要时间传播,线程数持续上升,最终所有前端一起越过上限。
  2. 线程耗尽后分片映射无法构建完成,服务器仍能接受连接,却没有映射把请求路由到后端分片集群。
  3. 重启时缓存重建会与请求处理争抢资源;返回过快会让服务器被判定为不健康,反而延长恢复。
微信小程序码:扫码打开命令速查小程序

小程序版

微信扫码,随时查命令