千军万马:NodeJS 并发指北

80 阅读3分钟

Nodejs有多重并发能力

  1. 单线程异步
  2. 多线程
  3. 多进程

非阻塞异步IO

单线程,不利于执行CPU密集型任务

Nodejs 的IO是非阻塞的,发出IO后交给系统线程池去执行IO,Nodejs继续执行其他代码,所以能够实现非阻塞的异步IO

当IO完成后,通过事件机制通知到时间循环的 Poll 阶段去执行回调 (nodejs通过libuv获取系统通知)

所以使用Nodejs执行并发IO的操作的时候我们应该考虑的是下游的承载能力,通过并发池来控制并发数

const fs = require('fs');

function readFileAsync() {
  fs.readFile('file.txt', 'utf8', (err, data) => {
    if (err) throw err;
    console.log(data);
  });
}

// 可以在读取文件的同时执行其他操作
readFileAsync();
console.log('继续执行其他任务');

子进程 Child Process

多进程(cluster & child process) 必须使用IPC通信

而且创建进程也会有性能损耗

子进程可以利用其他CPU核心

const { fork } = require('child_process');const child = fork('./child.js');
child.send('start');
child.on('message', (msg) => {console.log('Received from child:', msg);});

多进程 Cluster

计算机资源调度和分配的基本单位,无法共享内容,需要通过消息通信

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

if (cluster.isMaster) {
  console.log(`Master ${process.pid} is running`);

  // Fork workers
  for (let i = 0; i < 4; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`Worker ${worker.process.pid} died`);
  });
} else {
  console.log(`Worker ${process.pid} is running`);

  http.createServer((req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Hello, World!\n');
  }).listen(3000);
}

由于多进程是独立的内存环境,所以还能够防止整个程序崩溃

多线程 Worker Threads

进程中的一个执行单元,可以共享进程内的内存

直接通过原子操作共享内存,需要注意线程安全问题

const { Worker, isMainThread, parentPort } = require('worker_threads');

if (isMainThread) {
  const worker = new Worker(__filename);
  worker.on('message', (message) => {
    console.log('Received from worker:', message);
  });
  worker.postMessage('Hello, worker!');
} else {
  parentPort.on('message', (message) => {
    console.log('Received from main:', message);
    parentPort.postMessage('Hello, main thread!');
  });
}

aysnc_hook 异步追踪

  1. createHook 方法

较底层的方法,为每个异步任务配置回调钩子

  1. AsyncLocalStorage 方法

利用TLS(threal local storage)的思想在异步任务维度做本地存储以解决闭包问题

  1. AsyncResource 类 asyncLoaclStorage & AsyncResource.runInAsyncScope 方法

利用runInAsyncScope 复现异步当前类的Callback环境

runInAsyncScope是同步的,其中的异步函数也能够获得正确store数据

CommonJS & ES Module

  • CommonJS (cjs)
  1. 同步执行,导出拷贝
  2. 随处可用
  3. 循环引用会导致依赖不可靠
  4. exports module.exports require
  • ES Module (mjs)
  1. 异步执行,导出引用

  2. 在文件顶部使用

  3. 静态扫描提前发现循环应用问题

  4. export export default import

多进程和多线程

JS代码的执行是单线程异步非阻滞的

  • child_process 子进程

执行命令/调用子进程

spawn exec execFile fork ****(创建nodejs子进程)

  • cluster 集群多进程

相比fork是更加高级和抽象的封装

通过IPC通信复用主进程HTTP端口服务

内置简单的负载均衡

  • worker_threads 多线程

BroadcastChannle 多线程共享信息

可以通过序列化消息通信或者内存共享(传引用)

流和网络

Stream : 可读流可写流双工流

Protocol: TCP , UDP , HTTP , SOCKET ...

异步策略收益

  • 剥离长耗时,重资源任务,降低请求延迟

  • 缓冲大量突发性请求,削峰

  • 节约成本

  • 有利于完善重试和错误处理

  • 将异步任务并行执行,提高速度

  • 更好的任务优先级管控和流控

  • 多样的任务触发方式

  • 更好的观测性,异步任务有利于提供任务日志,指标,状态查询等

  • 更高的研发效率,专注于处理逻辑的实现(Serverless思想)

Node Inspector

作用: 调试nodejs代码,收集Nodejs进程的Heap,Snapshot,CPU Profile等

协程coroutine与纤程fiber

协程和纤程都是线程下的更小的单位,可以看作是用户态的线程

  • 线程的调度由操作系统调度,是抢占式的
  • 协程和纤程的调度有代码显示控制,再JS中生成器就是一个典型的利用了协程实现的异步模型,async await 其实就是利用生成器创建的语法糖

笔者才疏学浅,请多多指教。 部分插图来自网络,侵删。