jawil/Node.js

Node.js的核心概念——单线程,非阻塞,事件驱动

Opened this issue · 2 comments

jawil commented

一些文章

1、这就是所谓的Node.js------单线程,非阻塞,事件驱动
2、Node.js 探秘:初识单线程的 Node.js
3、进程和线程
4、同步、异步、阻塞及非阻塞
5、进程 线程 协程 管程 纤程 概念对比理解
6、I/O模型 阻塞 非阻塞 同步 异步概念对比区分

一些概念

1、什么是Node.js?

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. Node.js' package ecosystem, npm, is the largest ecosystem of open source libraries in the world.

我们可以注意到:Node.js是一个平台,它构建在Chrome的JavaScript引擎(也就是大名鼎鼎的V8引擎)之上,它能快速构建有拓展性的网络应用程序。这里官方用的“网络应用程序”,整个描述没有提到“web","sever"等等概念。

举个栗子,类比来说,概念上,Node.js相当于.net,JVM或者PythonVM。它是一个运行平台,只不过它运行的是JavaScript语言而已。类似的,.net一般运行C#,VB等编译的IL,JVM一般运行Java编译成的字节码,而PythonVM解释运行Python代码,RubyVM解释运行Ruby代码。严格来说Node.js更像PythonVM,RubyVM,因为他们都是解释运行的动态语言。

回到刚才的话中:,有几个是重点:

  1. node.js是一个构建在Chrome JavaScript运行环境的平台,这是很重要的一点,node.js并不是一门语言,而是一个平台。
  2. node.js致力于构建速度快、稳定的网络程序更简单。
  3. node.js具有事件驱动和非阻塞I/O的特色,使之轻量级并且高效率。
  4. node.js非常适合在分布式设备运行数据密集型实时应用程序。

如果你能掌握住这几点,我觉得算是掌握了Node.js的真谛了。

Node.js核心主要是两部分组成的:

  1. V8引擎,它负责把JavaScript代码解释成本地的二进制代码运行。
  2. libuv,类似Windows上的窗口消息机制,它主要负责订阅和处理系统的各种内核消息。而且它也实现了消息循环(是不是很耳熟?没错,这个几乎就和Windows的窗口消息循环是一个概念。)它的前身是linux上的libev,专门封装linux上的内核消息机制。后来Node.js重写了它,并在Windows上使用iocp技术重新实现了一遍。所以Node.js现在能跨平台运行在Windows上了。

可以说,Node.js其实就是libuv的一个应用而已。简单的说Node.js只是把JavaScript解释成C++的回调,并挂在libuv消息循环上,等待处理。这样就实现了非阻塞的异步处理机制。

那么为什么是JavaScript而不是其他语言呢?很简单,因为JavaScript的闭包。这非常适合做回调函数。因为我们一般都希望当回调发生时,闭包能记住它原来所在的执行上下文。这就是闭包最好的应用场景。

2、Why Node.js?

Node.js出现确实能为我们解决现实当中系统瓶颈提供了新的思路和方案,下面我们看看它能解决什么问题?

并发连接

举个栗子,想象一个场景,我们在银行排队办理业务,我们看看下面两个模型。

(1)系统线程模型:
这种模型的问题显而易见,服务端只有一个线程,并发请求(用户)到达只能处理一个,其余的要先等待,这就是阻塞,正在享受的请求阻塞后面的请求了。

(2) 多线程、线程池模型:
这个模型已经比上一个有所进步,它调节服务端的数量来提高对并发请求的接收和响应,但并发量高的时候,请求仍然需要等待,他有个更严重的问题。到代码层面上来讲,我们看看客户端请求与服务通讯的过程:服务端与客户端每建立一个连接,都要为这个连接分配一套配套的资源,主要体现为系统内存资源,以PHP为例,维护一个连接可能需要20M的内存。这就是为什么一般并发量一大,就需要多开服务器。那么Node.js是怎么解决这个问题呢?我们来看看另外一个模型,想象一下我们在快餐店吃饭的场景。

(3)异步、事件驱动模型
我们同样是发起请求,等待服务器端响应;但是与银行例子不同的是,这次我们点完餐后拿到了一个号码,拿到号码,我们往往会在位置上等待,而在我们后面的请求会继续得到处理,同样是拿了一个号码然后到一旁等待,接待员能一直进行处理。等待饭菜做好了,会喊号码,我们拿到了自己的饭菜,会进行后续的处理(吃饭)。这个喊号码的动作在Node.js中叫做回调(CallBack),能在事件(烧菜,I/O)处理完成后继续执行后面的逻辑(吃饭),这体现了Node.js显著特点,异步机制、事件驱动整个过程没有阻塞新用户的连接(点餐),也不需要维护已经点餐的用户与厨师的连接。基于这样的机制,理论上陆续有用户请求连接,Node.js都能够进行响应,因此Node.js能支持比Java,PHP程序更高的的并发量虽然维护事件队列也需要成本,再由于Node.js是单线程,事件队列越长,得到响应的时间就越长,并发量上去还是会力不从心。

总结一下Node.js是怎么解决这个问题的:

更改到连接服务器的方式,每个连接发射(emit)一个在Node.js引擎进程中运行的事件(Event),放进事件队列当中,而不是为每个连接生成一个新的OS线程(并未其分配一些配套内存)。

I/O阻塞

Node.js解决的另外一个问题是I/O阻塞,看看这样的业务场景:需要从多个数据源拉取数据,然后进行处理。

①串行获取数据,这是我们一般的解决方案,一PHP为例的I/O阻塞-PHP为例,假如获取profile和timeline操作各需要1s,那么串行获取就需要2s。
②Node.js非阻塞I/O事件会创建一个线程去执行,然后主线程会继续往下执行,因此,拿profile的动作触发一个I/O事件,马上就会去执行拿timeline的动作,两个动作并行执行,加入各需要1s,那么总的时间就是1s。它们的I/O操作执行完成后,会发射一个事件,profile和timeline,事件代理接收后继续往下执行后面的逻辑,这就是Node.js非阻塞I/O的特点。

总结一下:Java、PHP也有办法实行并行请求(子线程),但Node.js通过回调函数(CallBack)和异步机制会做的很自然。

3、Node.js优缺点

优点:
  1. 高并发(最重要的优点)
  2. 适合I/O密集型应用(静态资源服务器、blog、聊天室)
缺点:
  1. 不适合CPU密集型应用(模板渲染、压缩/解压缩、加/解密);
    CPU密集型应用给Node带来的挑战主要是:由于JavaScript单线程的原因,如果有长时间运行的计算(比如大循坏),将会导致CPU时间片不能释放,使得后续I/O无法发起;解决方案:分解大型运算任务为多个小任务,使得运算能够适时释放,不阻塞I/O调用发起;
  2. 只支持单核CPU,不能充分利用CPU
  3. 可靠性低,一旦代码某个环节崩溃,整个系统都崩溃,其中2和3的原因:单进程,单线程解决方案:
    i. Ngnix反向代理,负载均衡,开多个进程,绑定多个端口;
    ii. 开多个进程监听同一个端口,使用cluster模块;
  4. 开源组件质量参差不齐,更新快,向下不兼容。
  5. Debug不方便,错误没有stack trace。

那么为什么是JavaScript而不是其他语言呢?很简单,因为JavaScript的闭包。
你好,这句话我有些疑问,其他语言也有闭包啊,比如python,为什么只是因为js的闭包呢?希望得到解答~

这句话不够准确,应该是因为 node.js中的libuv实现了 基于事件驱动的 事件循环,因此因为单线程,而有非阻塞的feature,事件轮询 中 每个回调函数 正是因为js的闭包才完整包含的函数执行上下文。而不是因为js语言 feature。因果关系不够准确而已。