通过使用非阻塞、事件驱动的I/O操作,Node.js为构建和运行大规模网络应用及服务提供了很好的平台,也受到了广泛的欢迎。其主要特性表现为能够处理庞大的并且高吞吐量的并发连接,从而构建高性能、高扩展性的互联网应用。然而,Node.js单线程的的工作方式及有限的可管理内存使得其计算性能十分有限,限制了某些场景中的应用。近日,Jut开发团队的工程师Dave Galbraith分享了他们所遇到的Node.js的限制以及超越这些限制的方法。接下来,本文就详细分析其所遇到的问题及解决思路。

首先,Jut团队所研发的产品称为操作数据中心(operations data hub)。该产品是一个专门为研发团队所设计的流分析平台,主要用于收集日志以及事件等操作数据,然后根据整体做分析和关联。其核心功能就是要能够同时处理实时数据、历史数据、结构和非结构数据。具体产品架构如下图所示。

从上图可以看出,该产品的核心就是数据引擎,包括底层大数据后端和JPC(Juttle Processing Core)两部分。其中,整体系统需要依赖ElasticSearchCassandra等这些大数据后端分系统,进行历史数据的处理和存储以及一般数据的复原和管理;JPC采用了Node.js,完成同等对待历史数据和实时数据、利用日志数据/度量数据/事件数据提出问题以及发送实时数据到浏览器来利用d3进行可视化等。而且,JPC负责运行Juttle程序。当用户点击Juttle程序时,浏览器把程序发送到JPC,将其转换为JavaScript进行执行。Galbraith提出,Jut团队选择JPC中使用Node.js的原因包括采用JavaScript等高级编程语言可以快速完成建模和迭代过程;鉴于程序前端采用JavaScript实现,后端同样采用JavaScript可以方便前后端配合和沟通;Node.js拥有强大的开源社区,使得开发团队可以有效利用社区的力量等三个方面。JPC就利用了社区中103个NPM包,同时也共享了自己开发的7个包。

尽管Node.js拥有着非常好的特性,JPC的开发团队还是遇到了一些Node.js不能直接解决的问题:

  1. Node.js的应用程序都是单线程的。这就意味着即使计算机是多核或多处理器的,node.js的应用程序也只能利用其中一个,大大限制了系统性能。
  2. 随着堆栈变大,Node.js的垃圾收集器变得非常低效。随着堆栈使用空间超过1GB,垃圾收集的过程开始变得非常慢,会严重影响程序的性能。
  3. 因为以上的问题,Node.js限制了堆栈所能使用的空间为1.5GB。一旦超过该范围,系统就会出错。 
    为了保证Jut系统的高效性,Jut团队想出了一些解决方案。

首先,针对Node.js单线程引起的性能低下问题,Jut团队采用了尽量避免利用Node.js进行计算的方式。JPC会把Juttle流图切割为一些子图,然后在Jut平台的更深层再进行高效执行。以ElasticSearch为例,在未优化之前,数据请求的流程为:ElasticSearch把相关数据从磁盘中取出->编码为JSON->通过HTTP协议发送给JPC->JPC解码JSON文件,执行预想的计算。然而,ElasticSearch拥有一种聚合(Aggregation)功能,能够跨数据集执行计算。这样,一次大的请求就可以优化为一个ElasticSearch聚合,避免了中间多次JSON转换以及Node.js针对大规模数据进行计算的过程。而且,ElasticSearch和Cassandra都是采用Java编写,可以有效利用多核或多处理器资源,实现高效率并行计算。总之,通过尽量避免在Node.js中进行计算的方式,Jut团队有效提高了系统的性能。

其次,关于堆栈空间问题。每当用户让Node.js服务器向其他服务器发送请求时,用户都会提供一些相应的函数,来对未来返回的数据进行处理。Node.js就会把这些函数放到event loop中,等待数据返回,然后调用相应的函数进行处理。这种类似中断的处理方式,可以大大提高单线程Node.js的效率。然而,一旦event loop中其中一个函数计算的时间过长,系统就会出现问题。以用户向Node.js发送从其他服务器中请求若干行的数据,然后对这些数据进行数学计算为例。如果请求的数据超过了1.5GB堆栈大小的限制,计算过程就会占用Node.js很长一段时间,甚至无法完成。由于Node.js为单线程,在这段时间内,新的请求或者新返回的数据只能放置在event loop的待办列表中。这样,Node.js服务器的反应时间将会大大增加,影响其他请求的正常处理。

为了解决该问题,Jut在任何可能的地方实现了分页(paging)。这就意味着,系统将不会一次读取大量数据,而是将其划分为若干小的请求。在这些请求中间,系统还可以处理器新的请求。当然,多次请求都需要一定的通信代价的。经过Jut团队的摸索,20000个点是比较合适的规模——系统仍然能够在若干毫秒中执行完毕,而且一般的请求也不需要进行大量分割。

针对这些问题,Galbraith分享了一个具体的使用案例。作为Jut的忠实客户,NPM一直伴随着Jut从alpha版本一直走到了现在的beta版本。NPM一个具体的任务就是找到所有包中过去两周下载量最大的前十名,然后在网站中以表格形式的显示。Juttle程序可以利用非常简单的代码完成该任务:

read -last :2 weeks: | reduce count() by package | sort count -desc | head 10 | @table

但是,Jut第一次跑该程序的时候就遇到了问题。经过调试发现,问题的原因在于JPC优化了read和reduce操作,将其合并为一个ElasticSearch聚合操作。由于聚合操作本身并不支持分页,而NPM的包数要超过数百万个,ElasticSearch就返回了一个超过百万个数组的巨大响应结果,总大小在几百MB。收到该响应后,JPC就试图一次处理完毕,导致内存空间使用超过了1.5GB的限制。垃圾收集器开始不断尝试回收空间。结果,处理时间超过了JPC内置的监控服务认为出现异常的阈值——60s。监控服务直接重启JPC,导致了NPM的任务一直无法完成。

为了解决该问题,Jut团队采用了模仿ElasticSearch针对聚合进行分页的方法。针对返回的包含大量信息的结果,JPC将其切分为可以方便处理的小块,一个个处理。在一些公开库的帮助下,修改后的JavaScript代码如下:

var points = perform_elasticsearch_aggregtion();`
Promise.each(_.range(points.length / 20000), function processChunk(n) {
return Promise.try(function() {
process(points.splice(0, 20000));
}).delay(1);
});

其中Promise.each(param1,param2)负责针对第一个参数param1中的每一个元素调用第二个参数中的函数param2_.range(num)函数接收一个数字num,返回该数字大小的数组。以包含100万个点为例,上述程序需要调用processChunk()函数50(points.length/20000=1000000/20000=50)次。每次调用负责把20000个点拉出数组,然后调用process()函数进行处理。一旦处理完毕,垃圾收集器就可以对这20000个点占用的空间进行回收。Promise.try()以一个函数作为参数,返回能够控制其参数中函数执行的对象。该对象的.delay(1)方法表示在多次调用中间允许处理器1ms的暂停去处理其他请求。经过这样的修改,程序只花费了大概20s的时间就完成了之前NPM的任务。而且,在此期间,服务器还对其他请求进行了响应。

如何更好的利用Node.js的性能极限的更多相关文章

  1. 利用Node.js对某智能家居服务器重构

    原文摘自我的前端博客,欢迎大家来访问 http://www.hacke2.cn 之前负责过一个智能家居项目的开发,外包重庆一家公司的,我们主要开发服务器监控和集群版管理. 移动端和机顶盒的远程通信是用 ...

  2. 【Node.js】利用node.js搭建服务器并访问静态网页

    node.js是一门服务端的语言,下面讲讲如何利用node.js提供给我们的api来搭建服务器,并且访问静态网页 项目结构如下 ------------------------------------ ...

  3. 利用Node.js对某智能家居server重构

    原文摘自我的前端博客,欢迎大家来訪问 http://www.hacke2.cn 之前负责过一个智能家居项目的开发,外包重庆一家公司的.我们主要开发server监控和集群版管理. 移动端和机顶盒的远程通 ...

  4. 教你利用Node.js漏洞搞事情

    PentestingNode.js Application : Nodejs Application Security 原文地址:http://www.websecgeeks.com/2017/04/ ...

  5. 利用Node.js实现模拟Session验证的登陆

    1.身份验证和用户登陆 在一般的Web应用上,假设要实现用户登陆,最经常使用,也是最简单的方法就是使用Session,主要的思路是在Session中保留一些用户身份信息,然后每次在Session中取, ...

  6. 利用Node.js的Net模块实现一个命令行多人聊天室

    1.net模块基本API 要使用Node.js的net模块实现一个命令行聊天室,就必须先了解NET模块的API使用.NET模块API分为两大类:Server和Socket类.工厂方法. Server类 ...

  7. 利用node.js来实现长连接/聊天(通讯实例)

    首先: 需要在服务器端安装node.js,然后安装express,socket.io这两个模块,并配置好相关的环境变量等. 其次: 服务端代码如下: var app = require('expres ...

  8. 学信网改绑手机号码,但是忘记了老号码怎么办?利用node.js + puppeteer 跑脚本实现改绑手机号

    最近登录学信网发现自己学信网上绑定的手机号码不是目前自己使用的手机号码,于是想改绑手机号,但是发现不记得之前的手机号码了: 于是百度各种方法都无济于事:也不想重新注册账号,最后看见一篇文章通过Pyth ...

  9. 利用Node.js编写跨平台的spawn语句

    node const child = cp.spawn('npm', ['run', 'build']); 报错 events.js:182 throw er; // Unhandled 'error ...

随机推荐

  1. jmeter的dubbo插件

    调研是否可以把dubbo压测的一些公共配置变成变量.可以调控 Dubbo接口如何在Jmeter中测试,自研Dubbo Plugin for Apache JMeter 最新使用手册参考:https:/ ...

  2. Apache Beam WordCount编程实战及源代码解读

    概述:Apache Beam WordCount编程实战及源代码解读,并通过intellij IDEA和terminal两种方式调试执行WordCount程序,Apache Beam对大数据的批处理和 ...

  3. WIN7 安装其他的系统boot

    1,安装win7系统不赘述. 2,在安装完win7系统后,准备安装CentOS7.0 3,准备ISO文件和所需软件 1.CentOS官网下载DVD ISO文件​ 一般选择DVD ISO ​     2 ...

  4. 使用Spring开发和监控线程池服务

    第1步:添加maven 项目 第2步:添加依赖库 将Spring的依赖添加到Maven的pom.xml文件中. 1 2 3 4 5 6 7 8 9 10 11 <!-- Spring 3 dep ...

  5. Android Gradle基础实践

    1,gradle是全新的一种IDE编程环境,Android Studio集成了Gradle IDE 2,要下载gradle(比方gradle-2.10)解压.配置环境变量.比方G:\Program   ...

  6. python特殊函数

    __doc__  类(实例).__doc__ 类的描述信息 '''class des''' __module__ 类(实例).__module__ 表示当前操作的对象在那个模块 __class__   ...

  7. MFC对话框贴图基础上控件Stasic变成透明的

    对应WM_CTLCOLOR函数 加入下面代码: HBRUSH CMFCApplication2Dlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) ...

  8. vue slot slot-scope

    https://segmentfault.com/a/1190000012996217 插槽,也就是slot,是组件的一块HTML模板,这块模板显示不显示.以及怎样显示由父组件来决定. 实际上,一个s ...

  9. 小猪的Git使用总结

    小猪的Git使用总结 文件夹 小猪的Git使用总结 安装配置与文档 下载安装 文档教程相关 概念 Git的四个组成部分 文件的几个状态 Git与SVN版本号版本号控制存储差异 每次Commit时仓库中 ...

  10. 如何在 Linux 中找到你的 公网IP 地址

    每个网站都有一个独有的公开 IP 地址,可供任何人从任何地方访问. 互联网协议Internet Protocol(IP)不需要介绍 —— 我们每天都在使用它.即使你不直接使用它,当你在浏览器上输入 w ...