本文是公开报告的摘要与解读,事实以原始报告为准。
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 的服务随后出现连锁故障:
PutMetricData、PutLogEvents 和告警受影响,部分告警进入 INSUFFICIENT_DATA,并产生指标缺口。报告没有给出受影响客户数量。更值得警惕的是,事故同时削弱了可观测性和状态沟通:团队要在监控数据不完整、告警不可靠的情况下定位一个“没有崩溃、只是无法路由”的故障,这比服务器直接宕机更难判断。
根因是前端机群的线程模型与容量规划不匹配,扩容只是把它推过临界点。每台前端为每个对端维护一个线程;新增前端会增加“所有现有前端”的对端数量,而不是只增加新服务器自己的线程。报告称现有服务器最多要一小时才能学习到新成员,这意味着扩容期间线程数会持续上升。
当线程数超过操作系统上限,分片映射的缓存构建无法完成。服务器没有崩溃,仍能接受连接,却没有可用的分片映射来路由请求,表现为持续的读写错误。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 说明“依赖失效时继续攒请求”可能把上游故障变成自身资源耗尽;重试、缓冲和熔断都需要上限,并经过故障注入验证。
合上页面,先回答,再展开对照。
小程序版
微信扫码,随时查命令