首页 > 编程语言 >[NodeJS] NodeJS运行原理简记

[NodeJS] NodeJS运行原理简记

时间:2024-07-08 14:11:21浏览次数:14  
标签:Node NodeJS EventEmitter 简记 线程 事件 监听器 原理

NodeJS的基本组成

NodeJS是JavaScript运行时,主要由V8引擎和libuv组成,其中V8使用 javascript 和 c++ 编写,而libuv是纯 c++ 编写的,二者都是开源的。

image-20240707155536398

V8引擎用于将 javascript 代码转换为计算机可以执行的机器码;

而libuv则负责完成异步IO、与操作系统交互(文件系统和网络模块)、事件循环、线程池等等。

Node还有其它模块:

  • http-parser:用于解析http;
  • c-ares:用于处理DNS请求;
  • OpenSSL:用于加密和安全编程;
  • zlib:与压缩有关。

总而言之,NodeJS相当于Javascript和操作系统之间的一个抽象层,为开发人员提供了API,使得开发人员可以编写纯JavaScript代码来操控操作系统。

NodeJS的特点

  • 单线程,基于事件驱动的非阻塞IO模型,使得NodeJS非常轻量级和高效;
  • 适用于需要快速且可扩展的数据密集型Web应用程序,例如:
    • 带有数据库的API(最好是像MongoDB这样的NoSQL数据库);
    • 数据流式传输
    • 实时聊天应用
    • 服务端Web应用(例如使用Pug这种模板引擎的模式)
  • 不适用于CPU密集型的服务端处理任务,例如图像处理、视频转换、文件压缩等等。

NodeJS程序的工作流程

NodeJS工作在单线程中,在开发后端服务的时候,应该时刻注意不要阻塞这个线程。

在启动NodeJS进程之后,主线程的工作流程如下:

  1. 初始化程序 initialize program
  2. 执行顶层代码 execute top-level code
  3. 获取依赖模块 require modules
  4. 注册回调事件 register event callbacks
  5. 启动事件循环 start event loop

事件循环是整个Node应用的核心,只能应付简单的任务,开销较大的任务不能在事件循环中执行,否则会阻塞主线程(对于后端服务来说是致命的)。

开销较大的任务实际上会被卸载到libuv提供的线程池中执行,线程池默认的数量是4,可以配置,至多128个。(配置项是process.env.UV_THREADPOLL_SIZE

如何卸载任务和卸载哪些任务到线程池是由Node处理的,与开发人员无关。会被卸载到线程池执行的任务是计算开销比较大的,比如:

  1. 文件系统API
  2. 与密码学相关的API
  3. 与压缩有关的操作
  4. DNS查询

事件循环

Node中的事件循环分为多个阶段,每个阶段都有对应的回调队列,每次到达一个阶段,会执行队列里的任务。

当队列被清空或者执行一定数量的任务后,则会进入下一个阶段。

具体流程如下:

详细的总结可以看官方文档:Node.js — The Node.js Event Loop (nodejs.org)

或者可以看我之前写的一篇博客:[NodeJS] NodeJS事件循环 - feixianxing - 博客园 (cnblogs.com)

graph TD; start-->timers; timers==>pending[pending callbacks]; pending==>idle[idle, prepare]; idle==>poll[poll callbacks]; poll==>check; check==>close[close callbacks]; close==>if[Any pending timers or I/O tasks?]; if-->|YES|timers; if-->|NO|exit[exit program]; incoming[incoming: connections, data, etc.]-.->poll

对于刚开始尝试使用NodeJS开发后端应用的前端开发人员来说,使用NodeJS编写服务端代码有以下注意事项:

  1. 在回调函数中使用fscryptozlib的API都应该使用异步的,因为这时已经进入事件循环阶段了,不能阻塞;而在顶层同步代码的范围内,则可以根据情况选择同步或者异步API。

    案例:有一个接口,它的响应与一个较大的文件有关,而这个文件的内容是不变的。假如每次请求都去异步地读取这个文件内容,然后再返回的话,其实很耗费时间,可以考虑在启动服务器的时候就同步/异步的读取文件内容保存到一个变量里,后续每次请求只需要拿变量的数据就OK了(其实就是做了个缓存,不需要每次请求都去磁盘找内容,在服务启动的时候就把内容先读到内存了)。

  2. 不要执行复杂的计算(即时间复杂度高的算法);

  3. 谨慎处理复杂对象的JSON序列化;

  4. 不要使用复杂的正则表达式;

  5. 将耗时任务卸载给线程池或者使用child.processes

总结:核心思想就是不要阻塞主线程,因为来自所有用户的所有请求都通过主线程处理,一旦阻塞基本上服务就废了。

事件驱动架构

NodeJS提供了一个events模块,其中有一个类是EventEmitter,这个类是NodeJS事件驱动架构的核心,很多其它NodeJS的核心类都继承了这个类。

http模块中的 server 就是继承了EventEmitter,因此有相关的 on 方法:

const server = http.createServer();
server.on('request', (req, res)=>{
 console.log('Request received');
 res.end('Request received');
});

EventEmitter是基于发布/订阅模式设计的:

  • 发布者Emitters通过emit方法发布指定名称的事件;
  • 订阅者Listeners通过on方法订阅指定名称的事件并注册回调函数。

简单地介绍发布/订阅模式:发布者和订阅者之间是松散耦合的,它们互相不知道对方的存在,仅通过中间的消息代理进行通信。

EventEmitter使用指南

  1. 限制监听器数量:一个事件如果有太多监听器会占用大量内存。可以使用 setMaxListeners 方法来控制最大监听器数量。

    const EventEmitter = require('events');
    const emitter = new EventEmitter();
    emitter.setMaxListeners(10); // 设置最大监听器数量为10
    
  2. 正确处理错误:始终提供错误监听器以捕获和优雅地处理错误;如果没有监听错误事件,emit('error')会抛出异常,并打印调用堆栈,结束进程。

  3. 移除不再使用的监听器:在不再需要时清理监听器,以释放资源。

  4. 使用描述性的事件名称:使用有意义且描述性的事件名称,使代码更加可读和易于维护。

参考

[1] B站 NodeJS 教学视频
[2] Events | Node.js v22.4.0 Documentation
[3] Node.js——The Node.js Event Loop
[4] [NodeJS] NodeJS事件循环 - feixianxing - 博客园
[5] geeks for geeks: What is EventEmitter in Node.js ?

标签:Node,NodeJS,EventEmitter,简记,线程,事件,监听器,原理
From: https://www.cnblogs.com/feixianxing/p/18289745/nodejs-v8-libuv-eventloop-eventemitter-

相关文章

  • AI人工智能写歌词的工作原理是什么?
    人工智能写歌词的原理主要基于自然语言处理和生成模型的技术,首先,收集大量的歌词文本数据,接下来,采用神经网络等机器学习技术,利用收集到的大量歌词数据对模型进行训练,在训练过程中,模型将学习歌词的语言特征,包括韵律、押韵、情感表达等,一旦训练完成,模型就可以根据输入的提示生成新的......
  • ModernWMS - 仓库管理系统简记
    ModernWMS-仓库管理系统简记 https://gitee.com/modernwms/ModernWMSZIP包全部下载下来,backend是后端,NET7开发,frontend是前端,用vue开发vs打开后端的sln项目,program.cs中定义了端口,运行后浏览器输入http://localhost:5555即可看到swagger界面,后端全是API接口cmd命令行下安......
  • MyBatis中二级缓存的配置与实现原理
    大家好,我是王有志,一个分享硬核Java技术的金融摸鱼侠,欢迎大家加入Java人自己的交流群“共同富裕的 Java 人”。上一篇文章《MyBatis中一级缓存的配置与实现原理》中,我们已经掌握了MyBatis一级缓存的配置(虽然根本不需要做什么配置)与原理,那么今天我们就来学习MyBat......
  • 昇思25天学习打卡营第11天 | LLM原理和实践:基于MindSpore实现BERT对话情绪识别
    1.基于MindSpore实现BERT对话情绪识别1.1环境配置#实验环境已经预装了mindspore==2.2.14,如需更换mindspore版本,可更改下面mindspore的版本号!pipuninstallmindspore-y!pipinstall-ihttps://pypi.mirrors.ustc.edu.cn/simplemindspore==2.2.14#该案例在min......
  • 硬件开发笔记(二十三):贴片电阻的类别、封装介绍,AD21导入贴片电阻原理图封装库3D模型
    前言  电阻,电容,电感还有各种基础的电子元器件、连接器和IC构成了各种实现功能的电子电路。  本篇介绍贴片电阻,并将贴片电阻封装导入AD21,预览其三维模型。 贴片电阻    贴片电阻(SMDResistor)作为一种不可或缺的电子元件,广泛应用于各种电路和设备中。其体积......
  • 灰色预测GM(1,1)模型的理论原理
    灰色预测是对时间有关的灰色过程进行预测。通过建立相应的微分方程模型,从而预测事物未来发展趋势的状况。由于笔者的水平不足,本章只是概括性地介绍GM(1,1)模型的理论原理,便于对初学者的初步理解目录一、灰色系统二、GM(1,1)灰色预测模型1.生成累加数据与紧临均值生成序列2.建立预测......
  • Optimize-Volume 命令用于优化指定驱动器的性能。除了 -Defrag 参数以外,还有一些其他
    Optimize-Volume命令起源于Microsoft的PowerShell环境中的一个磁盘优化工具。它主要用于对磁盘驱动器执行优化操作,包括碎片整理、TRIM操作(针对固态硬盘)、分块整理等。这些操作有助于提高磁盘性能和延长硬件寿命,特别是对于使用频繁的系统和数据驱动器来说尤为重要。在Power......
  • JWT原理
    JWTJWT(jsonwebtoken)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如用在用户登录上。因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算......
  • Java信号量semaphore的原理与使用方法
    Semaphore的基本概念在Java中,Semaphore是位于java.util.concurrent包下的一个类。它的核心就是维护了一个许可集。简单来说,就是有一定数量的许可,线程需要先获取到许可,才能执行,执行完毕后再释放许可。那么,这个许可是什么呢?其实,你可以把它想象成是对资源的访问权。比如,有5个......
  • .NET开源商城CoreShop简记
    .NET开源商城CoreShop简记大概本地运行起来了,官网https://www.coreshop.cn/先附加SQLSERVER库,运行redis,vs打开后项目9.APP下的admin是后台,api是接口,二个项目里的Appsetting.json里的数据库连接字符串和redis的字符串都得改,redis运行时默认密码是空的,然后api的那个跨域也设......