Nodejs的内部结构LibUV的详细介绍

125 阅读5分钟

大家好,今天我们要讲的是Nodejs的内部结构。本文将介绍Node js的工作原理以及如何处理异步任务。如果一次来了十个请求,会发生什么?它是处理一个请求并丢弃其他9个请求呢,还是创建一个队列并逐一服务。我们将在本课和下一课回答所有这些问题。让我们开始吧。

Nodejs背后的主要引擎LibUV

LibUV是支持Nodejs的核心引擎。LibUV提供对异步I/O操作的支持。它是一个基于C的库,主要为Nodejs创建,并被Luvit、Julia、pyuv和其他一些软件使用。

LibUV强制执行异步的、事件驱动的编程风格。它使用事件循环来有效地处理异步任务。它还支持非阻塞网络支持,异步文件系统访问,等等。

通过 "事件驱动 "编程,我们的意思是它观察事件的发生,然后在事件发生时处理它们。正如我们在前面的事件发射器课程中看到的,我们创建了一个事件监听器,他在 "听 "事件。然后我们写了一个处理函数,当那个特定的事件发生时,它被调用。对系统中发生的事件的监控是由LibUV使用我们称之为Event Loop的东西来管理的。这个事件循环通常会一直运行下去。

LibUV的职责是跨平台的输入输出操作,处理文件和网络,并支持事件循环。你自己不会与这些互动。不过,掌握这些知识对更好地理解Nodejs的工作还是有好处的。我们将在下一课中学习事件循环,所以我们现在就来了解一下其他的。

LibUV中的几个基本概念

为了更好地理解LibUV,我们必须首先理解以下三个概念,然后通过牢记这三个概念来了解更多关于LibUV的内容。

LibUV提供了两个抽象的工作:手柄和请求:

  1. 手柄:手柄代表了能够在活动时执行某些操作的长寿命对象。当它完成工作时,句柄将调用相应的回调。只要一个句柄处于活动状态,事件循环就会继续运行。句柄的一些例子是TCP服务器,它在每次有新的连接时都会被调用其连接回调,定时器,信号和子进程。
  2. 请求:短暂操作的抽象。与被视为对象的句柄相反,请求可以被认为是函数或方法。请求被用来在句柄上写入数据。像句柄一样,活跃的请求也将保持事件循环的活力。LibUV的另一个基本概念是线程池。LibUV将所有繁重的工作委托给一个工作线程池。
  3. 线程池:线程池负责文件的I/O和DNS查询。然而,所有的回调都在主线程上执行。从Node 10.5开始,工作线程也可以被程序员用来并行运行Javascript。

关于在LibUV中使用线程的更多信息

libuv模块有一个责任,与标准库中的一些特殊函数有关。对于一些标准库的函数调用,节点C++方和libuv决定完全在事件循环之外进行昂贵的计算。LibUV创建了一个叫做线程池的东西。这个线程池默认由四个线程组成。这些线程可以用来运行计算密集型的任务,如散列函数。许多包含在标准节点库中的函数将自动利用这个线程池。

如果你有太多的函数调用,它将使用所有的核心。CPU内核实际上并没有加快处理函数调用的速度。它们允许在你所做的工作中存在一定的并发性。

libuv并不对异步任务使用线程,而是对那些本质上不是异步的任务使用。举例来说,它不使用线程来处理套接字。它使用线程来使同步的fs调用成为异步的。

I/O(或事件)循环是libuv的核心部分。它为所有的I/O操作建立了内容,而且它是与一个线程联系在一起的。一个人可以运行多个事件循环,只要每个循环在不同的线程中运行。

LibUV帮助处理:

  1. 文件I/O包括文件观察、文件系统操作等
  2. 子进程(Node中的子进程模块)。
  3. 一个工人线程池来处理阻塞的I/O任务
  4. 线程的同步化基元。

让我们以启动这类服务器为例,看看发生了什么:

const http = require('http');
const fs = require('fs');
 
const server = new http.Server();

server.on('request', (req, info) => {
 if (req.url == '/') {
     fs.readFile('index.html', function(err, info) {
         if(err) {
             console.error(err);
             res.statusCode = 500;
             res.end("A server error occurred ");
             return
         }

         res.end("info");
     });

 } else { /*404 */ }
});
 
server.listen(3000);

首先,JavaScript被激活,它连接了各个模块:

const http = require('http');  
const fs = require('fs');

并创建一个对象

const server = new http.createServer();

它给出了一个处理程序:

server.on('request', (err, info) => {});

在这一点上,函数内部是什么并不重要,因为处理程序还没有被激活。最后一个字符串是对 "listen "命令的调用:

server.listen(3000);

到了Node.js,该命令通过其C++代码,成为一个内部方法调用TCPWrap::listen。这个内部方法调用uv__listen,它执行了整个工作:

LibUV working

LibUV工作

根据操作系统的不同,它在这个端口上挂上一个连接处理程序。例如,Unix系统需要一个系统调用"listen"。所以,LibUV为这个端口指定了一个连接处理程序。这个动作的结果在链中得到了向上的体现。今天就到此为止。我们将在下一课中学习事件循环。