文档章节

Node.js 初体验

redraiment
 redraiment
发布于 2012/09/25 23:10
字数 2247
阅读 1030
收藏 15
点赞 0
评论 4

又到周五晚上自由时间,^_^。今天看了一下 Node.js。

服务器端 JS 情缘

在校期间我学会了JavaScript和Java,当时我就在考虑JS有没有类似JSP一样的服务器端程序,名字应该是JSSP(JavaScript Server Page),可以在 HTML 中嵌入 JS。Google了一圈发现IIS支持用JScript代替VBScript做ASP开发,另外SourceForge上真有个叫JSSP的项目,以及今天的主角Node.js。当时的Node.js刚起步,首页背景还是黑乎乎的(不晓得其他童鞋是否也有印象)。经过一圈比较,我最终选择使用Rhino——一个纯Java实现的JS引擎,它吸引我的地方是能直接调用Java类库。

Node.js

最近关注Node.js人变多了。在长期与一堆厚重的Java框架、类库为伍之后,我也想看看外面的世界。Node.js最为人所津津乐道的就是异步加回调机制以及良好的性能。我想知道它和我熟悉的Java有何不同。

Node.js 要解决的问题

在使用Java开发的过程里,经常会有与下面类似的代码:

// block A
// do something

// block B
// on Database
ResultSet rs = dbo.executeQuery("Query Statement");

while (rs.next()) {
    // block C
    // parse the result
}

// block D
// do something

代码块A先处理一些任务;代码块B发送查询语句到数据库,等待返回数据集;代码块C处理返回结果;代码块D继续做其他事情。执行时序图如下:

[[1.png]]

容易看出,在等待代码块B时,整个程序都暂停了,中间有一大段空闲时间没有处理任何任务。从依赖关系上说,代码块C必须在代码块B成功执行后才能执行;但代码块D对前面的B、C并没有依赖关系。因此,如果在等待期间先执行代码块D,直到代码块B执行完毕再触发代码块C。如下图所示:

[[2.png]]

假设每个代码块所需的执行时间是5秒,那第一种方案需要20秒,而第二种只需要15秒。Node.js要做的事情就是使用第二种方案取代第一种方案以获得性能的提升。

回调函数队列

情理之中意料之外,Node.js实现的方式是单进程且单线程。它内部维护着一个回调函数队列,遵循先到先处理的原则逐个执行。让我联想到[[http://zh.wikipedia.org/wiki/%E6%89%B9%E5%A4%84%E7%90%86%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F][批处理操作系统]] ,任务一个接一个地执行,没有抢占并享有所有系统资源。Node.js回调机制所做的事情就是把相应的代码块塞到队尾。

比如上一节的例子中的方法二,执行过程就变成:代码块A被塞到队列中;数据库查询语句注册了一个事件并绑定代码块C为回调函数;代码块D被塞到队尾;此时代码块B执行完成并触发事件,把代码块C塞到队尾。因此,依次执行的是A、D、C(注:B是数据库服务器上的查询操作,并不是Node.js中执行的代码),期间并无间歇。

考虑下面的代码,就遵循上述的代码模式:

  1. 代码块A:请求计数以及记录请求开始处理的时间(不是到达时间)。
  2. 代码块B:此处用setTimeout做了5秒延迟,模拟外部程序处理五秒钟。
  3. 代码块C:记录回调函数被调用的时间,睡5秒来模拟服务器运算,并记录结束时间。
  4. 代码块D:记录响应的时间(即用户收到回馈的时间)。
var http = require('http');
var count = 0;

http.createServer(function(request, response) {
    // block A
    count++;
    var id = count;

    var start = new Date();
    var reply;

    // block B
    setTimeout(function() {
        // block C
        var called = new Date();
        var end;

        do {
            end = new Date();
        } while (end.getTime() - called.getTime() < 5000);

        console.log(id + ' start @ ' + start);
        console.log(id + ' reply @ ' + reply);
        console.log(id + ' called @ ' + called);
        console.log(id + ' end @ ' + end);
    }, 5000);

    // block D
    response.writeHead(200, {'Content-Type': 'text/html'});
    response.end();
    reply = new Date();
}).listen(80);

在我的机器上执行结果如下:

λ sudo node main.js 
1 start @ Fri Sep 21 2012 19:12:35 GMT+0800 (CST)
1 reply @ Fri Sep 21 2012 19:12:35 GMT+0800 (CST)
1 called @ Fri Sep 21 2012 19:12:40 GMT+0800 (CST)
1 end @ Fri Sep 21 2012 19:12:45 GMT+0800 (CST)

和预期的一样:请求在19:12:35时开始处理,并且没有被阻塞,而是在同一时间就返回了;5秒后回调函数开始被处理;又过了5秒回调函数执行完毕,整个过程结束。

我们来数数上面这段代码总共有几个显眼的回调函数:

  1. 整段代码/文件是一个回调函数,在程序启动时被塞到队列中并立即执行了;
  2. createServer 中注册的回调函数,在收到用户请求后被触发(塞到队列,不一定马上执行);
  3. setTimeout 中注册的回调函数,在延迟5后才被塞到队列。

单线程的问题

Node.js采用单进程+单线程的其中一个原因是避免系统频繁开辟线程带来的开销。网络上说开启一个线程要使用2M的内存(有这么多?),我没去验证过具体数值,但至少是有一些开销的,当请求量大是的确会成为瓶颈。上节提到Node.js的处理任务的方式类似批处理操作系统,因此它在规避线程开销的同时也完全继承了批处理方式的缺陷——交互不友好。我指的交互是后面的请求会被回调函数队列中前面的任务阻塞住,从接受到回馈耗费很长的时间等待。

用下面代码分别在第0秒、第1秒、第7秒发送一个请求,并记录每个请求从发出到收到回馈的时间:

#!/bin/bash

date
time curl http://localhost
sleep 1
date
time curl http://localhost
sleep 6
date
time curl http://localhost
date

执行结果如下:

  1. 19:16:36 发送第一个请求,几乎马上收到回馈;
  2. 等待1秒后发送第二个请求,同样马上收到回馈;
  3. 继续等待6秒发送第三个请求,耗时8秒后猜收到回馈!
$ ./submit.sh 
2012年 09月 21日 星期五 19:16:36 CST

real    0m0.018s
user    0m0.004s
sys     0m0.008s
2012年 09月 21日 星期五 19:16:37 CST

real    0m0.015s
user    0m0.012s
sys     0m0.000s
2012年 09月 21日 星期五 19:16:43 CST

real    0m7.982s
user    0m0.000s
sys     0m0.004s
2012年 09月 21日 星期五 19:16:51 CST

相信第三次请求的用户会极其不满,来看看Node.js执行时的快照:

1 start @ Fri Sep 21 2012 19:16:36 GMT+0800 (CST)
1 reply @ Fri Sep 21 2012 19:16:36 GMT+0800 (CST)
1 called @ Fri Sep 21 2012 19:16:41 GMT+0800 (CST)
1 end @ Fri Sep 21 2012 19:16:46 GMT+0800 (CST)
2 start @ Fri Sep 21 2012 19:16:37 GMT+0800 (CST)
2 reply @ Fri Sep 21 2012 19:16:37 GMT+0800 (CST)
2 called @ Fri Sep 21 2012 19:16:46 GMT+0800 (CST)
2 end @ Fri Sep 21 2012 19:16:51 GMT+0800 (CST)
3 start @ Fri Sep 21 2012 19:16:51 GMT+0800 (CST)
3 reply @ Fri Sep 21 2012 19:16:51 GMT+0800 (CST)
3 called @ Fri Sep 21 2012 19:16:56 GMT+0800 (CST)
3 end @ Fri Sep 21 2012 19:17:01 GMT+0800 (CST)

下表中A1表示第一次请求中的代码块A,其他以此类推。假定代码块A、B、D都是瞬间完成,只有C耗时5秒。从表中可知,第三次请求是在第15钟才开始被处理。根据测试脚本的输出可知,第三次请求其实在第7秒就已经发出了,但由于是那时队列中还有C1、C2在处理,因此等待了8秒钟!假设队列的平均长度是100,那每个请求平均的等待时间就是 (100 / 4 - 1) * (A+B+C+D),即要等前面24个请求处理完。这还仅仅是请求得到回馈的时间,该请求对应回调函数被执行的时间还要更久。

当前时间 当前队列
0 A1 B1 D1
1 A2 B2 D1
5 C1
6 C1 C2
7 C1 C2 A3 B3 D3
10 C2 A3 B3 D3
15 A3 B3 D3
20 C3
25

适用场景

通过上面的研究,我觉得Node.js并不适合需要与用户实时交互的系统;它适合集中处理用户发来的大规模“指令”,即不需要及时看到结果的请求。比如微博系统,用户发表一条微博,可能需要在服务器上排队1分钟才能最终保存到数据库。在这一分钟里,用户更多地是看看别人发表的微博,并不十分迫切地想看到自己那条微博。如果希望有更好的体验,其实可以用DOM直接把用户发表的微博先更新到当前页面,同时使用Ajax异步请求保存这条数据。

© 著作权归作者所有

共有 人打赏支持
redraiment

redraiment

粉丝 128
博文 21
码字总数 25032
作品 6
杭州
架构师
加载中

评论(4)

redraiment
redraiment

引用来自“西夏一品堂”的评论

请问撸主,node.js有哪些应用场景,你一般用它干啥
窃以为,Node.js适应于并发量大的,但不需要实时响应的“批量”式任务。例如给图片点赞,这类任务频繁、规模小……用Node.js解决比其他方式会更节省资源。
西夏一品堂
西夏一品堂
请问撸主,node.js有哪些应用场景,你一般用它干啥
redraiment
redraiment

引用来自“dylan_he”的评论

最后一段话,请问那和javascript的ajax有什么区别和优势?

Ajax的A也是异步,只不过它应用于客户端,特指客户端和服务器端的异步通信。而Node.js是服务器端的JS,它的异步可以是任何I/O,包括数据库、文件等
武松找零食
武松找零食
最后一段话,请问那和javascript的ajax有什么区别和优势?
Node.js 应用性能优化的五个技巧

在这个由软件定义的世界里,企业往往是通过 Web 应用和移动应用程序来提供他们大部分的服务。所以对企业来说,一个非常重要的任务就是要确保用户拥有出色的使用体验。Node.js 正迅速成为时下...

OneAPM蓝海讯通
2015/07/09
39
0
PayPal从Java切换到JavaScript

已经决定使用JavaScript开发Web应用程序,从浏览器一直到后端服务器,并放弃了使用JSP/Java编写的遗留代码。 PayPal技术总监Jeff Harrell在两篇博文中(解放我的UI第一部分:Dust JavaScript...

greki
2014/05/04
0
0
Node.js---03、node.js 的事件(仅四行关键代码)以及应用实例

一、自定义事件的基本步骤 1、加载 events 事件对象(const EventEmitter = require('events')); 2、创建事件存储容器(let emitter = new EventEmitter( )); 3、注册绑定事件(emitter...

秋季长青
2017/11/07
0
0
Node.js---01、初识NodeJS和Node.js的HTTP服务器搭建

一、前言 我们先从以下几个方面在大体上认识一下Node.js: Node.js 是什么?==> 运行环境 是一个 可以运行 并加载 ES语法的 脚本运行环境 JS(ES5)、ES6、Common.js 语法 …… Node.js 可以加...

秋季长青
2017/11/06
0
0
io.js入门(一)—— 初识io.js

io.js可以说是彻底从NodeJS里分离出来的一条分支,其事情始末可以查看这篇报道,此处便也不赘言。既然是分支,io.js便也基本兼容NodeJS的各种API,连执行指令也依旧兼容Node的 node XXX (新...

开源oschina
2015/01/14
0
0
《Node.js开发指南》书评汇总

刚查了下库存,发现订阅《Node.js开发指南》的读者大增,这是为什么呢?看了下近期本书在豆瓣的评论,口碑很好,现将豆瓣的书评汇总如下: ----------------------------------------------...

生气的散人
2012/10/15
0
0
Node.js之HTTP/2服务器推送

译者按: 盆友们,是时候拥抱新一代HTTP协议了! 译者:Fundebug 原文:HTTP/2 Server Push with Node.js Node.js 8.4.0已经开始支持HTTP/2,执行node命令时,加上选项就可以使用了。 在这篇...

Fundebug
04/23
0
0
深入浅出Node.js(四):Node.js的事件机制

Node.js的事件机制 Node.js在其Github代码仓库(https://github.com/joyent/node)上有着一句短短的介绍:Evented I/O for V8 JavaScript。这句近似广告语的句子却道尽了Node.js自身的特色所...

leeldy
2012/10/25
0
0
基于Nodejs开发的web即时聊天工具

由于公司需要开发web即时聊天工具,开始时我们主要的实施方法是用jquery的ajax定时(10秒)轮询向服务器请求,由于是轮询请求,对服务器的压力比较大。我们网站上线的时间不长,访问量不是很...

Jackwoo
2013/03/12
0
5
為什麼 Node.js 不適合大型和商業專案?

JavaScript 和 Node.js 一直都是這幾年的話題,無論是前端還是後端,到處都可見 JavaScript,就好像爬滿了你全身上下,他們不斷地對你說道「嘿!老兄!快來用我吧!」。 為什麼 Node.js 會這...

临江仙卜算子
05/25
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

对基于深度神经网络的Auto Encoder用于异常检测的一些思考

一、前言 现实中,大部分数据都是无标签的,人和动物多数情况下都是通过无监督学习获取概念,故而无监督学习拥有广阔的业务场景。举几个场景:网络流量是正常流量还是攻击流量、视频中的人的...

冷血狂魔
18分钟前
0
0
并发设计之A系统调用B系统

A-->B A在发送请求之前,用乐观锁,减少对B的重复调用,这样一定程度上是幂等性。 比如A系统支付功能,要调用B系统进行支付操作,但是前端对"支付"按钮不进行控制,即用户会不断多次点击支付...

汉斯-冯-拉特
38分钟前
0
0
HTTP协议通信原理

了解HTTP HTTP(HyperText Transfer Protocol)是一套计算机通过网络进行通信的规则。计算机专家设计出HTTP,使HTTP客户(如Web浏览器)能够从HTTP服务器(Web服务器)请求信息和服务。 HTTP使用...

寰宇01
今天
0
0
【Java动态性】之反射机制

一、Java反射机制简介

谢余峰
今天
1
0
Centos 6.X 部署环境搭建

1.Linux学习笔记CentOS 6.5(一)--CentOS 6.5安装过程

IT追寻者
今天
0
0
博客即同步至腾讯云+社区声明

我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=8vy9bsmadbko...

xiaoge2016
今天
1
0
大数据教程(3.1):Linux系统搭建网络YUM源服务器

博主在前面的2.5章节讲述了linux系统本地YUM服务器的搭建和httpd轻量级静态网站服务器的安装,本节博主将为大家分享内网环境中搭建自己的网络YUM服务器的全过程。如果大家对本地YUM服务器还不...

em_aaron
今天
1
0
蚂蚁技术专家:一篇文章带你学习分布式事务

小蚂蚁说: 分布式事务是企业集成中的一个技术难点,也是每一个分布式系统架构中都会涉及到的一个东西,特别是在这几年越来越火的微服务架构中,几乎可以说是无法避免,本文就围绕分布式事务...

Java大蜗牛
今天
1
0
新的Steam应用将拓展服务项目

导读 未来几周,Steam将推出两个免费的应用程序Steam Link和Steam Video。这两个应用程序都旨在拓展Steam平台的业务和便利性。 即将开放的Steam Link应用程序最先提供了Android测试版,它将允...

问题终结者
今天
0
0
golang 第三方包的使用总结

golang 第三方包的安装的方法: 1. go get 安装 $ go get github.com/gin-gonic/gin 注意:执行go get 命令需要先安装git命令,并配置git全局变量。 2. 源码包安装 由于国内网络问题,很多时...

科陆李明
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部