大家好,我是程序员鱼皮。
每个年底都是互联网事故的高峰期,我们的 刷题网站 - 面试鸭也未能幸免,在 12 月 29 号当天翻皮水了,宕机了 2 个小时左右……

事情是这样的,晚上 19 点左右,面试鸭用户群的鸭友反馈说:面试鸭访问不了!

这个时间,我正好在开会,完全没有看到这些消息,竟然也没有团队的任何一位同学来通知我?!
等我开完会回到公司,我发现不对劲了。怎么都晚上 20 点了,开发同学还围在一起,不知道在讨论什么?
果不其然出事了!此时面试鸭已经安睡 1 个多小时……

起初以为是服务器被攻击了,但是开发同学看了下监控,没有什么明显的异常:

当时面试鸭 Java 后端服务的内存占用看起来也是正常的:

不过看了下阿里云 ARMS 的 JVM 监控,发现了端倪。大概 19 : 30 左右,GC 的次数明显过多,并且这个时候线程池也满了。


请求线程满了,就意味着新的接口调用请求会被拒绝或者进入等待队列,包括题目获取接口、题库获取接口、题目搜索接口等等,所以导致很多用户无法正常看到面试鸭的数据。
那为什么前端网站也加载不出来呢?

因为面试鸭采用了服务端渲染技术,前端服务器会先请求后端接口拿到数据,拼接到网页模板中,再一起返回给浏览器用户。后端挂了,自然服务端渲染也会失败,导致前端也加载不出来。
为什么线程池会满呢?难道是应用层 DDoS 攻击?有攻击者在疯狂调用我们的某个接口?
我直接登录服务器源站,先找到目标 Java 进程的 PID:
ps -ef | grep java
然后生成线程 Dump 文件:
jstack <PID> > thread_dump.txt
接下来把这个文件下载到本地,用个代码编辑器打开,一眼就发现问题了!

很多线程的状态都是 BLOCKED 阻塞中,问题代码跟题目对象的 JSON 转换相关。
然后就查看对应的代码,发现里面用到了 JSONUtil 这个 Hutool 工具库的类进行了 JSON 转换:

然后我立刻就到网上搜索了。果不其然,是我们项目用的 Hutool 版本自带的 Bug!之前有同学也遇到过一模一样的问题。

Hutool 开发者也承认了这个 Bug:

然后我赶紧让团队同学升级 Hutool 依赖的版本,并重新上线。

这里有个细节,为了防止有 API 变更,我选择了一个相对跨度较小的安全版本,不然说不定还得调整代码,哪来得及啊!
这个版本上线后,服务终于恢复正常了。
但问题是,这行 JSON 序列化的代码在两周之前就上线了,为什么现在才出问题呢?
经过我们的开发同学复盘,真相大白了。
他在 29 号下午时,给数据库的 question 表里插入了大概 10 条左右的测试数据,这些数据的 faqList(需要 JSON 转换的字段)是有值的。而且这些题目都在热门题库内,导致前端在分页查询题库内题目、并且组装每个题目的数据时,都会调用这个 objToVo 方法,导致循环进行 JSON 反序列化 faqList,每个 faqList 内容又比较多,就会导致线程阻塞。
此外,还有一些接口也调用了这个 JSON 转换方法,而且这些接口调用的频率非常高,最后导致大量线程阻塞,页面卡死。
总结一下这次问题的关键原因:
1)查询列表时,不应该把页面不需要的 faqList 查询出来,在数据库查询时要直接过滤这个字段
2)使用 Hutool 5.8.8 版本的 JSONUtil.toList 有线程阻塞问题,在频繁执行反序列化时服务会卡死
为了验证这个推测,开发同学在本地进行了测试。
使用 Hutool 5.8.8 版本,当测试频率为100 QPS 时,持续 10s,有 45 个线程会进入阻塞状态:

升级到 Hutool 5.8.16 版本后,仍然用 100 QPS 测试,持续 10s,并没有线程阻塞:

OK,就是这样,吃了信任第三方工具的亏。
虽然问题解决了,但是通过这次事故,严重暴露了我们团队应急处理能力的不足。
出了事故的第一件事,难道不是抓紧通知团队里的其他同学么?因为我当时在外面开会没看手机,群里也没有任何通知,结果事故发生了一个多小时,我才知道。而且我竟然是整个团队最后一个知道这件事的?!

这是典型的事故处理意识不足啊!
去年发生类似事故的时候,我专门带所有技术同学一起整理了一个紧急事故复盘文档,出问题后的第一件事就是摇人。

真是应了那句话 “生于忧患,死于安乐”。太久没有遇到线上事故,竟然连最重要的事情都忘了?!
除此之外,这样一个典型的问题竟然要排查这么久?而且最后还是等我开完会回来之后,由我来排查出来的???
问了一下原因,大家在排查期间乱了阵脚,一直在回滚版本、观察、回滚版本、观察,却没想到导致阻塞的那行问题代码早在两周前就上线了,属于是完全凭借着主观推测、抱着试一试的心态来排查问题……
看来不仅是八股文背的少了,技术水平和线上事故处理能力也都有待提高啊,让大家见笑了。
通过这次的事故,我们团队内部也进行了深刻反思,会更注重线上服务的稳定性。也希望通过我们的这次笑话,提醒各位程序员朋友们:以后上线新功能时,比如加了新的字段,需要检查对已有代码有没有影响。另外使用 JSON 工具类时,要使用更成熟稳定的工具类,比如 Jackson 或 Gson。