chapter3 node.js基础服务器搭建与机制剖析

在传统的动态编程语言(如php)中,需要依赖外部的服务器来进行请求的接收,但在node.js中,则无需担心这个问题,node.js本身内置了一个http服务器,开发者可以直接使用node的模块来进行服务器的创建而无需依赖外部的服务器。

初识node应用

在之前的学习中,我们通过node运行了一个js程序,这实际上只是一个脚本,而非规范的node应用,实际上,一个规范的node应用包含以下几部分:

  • require 指令:在 Node.js 中,使用 require 指令来加载和引入模块,引入的模块可以是内置模块,也可以是第三方模块或自定义模块。
  • **创建服务器:服务器可以监听客户端的请求,类似于 Apache 、Nginx 等 HTTP 服务器。
  • 接收请求与响应请求 服务器很容易创建,客户端可以使用浏览器或终端发送 HTTP 请求,服务器接收请求后返回响应数据。

require 指令

require指令用于加载不同的模块进行开发,其基础语法为:

const http=require('http')//加载http模块并赋给一个http对象

这样会将一个模块进行加载并赋给http变量

基础服务器的创建

在加载完http模块后,我们调用其createServer方法创建一个基础的服务器并监听8888端口:

var http = require('http');
//创建服务器,监听8088端口
http.createServer(function (request, response) {
//请求时的回调
        // 发送 HTTP 头部 
        // HTTP 状态值: 200 : OK
        // 内容类型: text/plain
        response.writeHead(200, {'Content-Type': 'text/plain'});

        // 发送响应数据 "Hello node"
        response.end('Hello node!!\n');
}).listen(8888);
// 终端打印如下信息
console.log('Server running at http://127.0.0.1:8888/');

访问地址后,应该可以看到一个hello node的输出。
下面,我们来了解一些node.js相关的工作机制与相关架构。

Node.js 的工作机制

核心特点

node.js工作机制的核心特点是:单线程、事件循环、非阻塞I/O、跨平台

核心架构组成

Node.js 通过 V8 引擎执行 JavaScript 代码,使用 Node.js API 与操作系统交互,并通过 Libuv 处理异步 I/O 操作。事件循环和工作线程确保了 Node.js 的高效和非阻塞特性。

  • V8 JavaScript Engine:这是 Node.js 的核心,负责执行 JavaScript 代码。V8 是 Chrome 浏览器的 JavaScript 引擎,它将 JavaScript 代码编译成机器码以提高执行效率。
  • Node.js Bindings (Node API):这一层提供了一组 API,允许 JavaScript 代码与操作系统进行交互。这些 API 包括文件系统、网络、进程等操作。
  • Libuv (Asynchronous I/O):Libuv 是一个跨平台的异步 I/O 库,它在 Node.js 下运行,用于处理文件系统、网络和进程等异步操作。Libuv 使用事件循环和工作线程来处理这些操作,而不会阻塞主线程。
  • Event Loop:这是 Node.js 的核心概念之一。事件循环不断检查事件队列,处理事件和执行回调函数。它确保了 Node.js 的非阻塞和事件驱动的特性。
  • Event Queue:事件队列用于存储即将处理的事件。当一个异步操作完成时,相关的回调函数会被放入事件队列中,等待事件循环处理
  • Worker Threads:这些是用于处理阻塞操作的线程,如文件读写、网络请求等。它们允许 Node.js 在不阻塞主线程的情况下执行这些操作。
  • Blocking Operation:这些是可能阻塞线程的操作,如同步的文件读写。在 Node.js 中,这些操作通常被放在工作线程中执行,以避免阻塞事件循环。
  • Execute Callback:一旦一个异步操作完成,它的回调函数就会被执行。这是通过事件循环来管理的。
    简而言之,Node.js通过V8引擎来执行Js代码,Libuv 管理异步 I/O,libuv中又通过事件循环与工作线程实现高性能、非阻塞的事件驱动架构。

node.js架构分层

node.js可以分为javascript层、C++绑定层、依赖层:

javascript层

面向开发者的层面,包括:

  • 核心模块:目录操作fs、路径操作path、网络通信http等等
  • npm提供的第三方模块
  • 开发者本地开发的模块

c++绑定层

这一层将底层功能暴露给 JavaScript 层,包括:

  • node API:- Node.js 核心 API 的 C++ 实现
  • V8接口的封装

依赖层

这一层是最底层的依赖:

  • V8引擎:用于解释运行javascript的引擎
  • libuv:异步I/O库
  • c-ares:异步 DNS 解析库
  • zlib:压缩功能支持
  • openssl:加密支持

事件循环

node.js一个比较核心的工作机制就是它的事件循环,它带来了良好的异步操作调度,它有以下几个阶段:

  1. timers 阶段:处理setimtout与setinterval定时器的回调,到达预设时间后,相关回调会进入该阶段执行。
  2. pending callbacks 阶段:执行某些系统操作(如 TCP 错误类型)的回调。
  3. idle, prepare 阶段:Node.js 内部使用,供系统做一些准备工作
  4. poll 阶段:执行I/O相关操作的回调,如果 poll 队列不为空,则同步地执行回调,直到队列为空或达到系统限制,队列为空时会根据定时器到期与其他情况进行阶段的切换。
  5. check 阶段:执行 setImmediate() 注册的回调。
  6. close callbacks 阶段:执行事件关闭的回调

非阻塞 I/O 原理

node.js的I/O效率非常高,这是因为采用了非阻塞I/O的设计,它的流程如下:

  1. 应用发起I/O的请求
  2. node.js将请求给底层的libuv进行处理
  3. libuv使用系统提供的异步接口进行处理
  4. 主线程继续执行其他的任务
  5. I/O 完成后,回调函数被放入事件队列
  6. 事件循环在适当阶段执行回调

工作机制带来的缺点

node.js的这一套工作机制与I/O模型提供了极高的高并发能力,但也带来了一些缺点:

  1. 单线程限制:所有 JavaScript 代码在主线程上运行,一旦出现cpu密集型计算任务(机器学习,大规模计算),事件循环会被阻塞,影响其他请求的处理。
  2. 异步编程导致回调嵌套:由于采用异步编程,在回调时可能会出现回调地狱,影响维护与错误排查。
  3. 多核利用率不足:node.js默认是单线程,无法自动利用多核 CPU,需要通过 clusterworker_threads 手动实现多进程/线程调度,增加开发复杂度。

单线程限制的优化

在上面我们提到了node.js是单线程的,这意味这它无法良好的利用多核性能,但也提供了一些方法来利用多核cpu:

worker threads(推荐)

node.js从v10.5起引入的特性,真正的创建多个线程来并行处理任务:

// main.js
const { Worker } = require('worker_threads');

const worker = new Worker('./worker.js', {
  workerData: { number: 42 }
});

worker.on('message', msg => console.log('来自子线程的结果:', msg));
worker.on('error', err => console.error('子线程报错:', err));
worker.on('exit', code => console.log('子线程退出,code:', code));
// worker.js
const { parentPort, workerData } = require('worker_threads');

// 假设是个耗时计算
let result = workerData.number * 2;

parentPort.postMessage(result);

Child Process(创建子进程)

使用 child_process 模块可以创建新的 Node.js 进程(不是线程),适合进行 多进程并行任务:

// main.js
const { fork } = require('child_process');

const child = fork('child.js');

child.send({ number: 100 });

child.on('message', (msg) => {
  console.log('主进程收到:', msg);
});

// child.js
process.on('message', (data) => {
  let result = data.number + 1;
  process.send({ result });
});

Cluster 模块(主进程 + 多个工作进程)

利用 cluster 模块可以创建多个 Node.js 进程,共享服务器端口,实现类似多线程的服务负载:

// cluster_server.js
const cluster = require('cluster');
const http = require('http');
const os = require('os');

if (cluster.isPrimary) {
  const numCPUs = os.cpus().length;
  console.log(`主进程 ${process.pid} 正在运行`);

  // 创建多个工作进程
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker) => {
    console.log(`工作进程 ${worker.process.pid} 已退出`);
  });
} else {
  http.createServer((req, res) => {
    res.end(`来自工作进程 ${process.pid}`);
  }).listen(3000);

  console.log(`工作进程 ${process.pid} 启动`);
}

// cluster_server.js
const cluster = require('cluster');
const http = require('http');
const os = require('os');

if (cluster.isPrimary) {
  const numCPUs = os.cpus().length;
  console.log(`主进程 ${process.pid} 正在运行`);

  // 创建多个工作进程
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker) => {
    console.log(`工作进程 ${worker.process.pid} 已退出`);
  });
} else {
  http.createServer((req, res) => {
    res.end(`来自工作进程 ${process.pid}`);
  }).listen(3000);

  console.log(`工作进程 ${process.pid} 启动`);
}

posted @ 2025-07-30 22:44  fhyxz1  阅读(14)  评论(0)    收藏  举报