极速Node.js:来自LinkedIn的10个性能提升秘籍
【译注】:LinkedIn 最近从 Rails 转移到 Node.js 获得了巨大的成功,它砍掉了之前 90% 的服务器,并使性能提升了 20 倍。 这个消息令很多人把 Node.js 看成了葵花宝典一样的神功,可是练习神功也不是一朝一夕的事,光练招式没有内功也是不成的,更何况还得…那啥…总之不容易啊!那么除了 Node.js,LinkedIn 的性能提升还有什么秘密?LinkedIn 的软件工程师 Shravya Garlapati 写的这篇文章可以给我们一些启示。
以下是译文:
在之前的文章里,我们讨论了我们如何测试 LinkedIn 的移动服务集群,包括我们的 Node.js 移动端服务器。今天,我们想给你们说说我们是如何让这个服务器快速运行的。这里就是我们在 Node.js 上的十大性能提升秘籍:
1. 避免同步的代码
Node.js 从一开始的设计就是单线程的。要让一个线程处理很多并发请求,你就永远不要让这个线程在阻塞、同步或运行时间很长的操作中等待。Node.js 的一个超凡脱俗的特性就是它从上到下都是设计和实现为异步模式。这使它对事件驱动的应用是绝配。
不幸的是,在 Node.js 里进行同步/阻塞式的调用还是有可能的。例如,很多文件系统操作都提供了异步和同步版本,例如 writeFile 和 writeFileSync。即便你在自己的代码里避免了同步方法,却有可能漫不经心地引入了一个外部库,而在该库中使用了阻塞式调用。当你这么做了之 后,它对性能的影响是巨大的。
我们最初的日志实现就偶然性地引入了一个写入磁盘的同步调用。在我们进行性能测试之前,没人注意到它的存在。当我们在一台开发机上对比单个 Node.js 服务器性能的时候,这一个同步调用导致性能从每秒处理 1000 个请求下降到几十个!
2. 关闭 socket 池
Node.js 的 http 模块会自动使用 socket 池,缺省是每台主机限制为 5 个 socket。虽然这种 socket 复用方式可能对控制资源增长的速度有利,但如果你需要处理很多同时需要从同一台主机获取数据的并发请求,这个 socket 池会成为严重的性能瓶颈。在这种情况下,好的办法是增大 maxSockets 参数或者干脆把 socket 池 disable 掉。
3. 不要用 Node.js 处理静态资源
对于静态资源,例如 CSS 和图像,使用标准的 web 服务器而不是 Node.js 来处理。例如,LInkedIn mobile 使用 nginx。我们还利用内容分发网络(CDN),它能把静态资源复制到分布在全世界的很多服务器上。这样做有两大益处:①减少了 Node.js 服务器的负载,②CDN 能让静态内容从离用户比较近的服务器上传输过去,这样减少了延迟。
4. 在客户端渲染页面
让我们快速比较一下服务器端渲染和客户端渲染页面。如果我们让 Node.js 在服务器端渲染,我们会对所有请求发回一个类似于本文的 HTML 页面。
请注意,在这个页面上,除了用户名之外,所有内容都是静态的:也就是说,对于每个用户和每次刷新都是完全相同的。所以更有效率的方法是让 Node.js 只以 JSON 格式返回页面所需的动态数据。
页面的其他部分,也就是所有的静态 HTML 标记,可以放到一个 JavaScript 模板里(例如一个 underscore.js 模板)。
性能的受益是这么来的:根据秘籍3,静态 JavaScript 模板可以从你的 web 服务器(例如 nginx)或者 CDN 获取。而且,JavaScript 模板可以缓存在客户端或保存在本地存储,这样初始加载页面之后,唯一需要传给客户端的就是动态 JSON 数据,这是最高效率的做法。该方法极大地减少了 Node.js 服务器上占用的 CPU, IO 和负载。
5. 使用 gzip 压缩
大部分服务器和客户端都支持 gzip 压缩请求和响应的数据。一定要在对客户端发送响应和对远程服务器发送请求时利用它。
6. 并行
尽量让你所有的阻塞式操作(比如,对远程服务的请求,数据库调用,和文件系统访问)并发进行。这样会把总延迟减少为最慢的哪个阻塞式操作的时间,而不是每个操作顺序进行的延迟总和。为了保持回调函数和错误处理整洁清楚,我们使用了 Step 来进行流程控制。
7. 避免使用 session
LinkedIn mobile 使用 Express 框架来管理请求/响应循环。
缺省情况下,session 数据是保存在内存里的,这样会给服务器增加相当大的开销,特别是在用户数增长的情况下。你可以改用外部 session 存储,例如 MongoDB 或 Redis,可是这样每个请求又增加了获取 session 数据的远程调用的开销。在可能的情况下,最好的办法是根本就不在服务器端保存状态信息。通过去掉 Express 里类似于 “app.use (express.session ()); ” 的配置,实现无 session 后,你会看到更好的性能。(【译者注】原文缺了具体配置,这条是译者自己琢磨着加的)
8. 使用已编译的模块
尽可能使用已编译的模块而不是 JavaScript 模块。例如,当我们把 SHA 模块从一个用 JavaScript 写的版本转到一个包含在 Node.js 里的已编译版本,我们看到了一个巨大的性能飞跃。
9. 使用标准 V8 JavaScript 而不是客户端的库
大部分 JavaScript 库是做出来用在 web 浏览器上的。而在浏览器上 JavaScript 环境真是千差万别:例如,某个浏览器可能支持类似于 forEach, map/reduce 这样的功能,而其他浏览器却不支持。结果,客户端的库通常包含了很多低效率的代码来克服浏览器带来的差别。另一方面,在 Node.js 里,你能明确知道有哪些 JavaScript 函数,因为支撑 Node.js 的 V8 JavaScript 引擎是按第 5 版 ECMA-262 标准描述的 ECMAScript 标准实现的。通过直接使用标准的 V8 函数而不是客户端库里的东西,你可以看到相当大的性能改进。
10. 保持你的代码小规模、轻量级
在设备慢、延迟高的移动客户端环境下工作,你会学着保持代码的小规模和轻量级。把这个思路也带到你服务器端的代码中去。经常重新思考你的决定, 问自己一些问题,例如:“我们真的需要这个模块吗?”,“我们为啥要用这个框架?与其产生的开销相比它是否值得用?”,“我们能不能用更简单的方式来做这 件事?”。更小,更轻量的代码总是会更有效率,也会更快。