Node直出使用art-template填充带来的性能瓶颈优化
发布于 8 年前 作者 nevercoding 6053 次浏览 来自 分享

art-template介绍

art-template是TX出品的一个性能极佳的template engine,详见介绍戳它 大致原理是将template文件生成函数,后续填充变为str的操作。实际测试(amazon es2服务器)中发现,在template文件约45k的时候,首次填充模板,预编译约需要60~70ms时间,后续填充耗时在1ms左右。template文件为9k左右时,时间更短。

问题描述

目前,小组业务计划将接入层,全部替换为Node,部分重点页面采取直出的方法。经过测试后发现,直出的页面单机(虚拟机4核)接入请求量约为300~500 QPS(前端同事说法,作者并未实验证实)。

分析

业务架构如下:

上图中,Node这层主要有3个功能:

  1. 转发用户请求给Server
  2. 根据server返回的json数据填充art-template模板
  3. 将填充后的数据调用内置zlib模块进行压缩
  4. 将压缩后的数据返回给client.

其中, 1和4都属于IO型任务。对于长于此道的Node而言,不是性能瓶颈所在,典型实验如:hello-world echo实验,证实,单核机器可以轻松的达到QPS=5000+。 3属于Node内建核心模块,调用开源实现zlib。以35k的文件为例,单核QPS=1400+。如果加上模拟后台server处理的redis操作,单核QPS=1200+。平均延时约698ms,最大耗时1.68s。以上数据以wrk -c 1000 -t 4 -d 10s http://127.0.0.1:8000测试得出。 2渲染模板,从文章开头可以看到,其性能加载模板仅耗时1ms(除第一次外),按理来说,应该QPS更高。然而,实验发现,其单核QPS约为570左右。测试发现:

这个原因很明显了,瓶颈就在这里: art-template采用同步渲染模板的模式。这个数据也比较吻合: 渲染一次模板使用1~2ms,同步来算,1s可以处理1/1.5 = 600+。现网环境下,加上代码处理逻辑,可能下降至400~500QPS。数据和前端同学给出的一致。

这里要注意一点,Node主线程中不要执行过多耗CPU的操作,否则会导致Node一个Tick耗时过长,无法响应后续请求。

As long as you don’t do computationally heavy work on the main Javascript thread, then you are golden, because the slow I/O stuff is taken care of for you by the asynchronous non-blocking I/O libraries. If you do need to do computationally heavy work (CPU bound work), don’t do it on the main thread, instead fork a child_process and run it on a separate thread than the main Javascript event loop thread.


优化思路

模板渲染本身是CPU密集型操作。初看起来,确实无法利用Node的优势。毕竟Node是将CPU的iowait的时间利用起来,在CPU没有iowait时,Node也无能为力。

但是,并非无解

使用线程池

了解过Node实现原理的同学一定知道,Node本身是个伪异步的服务器。其异步操作严重依赖于libuv的线程池。类比下SPP,Node主js线程类似于proxy,线程池类似于worker,唯一的区别在于,Node的线程池多用于IO。那么,这里,是否可以将渲染模板的这部分工作交给线程池的线程去做,以此实现异步?我认为是可行的。随手google下,谁说Node不能处理CPU密集型任务? npool node-threads-a-gogo 由于时间关系,并没有去尝试,后续可以对比下效果。

C++ addon

Node是基于v8的,v8是C写的js engine。性能方面,C还是当之无愧的王者。一直以来,社区里的主流观点是,一旦涉及到CPU密集型任务,将性能损耗点重写为C++ Addon。这个话还是不错的。记得之前看到过,C、Node、Node with C++addon、java计算斐波拉奇数列的一个对比。使用C++Addon的Node比java性能还要高些。如果将渲染模板这里的代码使用C++实现,估计性能会提高30%以上。最重要的是,C++ Addon 可以multithread呀~~

Express有号称async的res.render()?

有空测试下。


一些声明:

假设后台server处理能力无限制,Node从转发用户请求开始,后台需要M秒时间才能返回响应,那么,无论何时算起,最晚的请求也会在M秒内返回。至于最晚的请求前可以有多少个请求,那就要看Node的接入效率了。QPS为1000时,latency=m,QPS为100时,latency还是m。

当然,后台server处理能力不可能无限制,但是系统的优化就是从一部分一部分开始的。只有接入端Node能够承受更多的请求量,后台server才能优化,扩容。从分层的角度而言,server层可以做到相比Node的处理能力无限,只不过此时就要相应的优化DB层了。

牵一发而动全身。

后台服务有两个指标比较重要:

  1. 请求延时latency
  2. QPS,每秒处理请求数

latency:受处理逻辑影响,逻辑越复杂,latency越高。 QPS:根据上述说明,假设请求相应需要m毫秒,那么QPS=N,N是(1000-m)毫秒内服务器接入的请求数

可以看到,QPS和latency无关(假设后台server远远未达高负载)。

好的系统,应该是QPS很高,latency很低。lantency要低,要减少逻辑处理时间;QPS要高,需要后台处理系统稳定,接入系统接入效率极高。


一些工具介绍


扩展阅读

Async template? what does it even mean?

在这次学习过程中,发现了居然有异步填充template的方案,也就是说可以把callback塞进渲染的中间过程中去,不过这个东西跟我要解决的问题不相关。有兴趣的同学可以调研下,也许可以通过改变模板渲染的方式去提高性能。 A Deep Dive into Asynchronous Templating

1 回复

用 alinode 烧个火焰图看看。

回到顶部