NodeJS CPU 占用100%问题排查

上周接到同事反馈,客户服务器不时出现 502 错误,需要定时重启前端服务。上服务器上看了一下 cpu 占用100%,无法响应请求,由于是生产系统,备份了一下日志,重启服务。

疑似 ddos 攻击

看了一下日志,由于应用日志是 warning 级别,并没有太多有用信息,倒是 nginx 日志有大量的无效访问,并且这些访问大部分都是由 100.97. * . * 发起的。

无效访问

当时第一感觉是服务器受到了 ddos 攻击,导致 cpu 吃满。但是仔细看了一下,访问频率似乎也不是太高,大概 8次/s 左右。排查后确认 100.97. * . * 并不是内部网络的 ip 段,配置了一下 nginx 限制了 ip 段访问,同时将应用的日志级别改为了 debug 。

问题再次出现

第二天问题再次出现,看了一下应用日志,发现日志 halt 住了。

日志 halt

日志是在 auth 中间件停住的(web 容器是基于 koa2),看了一下代码,中间件的访问顺序 auth -> uploader -> contextPrepare -> renderPrepare -> next logcontextPreparerenderPrepare 并没有什么业务逻辑,显然是 auth 或者 uploader 哪个地方死循环了。找客户要了出问题账号的密码,试了一下登录和上传并没有出现问题。

关键信息缺失,因为应用本是在 Response 输出访问日志,而在 Request 阶段系统便 halt 无法输出日志,仍然不清楚客户到底是通过哪个 URL 访问的系统。只能暂时加上 Request log ,重启静待下次问题出现。

定位问题

最终的错误定位在

[Mon Jun 19 2017 20:51:36 GMT+0800 (CST)] INFO --> POST /api/user/files/upload?folderId=0 ::ffff:127.0.0.1 -

这个请求哪个页面发起的?项目中搜索了一下 /api/user/files/upload 发现了几处地方,一个个试过去,最终定位在了公司信息编辑页面的上传图片。为什么只是这个地方的上传会出问题,为了搞清楚原因,我在本地通过 node --inspect xxx 启动了服务,并且通过改 hosts 和 nginx 将线上的请求代理到了本地,一步步 debug 过来...

死循环

这个方法是第三方 package async-busboy 里面解析 request body 的方法,这个方法会将 form-item 中的 key 归并为一个对象。(例如:a[b]=1&a[c]=2,会解析成 { a: { b:1, c:2 } })。

obj == "3" && target == "3" 时方法陷入无限递归。堆栈一路看过去,最终定位到了 bankProfileList[[provinceId]]=30001&bankProfileList[[provinceId]]=30001。问题就出在 30001,代码并没判断30001 是否是一个对象,直接取了 keys,代码陷入了无限递归...

> Object.keys("30001")
[ '0', '1', '2', '3', '4' ]
> "30001".hasOwnProperty("0")
true  

bankProfileList[[provinceId]] 是后端 java spring 框架认的 request body 格式,nodejs 在处理上传时并不会用到这些值

解决方案

  • 上传时上传接口会忽略其他表单项,只处理 file 类型,所以在最好不要带上其他的 form-item,上传是用的 AjaxFileUpload 默认会带上当前表单的值。
$("input[type=file]").fileupload({
  ...
  formData:[], // 忽略其他表单项
})
  • 修改 async-busboy 忽略 form-field,只解析 form-file

总结

  • 前端还是缺乏相应的测试,文中的情况是在表单中存在相同的 form-item key 时出现的,如果测试覆盖到了的话,上线之前应该是可以发现问题的。
  • 代码其实还是有很多可以优化的地方,上传时其实只用到了 form-file, 其他表单项可以不用传。
  • 100.97.* .* 是阿里云 SLB(Service Load Balance) 健康检查请求。

王宇

Read more posts by this author.

中国浙江省杭州市

Subscribe to The Terminus Blog

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!