- 原文地址:Node.js War Stories: Debugging Issues in Production
- 原文做者:Gergely Nemeth
- 译文出自:掘金翻译计划
- 译者:mnikn
- 校对者:lsvih、Aladdin-ADD
在这篇文章,这篇文章讲述了 Netflix、RisingStack 和 nearForm 在生产环境中遇到 Node.js 错误的故事 - 所以你能够此为鉴,避免犯上一样的错误。同时你将会学到如何调试 Node.js 的错误。javascript
感谢来自 Netflix 的 Yunong Xiao、来自 Strongloop 的 NearForm 和来自 Shubhra Kar 的 Matteo Collina 对这篇文章的看法与帮助。php
过去4年里,咱们在 RisingStack 的生产环境中运行 Node 应用,积累了许多相关经验 - 感谢 Node.js 咨询、学习和开发 的业务支持。html
Netflix 和 nearForm 的 Node 开发团队都同样,咱们都有把调试过程记录下来的习惯,所以整个开发团队 (如今是全世界的开发团队) 均可以从咱们的错误中学习。 前端
让咱们慢慢阅读咱们的朋友 Yunong Xiao 在 Netflix 发生的故事。java
Netflix 的开发团队发现他们的应用的响应时间在逐渐变长 - 他们部分终端的延迟每小时增长 10 ms。 node
同时,CPU 使用率的上升也反映了问题的存在。react
不一样时间段请求的传输时间 - 图片来源: Netflixandroid
一开始,他们调查是不是 request handler 形成其响应时间变长。 ios
在隔离测试后,他们发现 request handler 的响应时间稳定在 1 ms 左右。git
因此问题并非这个,他们开始怀疑到底层,是否是栈出现了问题。
接下来 Yunong 和 Netflix 开发团队的尝试是这个 CPU 火焰图 和 Linux 性能事件。
火焰图反映了 Netflix 的响应速度正在变慢 - 图片来源: Netflix
你能够从火焰图中看到的东西是
通过深刻调查,开发团队发现 Express 的 router.handle
和 router.handle.next
有许多引用。
Express.js 的源代码揭示了一系列有趣的事情:
- 全部终端的 Route handlers 都储存在一个全局数组中。
- Express.js 递归地遍历并唤醒全部 handlers 直到它找到合适的 route handler。
在揭示谜题的解决方案前,咱们须要知道更多的细节:
Netflix 的底层代码包含了每 6 分钟运行的定时代码,从拓展资源中抓取新的路由配置信息,更新应用的 route handlers 从而响应改变的信息。
这些是经过删除并添加新的 handlers 来实现的。意外的是,同时它再一次添加了相同的静态 handler - 甚至是之前的 API route handlers。这形成的结果是,响应时间额外增长了 10 ms。
从这里阅读整个故事: 火焰图中的 Node.js。
你可能已经听过咱们的故事 拆分单体式应用的故事,咱们的 CTO Peter Marton 把 Trace (咱们的 Node.js 监控系统) 分离成多个微服务模块。
咱们如今讨论的错误是 Trace 开发时的响应速度变慢:
做为一个在 PaaS 运行的 早期 Trace 版本,它经过公共云来与咱们的其余服务通讯。
为了确保咱们的请求是完整的,咱们决定对全部请求进行签名。为了实现这个,咱们看了 Joyent 的 HTTP signing library。很棒的是,request 这一模块支持开箱即用的HTTP签名。
解决方案代价不只很大,并且会对咱们的响应速度形成很差的影响。
网络延迟增长了咱们的响应时间 - 图片来源: Trace
从图中可看到,所给定的终端响应速度为 180 ms,然而对于整体来讲,单独两个服务的网络延迟只是 100 ms。
一开始,咱们 用 Kubernetes 转移 PaaS provider。咱们但愿响应速度会快一点,这样内部网络就会平衡。
咱们的方法奏效了 - 终端的响应速度提升了。
然而,咱们想要更好的结果 - 大幅度下降 CPU 的使用率。下一步是分析 CPU 的使用状况,就像 Netflix 的人们作的同样:
从截图能够看出,crypto.sign
函数消耗的 CPU 时间最多,每次请求花费 10 ms。为了解决这个问题,你有两种选择:
React 如今很流行。开发者在前端和后端都会使用它,甚至他们更进一步用它来构建同构的 JavaScript 应用。
然而,渲染 React 页面会让 CPU 有挺大的负担,当绘制复杂的 React 内容时会受到 CPU 限制。
当你的 Node.js 正在进行绘制,它会堵塞事件循环,由于它的行为都是基于同步的。
结果就是,服务器可能会毫无反应 - 当请求堆积起来,会把全部的负担都堆在 CPU 上。
更糟的是即便请求端已经关闭,请求仍然会被处理 - 仍然会对 Node.js 应用形成负担,nearForm 对此有解释 Matteo Collina。
不只是 React,大多数字符串操做也会这样。 若是你在构建 JSON REST APIs,你应该花心思在 JSON.parse
和 JSON.stringify
。
Strongloop(如今是 Joyent) 的 Shubhra Kar 对此解释是,解析和转化成 JSON 字符串的等消耗巨大的操做也会消耗大量时间 (同时在这期间会堵塞事件循环)。
functionrequestHandler(req, res) {
const body = req.rawBody
let parsedBody
try {
parsedBody = JSON.parse(body)
}
catch(e) {
res.end(newError('Error parsing the body'))
}
res.end('Record successfully received')
}复制代码
简易的 request handler
这个例子展现了一个简易的 request handler,用来解析 body。对于内容很少的状况下,它运行的挺好 - 然而,若是 JSON 的大小要以兆来描述的话,可能会花费数秒的时间来执行 而不是在毫秒时间内执行。同理 JSON.stringify
也同样。
为了缓解这个问题,首先你要了解它们。为此,你能够用 Matteo 的 loopbench 模块,或者 Trace 的事件循环度量功能。
经过 loopbench
,若是请求没有被实现,你能够返回状态码 503 给负载平衡器。为了启用这项功能,你要使用选项 instance.overLimit
。这样 ELB 或者 NGINX 能够在不一样的后端中重试,这样请求有可能会被处理。
一旦你了解这个问题并理解它,你就能开始修正它 - 你能够经过平衡 Node.js 流或者改变正在使用的架构来进行修正。
我但愿 Netflix、RisingStack 和 nearForm 的例子会对你在生产环境中调试 Node.js 应用有帮助。
若是你想要了解更多,我建议看下最近这些文章,它们会加深你的 Node 知识:
若有任何疑问,请留下评论让咱们知道!
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、React、前端、后端、产品、设计 等领域,想要查看更多优质译文请持续关注 掘金翻译计划。