当前位置: 首页 > 后端技术 > Node.js

百度工程师带你体验引擎中的nodejs

时间:2023-04-03 13:42:50 Node.js

糖糖攻略如果你是前端程序员,不懂PHP、Python或Ruby等动态编程语言,又想创建自己的服务,那么Node.js是一个很好的选择。Node.js是运行在服务器端的JavaScript。如果您熟悉Javascript,那么您将很容易学习Node.js。当然,如果你是后端程序员,想要部署一些高性能的服务,那么学习Node.js也是一个非常好的选择。全文6723字,预计阅读时间17分钟。01什么是Node.js?我们先来看看Node.js的官方定义:Node.js是一个基于V8JavaScript引擎的JavaScript运行环境。但是这句话可能有点笼统:1.JavaScript运行环境是什么?2、为什么JavaScript需要特殊的运行环境?3.什么是JavaScript引擎?4、什么是V8?带着这些问题,我们先来了解下nodejs的历史。在Node.js出现之前,最常见的JavaScript运行环境是浏览器,也称为JavaScript宿主环境。浏览器为JavaScript提供DOMAPI,使JavaScript能够运行浏览器环境(JS环境)。Node.js出现于2009年初,是基于ChromeV8引擎开发的JavaScript运行环境,因此Node.js也是JavaScript的宿主环境。而它的底层是我们熟悉的Chrome浏览器的JavaScript引擎,所以它和运行在Chrome浏览器中的JavaScript在本质上没有区别。但是,Node.js的运行环境与浏览器还是有区别的。通俗地说,也就是Node.js基于V8引擎执行JavaScript代码,但不仅仅是V8引擎。我们知道V8可以嵌入到任何C++应用程序中,不管是Chrome还是Node.js,其实就是嵌入了V8引擎来执行JavaScript代码,但是在Chrome浏览器中,需要解析和渲染HTML、CSS等等。渲染引擎还需要提供支持浏览器操作的API,浏览器自带的事件循环等。除此之外,我们还需要在Node.js中进行一些额外的操作,比如文件系统的读写、网络IO、文件的加密、压缩和解压等操作。那么我们来看看浏览器是如何解析和渲染的。02浏览器如何渲染页面?浏览器渲染一个网页,简单来说可以分为以下几个步骤:用户需要访问Connection,域名服务器会返回一段HTML文本给用户,供后面渲染(这个很关键,注意)。渲染树构建:浏览器客户端接收到服务器返回的HTML文本后,会对HTML文本进行解析,其中会使用DOM生成DOM树来确定页面的布局结构,CSS会用于生成CSSOM树来确定页面元素的样式。如果在此过程中遇到脚本或静态资源,则会进行预加载,提前请求静态资源,最终生成渲染树。布局:浏览器拿到渲染树后,会进行布局操作,确定页面上各个对象的大小和位置,然后进行渲染。渲染:我们计算机的视图是通过GPU的图像帧来显示的。渲染过程其实就是将上面得到的渲染树转换成GPU的图像帧进行显示。首先,浏览器会根据布局树的位置进行栅格化(用过组件库的同学应该不陌生,就是把页面按照行列划分成相应的层,比如12格,确定根据对应网格列的位置),最后得到一个合成框,包括文字、颜色、边框等;其次,将合成帧提升为GPU的图像帧,然后显示在页面上,我们的页面就可以在电脑上看到了。相信大家看到这里,对于浏览器是如何渲染页面的,已经有了一个大概的了解。页面绘制实际上是浏览器将HTML文本转换成相应页面框架的过程。页面的内容和渲染过程与第一步得到的HTML文本密切相关。03事件循环与异步IO关于事件循环首先要了解什么?那么我们先来了解进程和在线的概念。进程和线程:都是操作系统的概念。进程:计算机已经在运行的程序,线程:操作系统可以调度的最小单位。启动应用程序默认打开一个进程(或多个进程)。每个进程都会启动一个线程来执行程序中的代码,这个线程称为主线程。举个例子:比如工厂相当于操作系统,工厂里的车间相当于进程,车间里的工人相当于线程,所以进程相当于线程容器。那么浏览器是进程吗?里面只有一根线吗?目前浏览器一般都是多进程的。通常,打开一个选项卡将启动一个新进程。这是为了防止一个页面卡住导致所有页面无法响应,需要强制退出整个浏览器。其中每个进程都包含多个线程,包括执行js代码的线程。js代码在单独的线程中执行。一个线程一次只能做一件事。如果这个东西很耗时,那就意味着当前线程会被阻塞。浏览器时间周期维护两个队列:宏任务队列和微任务队列。宏任务队列:ajax、setTimeout、setInterval、DOM监控、UIRendering等;微任务队列:然后是Promise的回调。那么这两个队列的事件循环的优先级是多少呢?主脚本中的代码先执行(编写的顶层脚本代码);在执行任何宏任务(不是队列,是宏任务)之前,它会先检查微任务队列中是否有需要执行的任务,即在执行宏任务之前,微任务队列必须为空;如果它不为空,那么微任务队列中的任务将首先执行(回调)。newpromise()是同步的,promise.then、promise.catch、resolve、reject是微任务。04使用事件驱动程序Node.js使用事件驱动模型。当Web服务器收到请求时,它会关闭它并处理它,然后为下一个Web请求提供服务。当请求完成后,将其放回处理队列,当到达队列头部时,将结果返回给用户。该模型非常高效且可扩展,因为网络服务器总是在不等待任何读取或写入操作的情况下接受请求。(这也叫非阻塞IO或事件驱动IO)在事件驱动模型中,会产生一个主循环来监听事件,当检测到事件时会触发回调函数。整个事件驱动流程就是这样实现的,非常简洁。有点类似于观察者模式,一个事件相当于一个主题(Subject),所有注册到这个事件的处理者都相当于观察者(Observer)。Node.js有多个内置事件。我们可以通过引入事件模块并实例化EventEmitter类来绑定和监听事件,如下例所示://导入事件模块varevents=require('events');//创建一个eventEmitter对象vareventEmitter=新事件.EventEmitter();以下程序绑定事件处理程序://绑定事件和事件处理程序eventEmitter.on('eventName',eventHandler);我们可以通过程序触发事件://触发一个事件eventEmitter.emit('eventName');创建main.js文件实例,代码如下://导入事件模块varevents=require('events');//创建一个eventEmitter对象vareventEmitter=newevents.EventEmitter();//创建事件处理器varconnectHandler=functionconnected(){console.log('连接成功~~~');//触发data_received事件eventEmitter.emit('data_received');}//绑定连接事件处理器eventEmitter.on('connection',connectHandler);//使用匿名函数绑定data_received事件eventEmitter.on('data_received',function(){console.log('收到数据');});//触发连接事件eventEmitter.emit('connection');console.log("程序执行完毕。");接下来我们执行上面的代码:$nodemain.js连接成功~~~数据接收到。程序执行完毕。05Node.js架构及与浏览器的差异上图展示了Node.js的基本架构。我们可以看到(Node.js运行在操作系统上),它的底层由V8JavaScript引擎和一些C/C++库组成,包括libUV库、c-ares、llhttp/http-parser、open-ssl、zlib等。其中libUV负责处理事件循环,c-ares、llhttp/http-parser、open-ssl、zlib等库提供DNS解析、HTTP协议、HTTPS等功能文件压缩。这些模块的上层是中间层,包括Node.jsBindings、Node.jsStandardLibrary、C/C++AddOns。Node.jsBindings层的作用是将C/C++编写的底层库接口暴露给JS环境,而Node.jsStandardLibrary是Node.js本身的核心模块。至于C/C++AddOns,它允许用户通过桥接器将自己的C/C++模块提供给Node.js。中间层之上是Node.jsAPI层。我们使用Node.js来开发应用,主要是使用Node.jsAPI层,所以Node.js应用最终会运行在Node.jsAPI层上。总结一下:Node.js系统架构图主要由四部分组成:application、V8javascriptengine、Node.jsbindings、libuv。应用:nodejs应用就是我们写的js代码。V8:JavaScript引擎,解析js代码,调用Nodeapi。Node.jsbindings:Nodeapi,这些API最终由libuv驱动。libuv:AsynchronousI/O,实现异步非阻塞的核心模块。libuv库提供了两个最重要的东西,事件循环和线程池,它们共同构建了一个异步非阻塞I/O模型。以线程为纬度划分,可以分为Node.js线程和其他C++线程。应用程序启动一个线程并在Node.js线程中完成它。Node.js的I/O操作是非阻塞的,大量的计算能力被分配给其他C++线程。C++线程完成计算后,将结果发送给Callback给Node.js线程,Node.js线程将内容返回给应用程序。浏览器中的事件循环是按照HTML5规范实现的,不同的浏览器可能有不同的实现,而libuv是在node中实现的,因为Node.js不是浏览器,所以没有浏览器提供的DOMAPI。如Window对象、Location对象、Document对象、HTMLElement对象、Cookie对象等。但是Node.js提供了自己独有的API,比如global全局对象,当前进程信息的Process对象,操作文件的fs模块,创建Web服务的http模块等等,这些API让我们可以操作使用JavaScript的计算机,因此我们可以使用Node.js平台开发Web服务器。还有一些Node.js和浏览器通用的对象,比如JavaScript引擎的内置对象,由V8引擎提供。常见的有:基本常量undefined、null、NaN、Infinity;内置对象Boolean、Number、String、Object、Symbol、Function、Array、Regexp、Set、Map、Promise、Proxy;全局函数eval、encodeURIComponent、decodeURIComponent等。另外还有一些方法不属于引擎内置的API,但是两者都可以实现,比如setTimeout、setInterval方法、Console对象等。5.1阻塞IO和非阻塞IO如果我们要在程序中要对一个文件进行操作,那么我们就需要打开这个文件:通过文件描述符。我们想:JavaScript可以直接操作文件吗?看似可以,但实际上我们任何程序中的任何文件操作都需要系统调用(操作系统的文件系统);实际上,对文件的操作就是操作系统的一次IO操作(输入、输出)。操作系统为我们提供了阻塞调用和非阻塞调用:阻塞调用:在调用结果返回之前,当前线程处于阻塞状态(处于阻塞状态的CPU不会分配时间片),调用线程只能将继续执行。非阻塞调用:调用执行后,当前线程不会停止执行,只需要一段时间检查是否有结果返回。因此,我们开发中的很多耗时操作都可以基于这样的非阻塞调用:比如网络请求本身使用Socket通信,Socket本身提供了select模型,可以以非阻塞的方式工作;比如文件读写等IO操作,我们可以使用操作系统提供的基于事件的回调机制。5.2非阻塞IO的问题但是非阻塞IO也有一定的问题:我们还没有得到需要读取的结果(我们以读取为例),也就是说为了知道完整的数据是否有读取完毕,我们需要经常判断读取的数据是否完整。我们称这个过程为轮训。那么这次轮训的工作由谁来做呢?如果我们的主线程频繁进行round-robin训练,性能会大大降低,而在开发中,我们可能不只是读写一个文件,可能有多个文件,可能有多种功能:网络IO,数据库IO,子进程调用。libuv提供了一个线程池(ThreadPool):线程池会负责所有相关的操作,并会通过循环训练的方式等待结果。当得到结果后,可以将相应的回调放入事件循环(一个事件队列)中。事件循环可以负责接管后续的回调工作,通知JavaScript应用程序执行相应的回调函数。5.3阻塞和非阻塞、同步和异步的区别是什么?首先,阻塞和非阻塞是针对被调用者的;在我们的例子中,它是一个系统调用。操作系统为我们提供了阻塞调用和非阻塞调用,同步和异步是给调用者的。这是我们自己的程序;如果我们在发起调用后不进行任何其他操作,只是等待结果,这个过程称为同步调用;如果再次发起调用,则不等待结果,继续完成其他工作,等待有回调执行。这个过程是一个异步调用。libuv使用非阻塞异步IO的调用方式。5.4Node事件循环阶段我们一开始就强调了,事件循环就像一座桥,一个连接应用的JavaScript和系统调用的通道:不管是我们的文件IO,数据库,网络IO,定时器,子进程完成相应的操作后,会将相应的结果和回调函数放入事件循环(任务队列);事件循环会不断从任务队列中取出对应的事件(回调函数)执行;但是一次完整的事件周期Tick分为很多阶段:定时器(Timers):这个阶段执行已经setTimeout()和setInterval()的调度回调函数。PendingCallback(挂起回调):对某些系统操作(如TCP错误类型)执行回调,如TCP连接期间接收空闲,prepare:仅供系统内部使用。Poll:检索新的I/O事件;执行I/O相关的回调;检测:这里执行setImmediate()回调函数。关闭回调函数:一些关闭回调函数,如:socket.on('close',...)。5.5Node事件循环阶段图06Node.js常用内置模块和全局变量如果想了解更多关于全局对象的知识,请参考以下链接:https://m.runoob.com/nodejs/n...——END——参考资料:[1]https://juejin.cn/post/684490...[2]https://nodejs.org/zh-cn/docs...[3]部分图片来自稀土掘金网站推荐阅读:揭秘百度智能测试在测试定位领域百度工程师带你探秘C++内存管理(ptmalloc篇)为什么OpenCV计算的视频FPS不对百度安卓直播广播体验优化iOSSIGKILLsemaphorecrash捕获及优化练习如何在百万级qps的网关服务中实现灵活的调度策略