![]()
4月6日周一,Bluesky约50%用户经历了断断续续8小时的服务中断。这是该平台史上最严重的一次事故。
系统工程师Jim在事后报告中写道:「这是我入职以来见过最糟糕的宕机,完全不可接受。」但真正的麻烦,其实从前一个周末就开始了。
周六的警报:被误判的"网络问题"
4月4日周六,监控系统触发告警。Jim第一反应是"传输层问题"——毕竟他们有完善的网络监控,看起来一切正常。
但日志里藏着线索。数据平面(AppView的后端服务)频繁报错:
「failed to set post cache item」——绑定地址已被占用
错误指向TCP端口耗尽。Bluesky的数据平面重度依赖Memcached(一种高性能缓存系统)来分担主数据库Scylla的压力。如果端口耗尽,缓存失效,请求直接砸向数据库,雪崩开始。
问题在于,当时的监控体系有个盲区。Jim后来承认:「我们假设每个请求都很轻量、很快完成。」但这个假设,在上周部署的一项新服务面前彻底失效。
新服务的"温柔一刀":每秒3次请求,每次2万个URI
上周上线的内部服务,看起来人畜无害。调用频率极低——每秒不到3次。但某些请求会一次性批量查询15,000到20,000条帖子URI(统一资源标识符)。
正常业务场景?1到50条URI per请求。
数据平面的代码用Go语言编写,每个RPC处理器都有并发限制(errgroup.SetLimit),防止资源被单个请求吃光。这是基础设施的标准防护。
唯独GetPostRecords这个端点没有。
代码里本该有一行:group.SetLimit(50)。它不存在。于是15,000个URI进来,系统瞬间启动15,000个goroutine(Go语言的轻量级线程),向Memcached狂建连接。
连接池上限是1,000。超额连接用完即抛,堆积在TCP的TIME_WAIT状态。65535个可用端口,耗尽只是时间问题。
周一的全面崩溃:端口枯竭的连锁反应
周六的"小波动"只是预演。周一流量高峰到来,问题被放大到平台级。
图表显示,用户请求量在8小时内出现多次断崖式下跌——绿色和黄色曲线不重要,那些深坑才是真实的用户掉线。约半数用户无法正常加载时间线、发帖或互动。
事后复盘的关键发现:一个漏写的参数,藏在整个系统最繁忙的端点之一。GetPostRecords负责批量获取帖子记录,是 feed 渲染的核心路径。它每天处理数十亿次查询,却唯独缺少并发保护。
Jim的描述很直白:「我们 slammed the daylights out of memcached」——把Memcached揍得够呛。
修复很简单:加上那行SetLimit。但定位问题花了整整两天,因为监控没准备好应对"单个请求内部爆炸"的场景。
一个参数背后的工程债务
Bluesky的技术栈选型相当激进:自研AT Protocol(认证传输协议)、联邦架构、Go语言全链路。这种架构下,单个服务的边界模糊,调用链复杂。
GetPostRecords的设计初衷是高效——批量查询减少往返。但"高效"和"安全"的边界,被一个新服务的异常用法击穿。
更值得玩味的是时间线。新服务上周部署,周六首次触发告警,周一全面爆发。中间有48小时窗口,但监控的假设让团队走了弯路。
Jim在报告末尾放出了招聘链接。这场事故成了技术品牌的另类广告:来帮我们修这种级别的坑。
分布式系统的恐怖故事往往如此——不是某个组件彻底坏掉,而是两个"正常工作"的东西以意想不到的方式共振。一个每秒3次的低频服务,和一个缺失的并发限制,联手制造了平台史上最长的中断。
如果Bluesky当时给这个端点设置了默认的并发上限,或者新服务的开发者注意到批量请求的潜在风险,这8小时本可以避免。但工程世界里没有如果,只有事后才能看清的依赖关系图。
Jim的报告没有给出具体的改进时间表,只提到"observability improvements are underway"。读者不妨想想:你负责的系统里,有没有哪个端点也缺了一行SetLimit?
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.