内容简介:从Node.js进入我们的视野时,我们所知道的它就由这些关键字组成 事件驱动、非阻塞I/O、高效、轻量,它在官网中也是这么描述自己的。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.于是在我们刚接触Nodejs时,会有所疑问:
前言
从Node.js进入我们的视野时,我们所知道的它就由这些关键字组成 事件驱动、非阻塞I/O、高效、轻量,它在官网中也是这么描述自己的。
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.
于是在我们刚接触Nodejs时,会有所疑问:
1、为什么在浏览器中运行的Javascript 能与操作系统进行如此底层的交互? 2、nodejs 真的是单线程吗? 3、如果是单线程,他是如何处理高并发请求的? 4、nodejs 事件驱动是如何实现的?
等等。。。
看到这些问题,是否有点头大,别急,带着这些问题我们来慢慢看这篇文章。
架构一览
上面的问题,都挺底层的,所以我们从 Node.js 本身入手,先来看看 Node.js 的结构。
· Node.js 标准库,这部分是由 Javascript 编写的,即我们使用过程中直接能调用的 API。在源码中的 lib 目录下可以看到。
·Node bindings,这一层是 Javascript 与底层 C/C++ 能够沟通的关键,前者通过 bindings 调用后者,相互交换数据。实现在 node.cc
·这一层是支撑 Node.js 运行的关键,由 C/C++ 实现。
V8:Google 推出的 Javascript VM,也是 Node.js 为什么使用的是 Javascript 的关键,它为Javascript 提供了在非浏览器端运行的环境,它的高效是 Node.js 之所以高效的原因之一。 Libuv :它为 Node.js 提供了跨平台,线程池,事件池,异步 I/O 等能力,是 Node.js 如此强大的关键。 C-ares :提供了异步处理 DNS 相关的能力。 http_parser、OpenSSL、zlib 等:提供包括 http 解析、SSL、数据压缩等其他的能力。
与操作系统交互
举个简单的例子,我们想要打开一个文件,并进行一些操作,可以写下面这样一段代码:
var fs = require('fs'); fs.open('./test.txt', "w", function(err, fd) { //..do something });
这段代码的调用过程大致可描述为:lib/fs.js → src/node_file.cc → uv_fs
lib/fs.js async function open(path, flags, mode) { mode = modeNum(mode, 0o666); path = getPathFromURL(path); validatePath(path); validateUint32(mode, 'mode'); return new FileHandle( await binding.openFileHandle(pathModule.toNamespacedPath(path), stringToFlags(flags), mode, kUsePromises)); } src/node_file.cc static void Open(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); const int argc = args.Length(); if (req_wrap_async != nullptr) { // open(path, flags, mode, req) AsyncCall(env, req_wrap_async, args, "open", UTF8, AfterInteger, uv_fs_open, *path, flags, mode); } else { // open(path, flags, mode, undefined, ctx) CHECK_EQ(argc, 5); FSReqWrapSync req_wrap_sync; FS_SYNC_TRACE_BEGIN(open); int result = SyncCall(env, args[4], &req_wrap_sync, "open", uv_fs_open, *path, flags, mode); FS_SYNC_TRACE_END(open); args.GetReturnValue().Set(result); } } uv_fs /* Open the destination file. */ dstfd = uv_fs_open(NULL, &fs_req, req->new_path, dst_flags, statsbuf.st_mode, NULL); uv_fs_req_cleanup(&fs_req);
Node.js 深入浅出上的一幅图:
具体来说,当我们调用 fs.open 时,Node.js 通过 process.binding 调用 C/C++ 层面的 Open 函数,然后通过它调用 Libuv 中的具体方法 uv_fs_open,最后执行的结果通过回调的方式传回,完成流程。
我们在 Javascript 中调用的方法,最终都会通过 process.binding 传递到 C/C++ 层面,最终由他们来执行真正的操作。Node.js 即这样与操作系统进行互动。
单线程
在传统web 服务模型中,大多都使用多线程来解决并发的问题,因为I/O 是阻塞的,单线程就意味着用户要等待,显然这是不合理的,所以创建多个线程来响应用户的请求。
Node.js 对http 服务的模型:
Node.js的单线程指的是主线程是“单线程”,由主要线程去按照编码顺序一步步执行程序代码,假如遇到同步代码阻塞,主线程被占用,后续的程序代码执行就会被卡住。实践一个测试代码:
var http = require('http'); function sleep(time) { var _exit = Date.now() + time * 1000; while( Date.now() < _exit ) {} return ; } var server = http.createServer(function(req, res){ sleep(10); res.end('server sleep 10s'); }); server.listen(8080);
下面为代码块的堆栈图:
先将index.js的代码改成这样,然后打开浏览器,你会发现浏览器在10秒之后才做出反应,打出Hello Node.js。
JavaScript是解析性语言,代码按照编码顺序一行一行被压进stack里面执行,执行完成后移除然后继续压下一行代码块进去执行。上面代码块的堆栈图,当主线程接受了request后,程序被压进同步执行的sleep执行块(我们假设这里就是程序的业务处理),如果在这10s内有第二个request进来就会被压进stack里面等待10s执行完成后再进一步处理下一个请求,后面的请求都会被挂起等待前面的同步执行完成后再执行。
那么我们会疑问:为什么一个单线程的效率可以这么高,同时处理数万级的并发而不会造成阻塞呢?就是我们下面所说的--------事件驱动。
事件驱动/事件循环
Event Loop is a programming construct that waits for and dispatches events or messages in a program.
1、每个Node.js进程只有一个主线程在执行程序代码,形成一个 执行栈 ( execution context stack )。
2、主线程之外,还维护了一个" 事件队列 "(Event queue)。当用户的网络请求或者其它的异步操作到来时,node都会把它放到Event Queue之中,此时并不会立即执行它,代码也不会被阻塞,继续往下走,直到主线程代码执行完毕。
3、主线程代码执行完毕完成后,然后通过Event Loop,也就是 事件循环机制 ,开始到Event Queue的开头取出第一个事件,从线程池中分配一个线程去执行这个事件,接下来继续取出第二个事件,再从 线程池 中分配一个线程去执行,然后第三个,第四个。主线程不断的检查事件队列中是否有未执行的事件,直到事件队列中所有事件都执行完了,此后每当有新的事件加入到事件队列中,都会通知主线程按顺序取出交EventLoop处理。当有事件执行完毕后,会通知主线程,主线程执行回调,线程归还给线程池。
4、主线程不断重复上面的第三步。
我们所看到的node.js单线程只是一个js主线程,本质上的异步操作还是由线程池完成的,node将所有的阻塞操作都交给了内部的线程池去实现,本身只负责不断的往返调度,并没有进行真正的I/O操作,从而实现异步非阻塞I/O,这便是node单线程和事件驱动的精髓之处了。
原文作者,腾讯工程师Howell。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
从入门到精通:Prezi完全解读
计育韬、朱睿楷、谢礼浩 / 电子工业出版社 / 2015-9 / 79.00元
Prezi是一款非线性逻辑演示软件,它区别于PowerPoint的线性思维逻辑;而是将整个演示内容铺呈于一张画布上,然后通过视角的转换定位到需要演示的位置,并且它的画布可以随时zoom in和zoom out,给演示者提供了一个更好的展示空间。 Prezi对于职场人士和在校学生是一个很好的发挥创意的工具,因为它的演示逻辑是非线性的,所以用它做出来的演示文稿可以如思维导图一样具有发散性,也可以......一起来看看 《从入门到精通:Prezi完全解读》 这本书的介绍吧!