【AICon】 如何构建高效的 RAG 系统?RAG 技术在实际应用中遇到的挑战及应对策略?>>> 了解详情
写点什么

Netflix:Express.js 性能调优心得

  • 2014-12-10
  • 本文字数:1708 字

    阅读完需:约 6 分钟

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 函数。

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

  • 所有端点的路由处理器 (route handler) 保存在一个全局数组里
  • 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 )关注我们,并与我们的编辑和其他读者朋友交流。

2014-12-10 00:483524

评论

发布
暂无评论
发现更多内容

时隔一年多 jQuery 再度发布 3.6.1 新版本,你还在用JQ吗?

茶无味的一天

JavaScript 前端 框架 ​jQuery

模块一作业

中心化决议管理——云端分析

字节跳动终端技术

ios 研发效能 CocoaPods 制品库 云化服务

概述数据交换的构建策略

穿过生命散发芬芳

数据交换 9月月更

50道Java集合高频面试题,看完面试成功率99%

钟奕礼

Java 面试 java;

EMQ荣获工信部第五届“绽放杯”5G应用征集大赛智慧金融专题一等奖

EMQ映云科技

5G 物联网 IoT 数智化 9月月更

深入剖析nodejs中间件

coder2028

node.js

想从事运维岗位应该学习什么技能?谁能告诉一下?

行云管家

运维 网络运维 IT运维

idea 远程开发 client

黄敏

2022第三届云原生编程挑战赛--Serverless VSCode WebIDE使用体验

六月的雨在InfoQ

Serverless 边缘容器 9月月更 Serverless VSCode WebIDE 线上ide

2022届秋招Java岗高频面试题盘点,老司机也未必全会,真的太卷了

钟奕礼

Java 面试 java;

为什么大数据工程师比数据科学家的需求更大

雨果

数据工程师

组装式交付-云巧 知多少

六月的雨在InfoQ

9月月更 云巧 组装式交付 云巧资产 云巧工坊

Java开发5年,复习1个月成功上岸京东物流,面试和复习思路分享

钟奕礼

Java 面试 java;

玩转 Flowable 流程实例

江南一点雨

Java springboot workflow flowable

字节半天*3面/5天拿offer,全凭自身硬实力和这份Java面试笔记

钟奕礼

Java 面试 java;

一比一手写迷你版vue,彻底搞懂vue运行机制

hellocoder2029

JavaScript

总览 Java 容器--集合框架的体系结构

钟奕礼

Java 面试 java;

【Java深入学习】并发常见方法的注意事项

钟奕礼

Java 面试 java;

数据API开发如何快速上手:先了解什么是数据API生命周期管理

雨果

API 数据api

阿里云服务器ECS基本操作指南

六月的雨在InfoQ

阿里云 SSH xshell 云服务器ECS 9月月更

HTTP - TLS1.3 初次解读

懒时小窝

Java | this和super关键字【深入理解子类和父类的继承关系】

Fire_Shield

super this 9月月更

LED显示屏价格与品质哪个更重要

Dylan

LED LED显示屏 led显示屏厂家

IP地址和MAC地址都可以确定目标地址,为什么二者都在使用,舍弃一个是否可行?

阿柠xn

Mac IP 网络 协议族 9月月更

公司用的堡垒机叫什么?多少钱?

行云管家

网络安全 堡垒机 等级保护 过等保

架构实战训练营模块1作业--开启架构之旅

阿姆斯壮

架构实战营 #架构实战营

SAE 助力贵州酒店集团从容支撑贵州特产抢购

阿里巴巴中间件

阿里云 Serverless 云原生 SAE

java基础面试题

钟奕礼

编程 java;

手写vue-router核心原理

hellocoder2029

Vue

开发者有话说|成长之路

六月的雨在InfoQ

个人成长 开会 996 007 9月月更

Netflix:Express.js性能调优心得_JavaScript_João Paulo Marques_InfoQ精选文章