2019独角兽企业重金招聘Python工程师标准>>>

科普文一则,说说我对NodeJS(一种服务端JavaScript实现)的一些认识,以及我为什么会向后端工程师推荐NodeJS.

"Node.js 是服务器端的 JavaScript 运行环境,它具有无阻塞(non-blocking)和事件驱动(event-driven)等的特色,Node.js 采用 V8 引擎,同样,Node.js 实现了类似 Apache 和 nginx 的web服务,让你可以通过它来搭建基于 JavaScript 的 Web App。"

上周末参与了CNodeJS社区的第一次北京聚会,现场气氛非常的好.而作为一名前端开发,我在后面的讨论环节讲了下我对NodeJS的看法,主要回答的问题是"我为什么会向后端工程师推荐NodeJS".这其实是去年年底大团队技术总结的话题之一,包含在我之前发过的PPT:团队年终技术Review中.因为之前没有准备,当天仓促上阵,也不知道说清楚了没,不如就在这里再详细展开记录下.

我想不仅仅是NodeJS,当我们要引入任何一种新技术前都必须要搞清楚几个问题: 
        1.我们遇到了什么问题? 
        2.这项新技术解决什么问题,是否契合我们遇到的问题? 
        3.我们遇到问题的多种解决方案中,当前这项新技术的优势体现在哪儿? 
        4.使用新技术,带来哪些新问题,严重么,我们能否解决掉?

我们的问题:Server端阻塞 
        NodeJS被设计用来解决服务端阻塞问题.通过一段简单的代码解释何为阻塞:

//根据ID,在数据库中Persons表中查出Name
var name = db.query("select name from persons where id=1");
//进程等待数据查询完毕,然后使用查询结果.
output("name")

这段代码的问题是在上面两个语句之间,在整个数据查询的过程中,当前程序进程往往只是在等待结果的返回.这就造成了进程的阻塞.对于高并发,I/O密集行的网络应用中,一方面进程很长时间处于等待状态,一方面为了应付新的请求不断的增加新的进程.这样的浪费会导致系统支持QPS远远小于后端数据服务能够支撑的QPS,成为系统的瓶颈.而且这样的系统也特别容易被慢链接攻击(客户端故意不接收或减缓接收数据,加长进程等待时间). 
如何解决阻塞问题 
        解决这个问题的办法是,建立一种事件机制,发起查询请求之后,立即将进程交出,当数据返回后触发事件,再继续处理数据:

//定义如何后续数据处理函数
function onDataLoad(name){output("name");
}
//发起数据请求,同时指定数据返回后的回调函数
db.query("select name from persons where id=1",onDataLoad);

我们看到按照这个思路解决阻塞问题,首先我们要提供一套高效的异步事件调度机制.而主要用于处理浏览器端的各种交互事件的JavaScript.相对于其他语言,至少有两个关键点特别适合完成这个任务. 
为什么JS适合解决阻塞问题 
        首先JavaScript是一种函数式编程语言,函数编程语言最重要的数学基础是λ演算(lambda calculus) -- 即函数可以接受函数当作输入(参数)和输出(返回值). 
        函数可以作为其他函数的参数输入的这个特性,使得为事件指定回调函数变得很容易.特别是JavaScript还支持匿名函数.通过匿名函数的辅助,之前的代码可以进行简写如下.

db.query("select name from persons where id=1",function(name){output(name);
});

还有一个关键问题是,异步回调的运行上下文保持(称状态保持),我看一段代码来说明何为状态保持.

//传统同步写法:将查询和结果打印抽象为一个方法
function main(){var id = "1";var name = db.query("select name from persons where id=" + id);output("person id:" + id + ", name:" + name);
}
main();

前面的写法在传统的阻塞是编程中非常常见,但接下来进行异步改写时会遇到一些困扰.

//异步写法:
function main(){var id = "1";db.query("select name from persons where id=" + id,function(name){output("person id:" + id + ", name:" + name);//n秒后数据返回后执行回调});
}
main();

细心的同学可以注意到,当等待了n秒数据查询结果返回后执行回调时.回调函数中却仍然使用了main函数的局部变量"id",而"id"已经在n秒前走出了其作用域,这是为什么呢?熟悉JavaScript的同学会淡然告诉您:"这是闭包(closures)~". 
        其实在复杂的应用中,我们一定会遇到这类场景.即在函数运行时需要访问函数定义时的上下文数据(注意:一定要区分函数定义时和函数运行时这样的字眼和其代表的意义,不然很快就会糊涂).而在异步编程中,函数的定义和运行又分处不同的时间段,那么保持上下文的问题变得更加突出了. 
        在这个例子中,db.query作为一个公共的数据库查询方法,把"id"这个业务数据传入给db.query,交由其保存是不太合适的.但聪明的同学们可以抽象一下,让db.query再支持一个需要保持状态的数据对象传入,当数据查询完毕后可以把这些状态数据原封不动的回传.如下:

function main(){var id = "1";var currentState = new Object();currentState.person_id = id;db.query("select name from persons where id=" + id, function(name,state){output("person id:" + state.person_id + ", name:" + name);},currentState);//注意currentState是db.query的第三个参数
}
main();

记住这种重要的思路,我们再看看是否还能进一步的抽象?可以的,不过接下的动作之前,我们先要了解在JavaScript中一个函数也是一个对象.一个函数实例fn除了具备可函数体的定义之外,仍然可以在这个函数对象实例之上扩展属性,如fn.a=1;受到这个启发我们尝试把需要保持的状态直接绑定到函数实例上.

function main(){var id = "1";var currentState = new Object();currentState.person_id = id;function onDataLoad(name){output("person id:" + onDataLoad.state.person_id + ", name:" + name);}onDataLoad.state = currentState ;//为函数指定state属性,用于保持状态db.query("select name from persons where id=" + id, onDataLoad);
}

我们做了什么?生成了currentState对象,然后在函数onDataLoad定义时,将currentState绑定给onDataLoad这个函数实例.那么在onDataLoad运行时,就可以拿到定义时的state对象了.而闭包就是内置了这个过程而已.

在每个函数运行时,都有一个运行时对象称为Execution context,它包含如下variable object(VO,变量对象),scope chain(作用域链)和thisValue三部分.详见ECMA-262 JavaScript. The Core 
         
        其中变量对象VO,包含了所有局部变量的引用.对于main函数,局部变量"id"存储在VO.id内.看起来用VO来代替我们的currentSate最合适了.但main函数还可能嵌套在其他函数之内,所以我们需要ScopeChain,它是一个包含当前运行函数VO和其所有父函数scope的数组. 
        所以在这个例子中,在onDataLoad函数定义时,就为默认为其绑定了一个[[scope]]属性指向其父函数的ExecutionContext的ScopeChain.而当函数onDataLoad执行时,就可以通过[[scope]]属性来访问父函数的VO对象来找到id,如果父函数的VO中没有id这个属性,就再继续向上查找其祖先的VO对象,直到找到id这个属性或到达最外层返回undefined.也正是因为这个引用,造成VO的引用计数不为0,在走出作用域时,才不会被垃圾回收. 
        很多人觉得闭包很难理解,其实我们只要能明确需要区分函数定义和函数运行这两个时机,记住闭包让函数在运行时能够访问到函数定义时的所处作用域内的所有变量.或者说函数定义时能访问到什么变量,那么在函数运行时通过相同的变量名一样能访问到.

关于状态保持是本文的重点,在我看到的多数NodeJS的介绍文章,并没有详解这里,我们只是知道了要解决阻塞问题,但是JavaScript解决阻塞问题的优势在哪里,作为一个前端开发,我想有必要详细解释一下. 
        
        其实说到状态保持还有一个类似的场景,比如用户从A页面提交表单到B页面,如果提交数据校验不通过,则需要返回A页面,同时保持用户在A页面填写的内容并提示用户修改不对的地方.从提交到返回显示这也是一个包含网络交互的异步过程.传统网页,用户的状态通过请求传递到服务端,交由后端状态保持(类似交给db.query的currentSate).而使用Ajax的网页,因为并未离开原页面,那么服务端只要负责校验用户提交的数据是否正确即可,发送错误,返回错误处相关信息即可,这就是所谓前端状态保持.可以看到这个场景里边服务端做的事情变少了,变纯粹了.正如我们的例子中db.query不再存储转发第三个state参数,变得更轻量. 
        
        我们看到通过JavaScript函数式语言特性,匿名函数支持和闭包很漂亮的解决了同步编程到异步编程转化过程中遇到的一系列最重要的问题.但JavaScript是否就是最好的?这就要回答我们引用新技术时需要考虑的最后一个问题了 
使用NodeJS是否带来额外的困扰,如何解决 
        性能真的是最好么?不用比较我们也可以得到结论NodeJS,做无阻塞编程性能较难做到极致.何为极致,处理一个请求需要占用多少内存,多少cpu资源,多少带宽,如果有浪费就不是极致.阻塞式编程浪费了大量进程资源只是在等待,导致大量内存和cpu的浪费.NodeJs好很多,但也正是因为一些闭包等JS内建机制也会导致资源的浪费,看下面的代码

function main(){var id = "1";var str = "..."; //这里存储一个2M的字符串db.query("select name from persons where id=" + id,function(name){output("person id:" + id + ", name:" + name);//n秒后数据返回后执行回调});
}
main();

直到数据查询完成,变量str所使用的2M内存不会被释放,而str保持下去可能并没有意义.前面已经解释过闭包的原理,闭包并没有智能到只包起来今后可能被访问到的对象.即使不了解闭包的原理,也可以通过一段简单脚本验证这点:

function main(){var id = "1";var str = "..."; //这里存储一个2M的字符串window.setTimeout(function(){debugger;    //我们在这里设置断点},10000)
}
main();

我们在回调函数当中只设置一个断点,并不指明我们要访问哪个变量.然后我们在控制台监视一下,id和str都是可以拿到的.(此处结论不严谨,各种新浏览器已经就此做了相关优化,详见评论2楼,特别是2楼给出的详细测试报告的连接) 
        所以我来不负责任的预测一下,性能极端苛刻的场景,无阻塞是未来,但无阻塞发展下去,或者有更轻量的脚本引擎产生(lua?),或者V8JS引擎可能要调整可以disable闭包,或者我们可以通过给JS开发静态编译器在代码发布前优化我们的代码. 
        
        我之前谈到过JS静态编译器:"如果给JS代码发布正式使用前增加一个编译步骤,我们能做些什么",动态语言的实时编译系统只完成了静态语言编译中的将代码转化为字节码的过程,而静态语言编译器的额外工作,如接口校验,全局性能优化等待.所以JS也需要一个静态的编译器来完成这些功能,Google利用ClouserComplier提供了系列编译指令,让JS更好的实现OO编程,我来利用静态编译器解决一些JS做细粒度模块化引入的性能方面的问题.而老赵最近的项目JSCEX,则也是利用JS发布前的编译环节重点解决异步编程的代码复杂度问题. 
        我们习惯于阻塞式编程的写法,切换到异步模式编程,往往对于太多多层次的callback嵌套弄得不知所措.所以老赵开发的JS静态编译器,借鉴F#的Computation Expressions,让大家遵守一些小的约定后,能够仍然保持同步编程的写法,写完的代码通过JSCEX编译为异步回调式的代码再交给JS引擎执行. 
        如果这个项目足够好用,那就也解决了一个使用NodeJS这种新技术,却加大编程复杂度这个额外引入的困扰.甚至可以沿着这个思路,在静态编译阶段优化内存使用.

NodeJS还要解决什么问题 
        说了这么多,无阻塞编程要做的还远不止这些.首先需要一个高效的JS引擎,高效的事件池和线程池.另外几乎所有和NodeJS交互的传统模块如文件系统,数据访问,HTTP解析,DNS解析都是阻塞式的,都需要额外改造. 
        正是NodeJS作者极其团队,认清问题问题以及JS解决这些问题方面的优势.基于高效的V8 JavaScript引擎,贡献了大量的智慧和精力解决上述大部分问题后才有NodeJS横空出世. 
        当前Node社区如此火热,千余开源的NodeJS模块,活跃在WebFramework,WebSocket,RPC,模板引擎,数据抓取服务,图形图像几乎所有工程领域.

后记 
        本文主要的信息来自nodejs作者在JSConf09,JSConf10上的分享. 
        而作为前端开发,着重讲了函数式编程,闭包对于无阻塞开发的重要意义.我期待这篇文章能够给前端和后端同学都带来收获.

同样作为前端开发,不得不再插几句,说说服务端JS能够解决的另一个问题: 
        当前的Web开发前后端使用不同的语言,很多相同的业务逻辑要前后端分别用不同语言重复实现.比如越来越多重度依赖JS的胖客户端应用,当客户浏览器禁用JavaScript时,则需要使用服务端语言将主业务流程再实现一次(这即是所谓的"渐进增强"). 
        当我们拥有了服务端JavaScript语言,我们自然就会想到能否利用NodeJS做到"一次开发,渐进增强".解决掉这个为小量用户,浪费大量时间的恼人的问题.我们先要解决问题,这是使用NodeJS的最大动力.基于之前的统计,因为各种原因浏览器不支持JS的用户大概接近1%,至少淘宝绝对不会主动放弃这部分用户.至于在服务端也使用JS是否能够替掉LAMP架构,抑或NodeJS会对常见MVC架构带来何种冲击,V/C这些层是否能在前后端任意流动这些问题都是NodeJS解决问题后带来的额外话题. 
        "一次开发,渐进增强"这方面的实践,YAHOO仍然是先驱,早在一年多前开始YAHOO通过nodejs-yui3项目做了很多卓越的贡献,而淘宝自主开发的前端框架Kissy也有服务端运行的相关尝试,详见我的同事拔赤的分享.而接下来的几个月我也将在这方面做一些尝试,有一定积累后我将再写一篇文章更好的分析这个问题..

JS在诞生之时就不仅仅是浏览器端工具,如今JS能再一次回到服务端展示拳脚,感谢V8,感谢NodeJS作者,团队和社区的诸多贡献者,祝Node好运,JS好运.

转载于:https://my.oschina.net/u/200898/blog/188982

我们为什么要使用NodeJS相关推荐

  1. CentOS6安装nodejs

    Nodejs是JavaScript的一种运行环境,是一个服务端的JavaScript解释器. NPM是Nodejs的包管理器. Nodejs包含npm,所以安装完nodejs后npm默认也被安装. 安 ...

  2. 让我们一起认识一下Nodejs

    Nodejs Nodejs是一个运行在chrome Javascript运行环境下(俗称GoogleV8引擎)的开发平台,用来方便快捷的创建服务器端网络应用程序.可以把它理解为一个轻量级的JSP或PH ...

  3. nodejs 各种插件

    __dirname:全局变量,存储的是文件所在的文件目录 __filename:全局变量,存储的是文件名 代码:dirname.jsconsole.log(__dirname); 运行node dir ...

  4. nodejs pm2使用

    pm2特点 1.简单方便,部署快. 2.解决nodejs单线程缺点 3.完善的日志 pm2安装及使用 1.安装 npm install -g pm2 2.启动 查看版本 pm2 start app.j ...

  5. NodeJS+Mongodb+Express做CMS博客系统

    楼主正在用业余时间开发中-- ,目前的版本仅支持会员系统,尝鲜一下吧~ hi-blog 一个 nodejs+express+mongodb 的 cms 系统 怎么启动 默认你已经安装了 mongodb ...

  6. 简述nodejs、npm及其模块在windows下的安装与配置

    nodejs的安装 登陆官网http://nodejs.org/,自行安装,不需配置环境变量,安装中自动配置了. 检测是否安装成功,使用cmd输入 node -v 即可查看. npm的安装 如果是最新 ...

  7. 关于内网linux系统如果安装nodejs,npm,express,mongodb,forever等

    内网的linux系统要安装nodejs以及express等系列的框架,因为系统是局域网和互联网是物理隔离的,所以,没法像官网的安装教程那样直接install了,只能手动安装,这里已经我们自己的linu ...

  8. windows nodejs mysql_windows server 安装 mysql + nondejs连接mysql

    下载 安装 下载完后,将 zip 包解压到相应的目录,这里我将解压后的文件夹放在 C:\mysql 下. 接下来需要配置下 MySQL 的配置文件 打开刚刚解压的文件夹 C:\mysql ,在该文件夹 ...

  9. php渲染nodejs api,如何使用nodejs 服务器读取HTML文件渲染至前端

    这次给大家带来如何使用nodejs 服务器读取HTML文件渲染至前端,使用nodejs 服务器读取HTML文件渲染至前端的注意事项有哪些,下面就是实战案例,一起来看一下. 1.分别简单实现三个备用页面 ...

  10. nodejs ld linux.so,请教大神,如何在独立虚拟主机上配置node.js开发环境?

    安装一下NodeJs就好啦. Windows系统很简单,直接下载运行安装包.linux复杂一点. 下面给出linux二进制文件安装方法. 首先到这里下载安装文件https://nodejs.org/z ...

最新文章

  1. 解决Table td设置了相同百分比,由于文字个数不同导致列宽不同问题
  2. 判断iis是否已经安装
  3. You can Solve a Geometry Problem too(线段相交问题)
  4. SpringBoot专栏(一) -- SpringBoot简介
  5. PHP 学习笔记 - - - 简单方法的使用 (数组)
  6. 生成n对括号的合法全排列
  7. jquery之bind(),live(),delegate()
  8. 小书匠编辑器使用手册
  9. java课程设计学生信息管理系统。
  10. MEMS:万物智联技术关键
  11. 云痕大数据 家长登录_云痕大数据——苏州云痕教育科技有限公司
  12. python中--snip--是什么意思
  13. vue项目实现高德地图截图
  14. 你要问我应用层?我就和你扯扯扯
  15. Java8新特性学习第一天
  16. 天圆地方,物换星移. 北京天坛,先祖的祈福
  17. error C4189
  18. C 二级 函数fun的功能是:将s所指字符串中除了下标为偶数、 同时ASCII值也为偶数的字符外, 其余的全都删除;
  19. 如何使用华为云快速搭建个人博客
  20. 小米8刷安卓10魔趣Rom

热门文章

  1. Layui组件 sliderVerify 实现滑块验证
  2. 获取网页html内容
  3. 本地 hosts 文件找不到怎么办
  4. (2)、PHP Win10 本地配置RabbitMq
  5. 对图像进行各种滤波的编程思路
  6. 管理感悟:给自己编程水平打分
  7. 用remastersys备份LINUX,注意备份盘的空间占用
  8. 包装应由厂家回收利用
  9. ubuntu无法打开系统设置的解决办法
  10. html table control,HtmlTable.HtmlTableRowControlCollection