通过使用非阻塞、事件驱动的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的任务。而且,在此期间,服务器还对其他请求进行了响应。

最新文章

  1. the usage of map.put
  2. Java学习日记之 Java-其他类型
  3. Socket Programming in C#--Conclusion
  4. DOM解析和SAX解析的区别
  5. div中文字水平和垂直居中的css代码
  6. Java基础知识强化之IO流笔记82:NIO之 Pipe(管道)
  7. WingIde的快捷键
  8. 【Mysql】初学命令行指南
  9. Python 核心数据类型
  10. 006 列表的三种删除方法 remove,pop,del
  11. open vswitch常用操作
  12. Android Studio和eclipse混淆打包总结
  13. Minix3信号处理分析
  14. luogu1402 酒店之王
  15. Canal使用小结
  16. C++中数组作为形参进行传递(转)
  17. eclipse下classes文件夹无法发布到tomcat的问题--tomcat发布慢的问题
  18. ES5, ES6, ES2016, ES.Next: What's going on with JavaScript versioning?
  19. CodeChef题目选讲
  20. Linux命令执行的屏幕输出内容重定向到日志文件

热门文章

  1. 永久关闭WPS热点
  2. redis打开非英文存储显示问题
  3. wget jdk
  4. 通过java api提交自定义hadoop 作业
  5. 如何让mysql的自动递增的字段重新从1开始呢?(
  6. HTML5 Canvas 画钟表
  7. Exposing the Outlook Password Secrets
  8. Ubuntu16.04安装QQ
  9. v-if 条件渲染分组
  10. 仿新浪首页、主题、详情页,纯html静态页面