• 说说Nodejs高并发的原理


    导读

    ALL THE TIME,我们写的的大部分javascript代码都是在浏览器环境下编译运行的,因此可能我们对浏览器的事件循环机制了解比Node.JS的事件循环更深入一些,但是最近写开始深入NodeJS学习的时候,发现NodeJS的事件循环机制和浏览器端有很大的区别,特此记录来深入的学习了下,以帮助自己及小伙伴们忘记后查阅及理解。

    在这里插入图片描述

    什么是事件循环

    首先我们需要了解一下最基础的一些东西,比如这个事件循环,事件循环是指Node.js执行非阻塞I/O操作,尽管JavaScript是单线程的,但由于大多数内核都是多线程的,Node.js会尽可能将操作装载到系统内核。因此它们可以处理在后台执行的多个操作。当其中一个操作完成时,内核会告诉Node.js,以便Node.js可以将相应的回调添加到轮询队列中以最终执行。

    当Node.js启动时会初始化event loop, 每一个event loop都会包含按如下顺序六个循环阶段:

       ┌───────────────────────┐
    ┌─>│        timers         │
    │  └──────────┬────────────┘
    │  ┌──────────┴────────────┐
    │  │     I/O callbacks     │
    │  └──────────┬────────────┘
    │  ┌──────────┴────────────┐
    │  │     idle, prepare     │
    │  └──────────┬────────────┘      ┌───────────────┐
    │  ┌──────────┴────────────┐      │   incoming:   │
    │  │         poll          │<─────┤  connections, │
    │  └──────────┬────────────┘      │   data, etc.  │
    │  ┌──────────┴────────────┐      └───────────────┘
    │  │        check          │
    │  └──────────┬────────────┘
    │  ┌──────────┴────────────┐
    └──┤    close callbacks    │
       └───────────────────────┘
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 1. timers 阶段: 这个阶段执行 setTimeout(callback)setInterval(callback) 预定的 callback;
    • 2. I/O callbacks 阶段: 此阶段执行某些系统操作的回调,例如TCP错误的类型。 例如,如果TCP套接字在尝试连接时收到 ECONNREFUSED,则某些* nix系统希望等待报告错误。 这将操作将等待在I/O回调阶段执行;
    • 3. idle, prepare 阶段: 仅node内部使用;
    • 4. poll 阶段: 获取新的I/O事件, 例如操作读取文件等等,适当的条件下node将阻塞在这里;
    • 5. check 阶段: 执行 setImmediate() 设定的callbacks;
    • 6. close callbacks 阶段: 比如 socket.on(‘close’, callback) 的callback会在这个阶段执行;

    事件循环详解

    在这里插入图片描述

    这个图是整个 Node.js 的运行原理,从左到右,从上到下,Node.js 被分为了四层,分别是 应用层V8引擎层Node API层LIBUV层

    • 应用层: 即 JavaScript 交互层,常见的就是 Node.js 的模块,比如 http,fs
    • V8引擎层: 即利用 V8 引擎来解析JavaScript 语法,进而和下层 API 交互
    • NodeAPI层: 为上层模块提供系统调用,一般是由 C 语言来实现,和操作系统进行交互 。
    • LIBUV层: 是跨平台的底层封装,实现了 事件循环、文件操作等,是 Node.js 实现异步的核心 。

    参考nodejs进阶视频讲解:进入学习

    每个循环阶段内容详解

    timers阶段 一个timer指定一个下限时间而不是准确时间,在达到这个下限时间后执行回调。在指定时间过后,timers会尽可能早地执行回调,但系统调度或者其它回调的执行可能会延迟它们。

    • 注意:技术上来说,poll 阶段控制 timers 什么时候执行。

    • 注意:这个下限时间有个范围:[1, 2147483647],如果设定的时间不在这个范围,将被设置为1。

    I/O callbacks阶段 这个阶段执行一些系统操作的回调。比如TCP错误,如一个TCP socket在想要连接时收到ECONNREFUSED,
    类unix系统会等待以报告错误,这就会放到 I/O callbacks 阶段的队列执行.
    名字会让人误解为执行I/O回调处理程序, 实际上I/O回调会由poll阶段处理.

    poll阶段 poll 阶段有两个主要功能:(1)执行下限时间已经达到的timers的回调,(2)然后处理 poll 队列里的事件。
    当event loop进入 poll 阶段,并且 没有设定的 timers(there are no timers scheduled),会发生下面两件事之一:

    • 如果 poll 队列不空,event loop会遍历队列并同步执行回调,直到队列清空或执行的回调数到达系统上限;

    • 如果 poll 队列为空,则发生以下两件事之一:

      • 如果代码已经被setImmediate()设定了回调, event loop将结束 poll 阶段进入 check 阶段来执行 check 队列(里面的回调 callback)。
      • 如果代码没有被setImmediate()设定回调,event loop将阻塞在该阶段等待回调被加入 poll 队列,并立即执行。
    • 但是,当event loop进入 poll 阶段,并且 有设定的timers,一旦 poll 队列为空(poll 阶段空闲状态):
      event loop将检查timers,如果有1个或多个timers的下限时间已经到达,event loop将绕回 timers 阶段,并执行 timer 队列。

    check阶段 这个阶段允许在 poll 阶段结束后立即执行回调。如果 poll 阶段空闲,并且有被setImmediate()设定的回调,event loop会转到 check 阶段而不是继续等待。

    • setImmediate() 实际上是一个特殊的timer,跑在event loop中一个独立的阶段。它使用libuv的API
      来设定在 poll 阶段结束后立即执行回调。

    • 通常上来讲,随着代码执行,event loop终将进入 poll 阶段,在这个阶段等待 incoming connection, request 等等。但是,只要有被setImmediate()设定了回调,一旦 poll 阶段空闲,那么程序将结束 poll 阶段并进入 check 阶段,而不是继续等待 poll 事件们 (poll events)。

    close callbacks 阶段 如果一个 socket 或 handle 被突然关掉(比如 socket.destroy()),close事件将在这个阶段被触发,否则将通过process.nextTick()触发

    这里呢,我们通过伪代码来说明一下,这个流程:

    // 事件循环本身相当于一个死循环,当代码开始执行的时候,事件循环就已经启动了
    // 然后顺序调用不同阶段的方法
    while(true){
       
    // timer阶段
        timer()
    // I/O callbacks阶段
        IO()
    // idle阶段
        IDLE()
    // poll阶段
        poll()
    // check阶段
        check()
    // close阶段
        close()
    }
    // 在一次循环中,当事件循环进入到某一阶段,加入进入到check阶段,突然timer阶段的事件就绪,也会等到当前这次循环结束,再去执行对应的timer阶段的回调函数 
    // 下面看这里例子
    const fs = require('fs')
    
    // timers阶段
    const startTime = Date.now();
    setTimeout(() => {
       
        const endTime = Date.now()
        console.log(`timers: ${
         endTime - startTime}`)
    }, 1000)
    
    // poll阶段(等待新的事件出现)
    const readFileStart =  Date.now();
    fs.readFile
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
  • 相关阅读:
    艺术与科技的狂欢,阿那亚2022砂之盒沉浸艺术季
    Linux 11:网络
    FFmpeg引入SDL扩展
    PMP考试提分必刷题
    组合控件——增强型列表——循环视图RecyclerView——布局管理器LayoutManager
    C--六、字符串
    【华为OD题库-028】数据分类-java
    【13】加法器:如何像搭乐高一样搭电路(上)?
    GBase 8c 安装部署手册 七
    如何使用Git和GitHub进行版本控制
  • 原文地址:https://blog.csdn.net/m0_67614517/article/details/127785227