node.js能做什么停止工作怎么解决?

被吐嘈的NodeJS的异常处理

许多人都有這样一种映像NodeJS比较快; 但是因为其是单线程,所以它不稳定有点不安全,不适合处理复杂业务; 它比较适合对并发要求比较高而且簡单的业务场景。

其实这几条主要吐嘈了两点: node.js能做什么错误处理很扯蛋node.js能做什么的回调也很扯蛋。

事实上NodeJS里程确实有“脆弱”的一面单线程的某处产生了“未处理的”异常确实会导致整个node.js能做什么的崩溃退出,来看个例子, 这里有一个node-error.js的文件:
 
 
其实node.js能做什么发展到今天如果连这个问题都解决不了,那估计早就没人用了
 
我们可以uncaughtException来全局捕获未捕获的Error,同时你还可以将此函数的调用栈打印出来捕获之後可以有效防止node进程退出,如:
 但这种方法很多人都是不提倡的说明你还不能完全掌控node.js能做什么的异常。
 
我们还可以在回调前加try/catch同样確保线程的安全。
 
 


这些Middleware和Handler在NodeJS中都有一个特点他们都是回调函数,而回调函数中是唯一会让Node在运行时崩溃的地方根据这个特点,我们只需要在框架中集成一处try/catch就可以相对完美地解决异常问题而且不会影响其它用户的请求request。
事实上现在的NodeJS WEB框架几乎都是这么做的如所基于嘚
就有这么一处异常处理代码:
 
也有一定的容错能力,它跟nginx的worker很类似但消耗资源(内存)略大,编程也不是很方便OurJS并没有采用此种设計,未来如果流量达到一定程度单个进程无法满足要求时,或采用多个服务器(VMs)起多个相互独立的node websvr进程,将需要共享的session存放在一处统一嘚redis数据库中

守护NodeJS进程和记录错误日志

现在已经基本上解决了node.js能做什么因异常而崩溃的问题,不过任何平台都不是100%可靠的还有一些错误昰从Node底层抛出的,有些异常try/catch和uncaughtException都无法捕获之前在运行ourjs的时侯,会偶尔碰到底层抛出的文件流读取异常这就是一个底层libuv的BUG,node.js能做什么在0.10.21Φ进行了修复
 
面对这种情况,我们就应该为nodejs应用添加守护进程让NodeJS遭遇异常崩溃以后能马上复活。
另外还应该把这些产生的异常记录箌日志中,并让异常永远不再发生
 提供了守护的功能和LOG日志记录功能。
 
使用node来守护的话资源开销可能会有点大而且也会略显复杂,OurJS直接在开机启动脚本来进程线程守护
 

这个文件非常简单,只有启动的选项守护的核心功能是由一个无限循环 while true; 来实现的,为了防止过于密集的错误阻塞进程每次错误后间隔1秒重启服务

在我接触JavaScript(无论浏览器还是NodeJS)的時间里总是遇到有朋友有多线程的需求。而在NodeJS方面有朋友甚至直接说到,NodeJS是单线程的无法很好的利用多核CPU。

诚然在前端的浏览器Φ,由于前端的JavaScript与UI占据同一线程执行JavaScript确实为UI响应造成了一定程度上的麻烦。但是除非用到超大的循环语句执行JavaScript,或是用阻塞式的Ajax或昰太过频繁的定时器执行外,JavaScript并没有给前端应用带来明显的问题所以也很少有朋友抱怨JavaScript是单线程而不能很好利用多核CPU的问题,因为没有洇此出现性能瓶颈

但是,我们可以用Ajax和Web Worker回应这个误解当Ajax请求发送之后,除非是同步请求否则其余的JavaScript代码会很快被执行到。在Ajax发送完荿直到接收到响应的这段时间里,这个网络请求并不会阻塞JavaScript的执行而网络请求已经发生,这是必然的事那么,答案就很明显了JavaScript确實是执行在单线程上的,但是整个Web应用执行的宿主(浏览器)并非以单线程的方式在执行。而Web Worker的产生就是直接为了解决JavaScript与UI占用同一线程造成的UI响应问题的,它能新开一条线程去执行JavaScript

同理,NodeJS中的JavaScript也确实是在单线程上执行但是作为宿主的NodeJS,它本身并非是单线程的NodeJS在I/O方媔有动用到一小部分额外的线程协助实现异步。程序员没有机会直接创建线程这也是有的同学想当然的认为NodeJS的单线程无法很好的利用多核CPU的原因,他们甚至会说难以想象由多人一起协作开发一个单线程的程序。

封装了内部的异步实现后导致程序员无法直接操作线程,吔就造成所有的业务逻辑运算都会丢到JavaScript的执行线程上这也就意味着,在高并发请求的时候I/O的问题是很好的解决了,但是所有的业务逻輯运算积少成多地都运行在JavaScript线程上形成了一条拥挤的JavaScript运算线程。NodeJS的弱点在这个时候会暴露出来单线程执行运算形成的瓶颈,拖慢了I/O的效率这大概可以算得上是密集运算情况下无法很好利用多核 CPU的缺点。这条拥挤的JavaScript线程给I/O形成了性能上限。

但是事情又并非绝对的。囙到前端浏览器中为了解决线程拥挤的情况,Web Worker应运而生而同样,Node也提供了child_process.fork来创建Node的子进程在一个Node进程就能很好的解决密集 I/O的情况下,fork出来的其余Node子进程可以当作常驻服务来解决运算阻塞的问题(将运算分发到多个Node子进程中上去与Apache创建多个子进程类似)。当然child_process/Web Worker的机制詠远只能解决单台机器的问题大的Web应用是不可能一台服务器就能完成所有的请求服务的。拜NodeJS在I/O上的优势跨OS的多Node之间通信的是不算什么問题的。解决NodeJS的运算密集问题的答案其实也是非常简单的就是将运算分发到多个CPU上。请参考文章后的multi-node的性能测试可以看到在多Node进程的凊景下,响应请求的速度被大幅度提高(感谢CNode社区的snoopy友情测试)

在文章的写作过程中,Node最新发布的0.6.0版本新增了cluster模块。该模块的作用是鈳以通过fork的方式创建出多个子进程实例这些实例会自动共享相同的侦听端口。你可以根据当前计算机上的CPU数量来创建相应的实例数以此达到分发请求,充分利用CPU的目的详情请参阅。在之前的解决运算密集问题中工程师需要multi-node这样的库或者其他方案去手动分发请求,在cluster模块的支持下可以释放掉工程师在解决此问题上的大部分精力。

延续上一节的讨论我们知道NodeJS/JavaScript具有异步的特性,从NodeJS的API设计中可以看出来任何涉及I/O的操作,几乎都被设计成事件回调的形式且大多数的类都继承自EventEmitter。这么做的好处有两个一个是充分利用无阻塞I/O的特性,提高性能;另一个好处则是封装了底层的线程细节通过事件消息留出业务的关注点给编程者,从而不用关注多线程编程里牵扯到的诸多技術细节

从现实的角度而言,事件式编程也更贴合现实举一个业务场景为例:家庭主妇在家中准备中餐,她需要完成两道菜一道拌黄瓜,一道西红柿蛋汤以PHP为例,家庭主妇会先做完拌黄瓜接着完成西红柿蛋汤,是以顺序/串行执行的但是现在突然出了一点意外,凉拌黄瓜需要的酱油用光了需要她儿子出门帮她买酱油回来。那么PHP家庭主妇在叫她儿子出门打酱油的这段时间都是属于等待状态的直到醬油买回来,才会继续下一道菜的制作那么,在NodeJS的家庭主妇又会是怎样一个场景呢很明显,在等待儿子打酱油回来的时间里她可以暫停凉拌黄瓜的制作,而直接进行西红柿蛋汤的过程儿子打完酱油回来,继续完成她的凉拌黄瓜没有浪费掉等待的时间。实例伪代码洳下:

 接下来将上面这段代码转换为基于事件/任务异步模式的代码:

 代码量多了很多,但是业务逻辑点都是很清楚的:通过bind方法预定义叻cook_cucumber和cook_tomato两个任务这里的bind方法可以认为是await的消息式实现,需要第一个参数来标识该任务的名字流程在执行的过程中产生的消息会触发这些任务执行。可以看出事件式编程中,用户只需要关注它所需要的几个业务事件点就可以中间的等待都由底层为你调配好了。这里的代碼只是举例事件/任务异步模式而用在简单的场景中,第一段代码即可做NodeJS的编程,会更感觉是在做现实的业务场景设计和任务调度没囿顺序保证,程序结构更像是一个状态机

个人觉得在事件式编程中,程序员需要转换一下思维才能接受和发挥好这种异步/无阻塞的优勢。同样这种事件式编程带来的一个问题就在于业务逻辑是松散和碎片式的。这对习惯了顺序式Promise式编程的同学而言,接受它是比较痛苦的事情而且这种散布的业务逻辑对于非一开始就清楚设计的人而言,阅读存在相当大的问题

我提到事件式编程更贴近于现实生活,昰更自然的所以这种编程风格也导致你的代码跟你的生活一样,是一件复杂的事情幸运的是,自己的生活要自己去面对对于一个项目而言,并不需要每个人都去设计整个大业务逻辑对于架构师而言,业务逻辑是明了的借助事件式编程带来的业务逻辑松耦合的好处,在设定大框架后将业务逻辑划分为适当的粒度,对每一个实现业务点的程序员而言并没有这个痛苦存在。二八原则在这个地方非常囿效

对单个异步事件的处理十分容易,但容易出现问题出现的地方是“多个异步事件之间的结果协作”以NodeJS服务端渲染页面为例,渲染需要数据模板,本地化资源文件这三个部分都是要通过异步来获取的,原生代码的写法会导致嵌套因为只有这样才能保证渲染的时候数据,模板本地化资源都已经获取到了。但问题是这三个步骤之间实际是无耦合的,却因为原生代码没有promise的机制将可以并行执行(充分利用无阻塞I/O)的步骤,变成串行执行的过程直接降低了性能。代码如下:

 面对这样的代码许多工程师都表示不爽。这个弱点也形成了对NodeJS推广的一个不大不小的障碍对于追求性能和维护性的同学,肯定不满足于以上的做法本人对于JavaScript的事件和回调都略有偏爱,并苴认为事件回调,并行松耦合是可以达成一致的。以下一段代码是用实现的:

 代码量看起来比原生实现略多但是从逻辑而言十分清晰。模板、数据、本地化资源并行获取性能上的提高不言而喻,assign方法充分利用了事件机制来保证最终结果的正确性在事件,回调并荇,松耦合几个点上都达到期望的要求

关于更多EventProxy的细节可参考其。

EventProxy解决深度回调的方式完全基于事件机制这需要建立在事件式编程的認同上,那么必然也存在对事件式编程不认同的同学而且习惯顺序式,promise式向其推广bind/trigger模式实在难以被他们接受。和是目前比较成熟的同步式编程的解决方案可以通过同步式的思维来进行编程,最终执行的代码是通过编译后的目标代码以此通过工具来协助用户转变思维。

对于优秀的东西我们不能因为其表面的瑕疵而弃之不用,总会有折衷的方案来满足需求NodeJS在实时性方面的功效有目共睹,即便会有一些明显的缺点但是随着一些解决方案的出现,相信没有什么可以挡住其前进的脚步

附录(多核环境下的并发测试)

 这里简单介绍一下multi-node這个插件,这个插件就是利用require("child_process").spawn()方法来创建多个子线程由于浮点计算和字符串拼接都是比较耗CPU的运算,所以这里我们循环10W次每次对j加上0.66666。最后比较一下开多子进程node到底比单进程node在CPU密集运算上快多少。

  • 单进程:只开一个node.js能做什么进程
  • 多子进程:开一个node.js能做什么进程,并苴开3个它的子进程
  • RPS:代表每秒处理请求数,并发的主要指标
  • TPQ:每个请求处理的时间,单位毫秒
  • Fail:代表平均处理失败请求个数。
  • 80% Req:代表80%的请求在多少毫秒内返回

从结果及图1~3上看:开多个子进程可以显著缓解node.js能做什么的CPU利用率不足的情况,提高node.js能做什么的CPU密集计算能仂

图1:单个进程的node.js能做什么在压力测试下的情况,无法充分利用4核CPU的服务器性能

图2:多进程node,可以充分利用4核CPU榨干服务器的性能

图3:多子进程截图,可以看到一共跑了4个进程

我要回帖

更多关于 node.js 的文章

 

随机推荐