Netflix:Express.js性能调优心得

Netflix的软件工程师Yunong Xiao最近在公司的技术博客上写了一篇文章,分析了他所在的团队在将Netflix网站UI转移到Node.js上时遇到的延迟问题。在文章中他描述了找到问题根本原因所经历的复杂的工程过程,以及他们是怎样做出替换底层API框架的决定。

\\

最初,Yunnong的团队观察到API中某些端点的请求延迟会持续上升(每小时增加10ms),并且在高延迟的时段应用占用的CPU资源超过预期。他们最早的假设是请求处理器的一些问题(例如内存泄露)导致了延迟时间上升。为检验这一假设,他们配置了一个可控的环境,并在该环境下度量请求处理器的延迟和请求的总延迟。此外,他们将Node.js的堆大小增加到32GB。他们观察到,在实验过程中请求处理器的延迟和堆大小始终保持不变,但请求总延迟和CPU占用率持续增加。

\\

接下来,他们用CPU火焰图和Linux的Perf Events工具分析CPU使用情况。在仔细分析火焰图(如下图)后,工程师们发现图中很多方框指向Express.js的router.handle和router.handle.next函数。

\\

a2ce3ccb41dee6e9ebb02214db637b0b.png

\\

深入研究Express.js代码库后,工程师们发现:

\\
  • 所有端点的路由处理器(route handler)保存在一个全局数组里\\t
  • Express.js采用递归遍历所有路由处理器直至找到正确的处理器并调用\

正如Yunong所述:

\\
\

在这种情况下全局数组并不是理想的数据结构。不知道为什么Express.js不采用例如map这样的查询时间为常数的数据结构。更有甚者,数组是递归遍历的。这就解释了为什么会在火焰图里看到这么高的堆。有趣的是,Express.js甚至允许你给单个路由设置多个相同的路由处理器,比如:[a, b, c, c, c, c, d, e, f, g, h]。

\\

请求路由c时,程序会在第一次出现c处理器的位置终止(数组中下标为2的位置)。但是请求d时只会在数组下标6的位置停止,其实不必花费时间遍历a,b和多个c。

\
\\

为了更清楚地了解Express.js怎么样存储路由,工程师们创建了如下示例:

\\
\ var express = require('express');\ var app = express();\ app.get('/foo', function (req, res) {\    res.send('hi');\ }); \ // add a second foo route handler\ app.get('/foo', function (req, res) {\    res.send('hi2');\ });\ console.log('stack', app._router.stack);\ app.listen(3000);\
\\

以上代码产生如下结果:

\\
\ stack [ { keys: [], regexp: /^\\/?(?=/|$)/i, handle: [Function: query] },\  { keys: [],\    regexp: /^\\/?(?=/|$)/i,\    handle: [Function: expressInit] },\  { keys: [],\    regexp: /^\\/foo\\/?$/i,\    handle: [Function],\    route: { path: '/foo', stack: [Object], methods: [Object] } },\  { keys: [],\    regexp: /^\\/foo\\/?$/i, \    handle: [Function],\    route: { path: '/foo', stack: [Object], methods: [Object] } } ]\
\\

从运行结果可以推断出Express.js没有正确地处理重复的路由处理器。Yunong指出:“注意到对于路由/foo,有两个完全一样的路由处理器。更好的方式是,当路由处理器链中出现一个路由有多个路由处理器的情况时,Express.js抛出错误。”

\\

深入分析应用源码后,工程师们发现一个周期函数是出现重复路由器的罪魁祸首。这个函数每小时执行10次,而其目的是为了从外部刷新路由处理器。当团队修改了程序,使得函数不再增加重复的路由处理器后,高延迟和燃烧CPU的问题就随之消失了。

\\

经历这一插曲后,Yunong总结了团队得出几个结论:

\\
\

首先,在程序投入使用前我们要完全清楚它们的依赖关系。我们没有对Express.js的代码库做深入分析就对它做出了错误的假设,导致我们错误地使用了Express.js的API,这才是造成性能问题的根本原因。

\\

其次,在处理性能问题时,可观测性是极其重要的。火焰图帮助我们洞悉程序的CPU占用情况。如果不能抽样并用火焰图可视化Node.js堆栈使用情况,我无法想象该怎样解决我们的问题。

\\

为了进一步提升可观测性,我们正在迁移到Restify。Restify能提供更好的透视性、可视化以及对应用更好的控制。

\
\\

查看英文原文:Netflix Burned By Express.js

\\

感谢崔康对本文的审校。

\\

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ)或者腾讯微博(@InfoQ)关注我们,并与我们的编辑和其他读者朋友交流。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值