node模块化原理


Node 异步单线程原理?(Nodejs如何实现高并发的)

  1. Node.js 实现异步的核心是事件,也就是说,它把每一个任务都当成 事件 来处理,然后通过 Event Loop 模拟了异步的效果
  2. 每个Node.js进程只有一个主线程在执行程序代码,形成一个执行栈(execution context stack)
  3. 主线程之外,还维护了一个”事件队列”(Event queue)。当用户的网络请求或者其它的异步操作到来时,node都会把它放到Event Queue之中,此时并不会立即执行它,代码也不会被阻塞,继续往下走,直到主线程代码执行完毕。
  4. 主线程代码执行完毕完成后,然后通过Event Loop,也就是事件循环机制,开始到Event Queue的开头取出第一个事件,从线程池中分配一个线程去执行这个事件,接下来继续取出第二个事件,再从线程池中分配一个线程去执行,然后第三个,第四个。主线程不断的检查事件队列中是否有未执行的事件,直到事件队列中所有事件都执行完了,此后每当有新的事件加入到事件队列中,都会通知主线程按顺序取出交EventLoop处理。当有事件执行完毕后,会通知主线程,主线程执行回调,线程归还给线程池。

nodejs与js有哪些不同?

  1. js是编程语言,node是js的运行环境,node内置了一些增强js编程的功能模块。
  2. js在浏览器客户端中运行,node在任何操作系统中都可以用
  3. js可以在不同浏览器引擎上运行,node只能在v8引擎运行

谈谈你对Node的理解?优缺点?应用场景

  • nodejs是一个开源与跨平台的js运行环境,在浏览器外运行v8js引擎,利用事件驱动、非阻塞和异步输入输出模型等技术提高性能,就是一个服务器端的、非阻塞式I/O的、事件驱动的js运行环境
  • 非阻塞异步:nodejs采用了非阻塞型的I/O机制,在做I/O操作的时候不会造成任何的阻塞,当完成之后,以时间的形式通知执行操作。
  • 事件驱动就是当进来一个新的请求时,请求将会进入一个事件队列中,然后通过一个循环来检测队列中的事件状态变化,如果检测到有状态变化的事件,那么就执行该事件对应的处理代码,一般都是回调函数
  • 优点:
    • 处理高并发场景性能更佳
    • 适合I/O密集型应用,指的是应用在运行极限时,CPU占用率仍然比较低,大部分时间是在做I/O硬盘内存存读写操作
  • 缺点:
    • 不适合CPU密集型应用,只支持单核CPU,不充分利用CPU,可靠性低,一旦代码某个环节崩溃,整个系统都崩溃
  • 应用场景:
    • 用户表单手机系统、后台管理系统实时交互系统、考试系统、联网软件
      高并发量的web程序
    • 基于web、canvas等多人联网游戏
    • 基于web的多人实时聊天客户端、聊天室、图文直播
    • 单页面应用程序
    • 操作数据库、为前端和移动端提供基于json的API

Node 如何多进程?

  • 我们都知道 Node.js 是以单线程的模式运行的,但它使用的是事件驱动来处理并发,这样有助于我们在多核 cpu 的系统上创建多个子进程,从而提高性能。

node.js中进程通信

  • 使用子进程模块:可以使用 Node.js 的子进程模块(child_process)来创建子进程,并使用进程间通信机制(如进程间管道)来实现通信。
  • 使用共享内存:Node.js 中的共享内存模块(sharedArrayBuffer)可以在多个进程间共享内存,从而实现进程间通信。
  • 使用进程间消息传递:Node.js 提供了一个内置的进程间通信机制,可以使用 process.send() 方法在不同的进程之间发送消息。
  • 使用进程间的 TCP 通信:可以使用 Node.js 的 net 模块建立 TCP 服务器和客户端,从而在不同的进程之间进行通信。

express实现原理

  • express的所有服务端逻辑处理都是通过中间件来实现的,中间件是一个函数,而app.use()方法就是去装载这些函数,并放入一个数组中。

  • 当前端一个请求传到服务器的时候,首先会经过request,然后是一系列的服务端处理,也就是中间件处理,存放于数组中的中间件采用后进先出的栈模式处理请求,最先入栈的中间件处理完请求之后,通过next将执行权交给第二个入栈的中间件,依次类推,直到数组末尾或者中间某个中间件没有调用next()函数,最后再将处理完的结果response回前端。


node的事件循环

  • Node.js 在主线程里维护了一个事件队列,当接到请求后,就将该请求作为一个事件放入这个队列中,然后继续接收其他请求。当主线程空闲时(没有请求接入时),就开始循环事件队列,检查队列中是否有要处理的事件,这时要分两种情况:如果是非 I/O 任务,就亲自处理,并通过回调函数返回到上层调用;如果是 I/O 任务,就从 线程池 中拿出一个线程来处理这个事件,并指定回调函数,然后继续循环队列中的其他事件。
  • 当线程中的 I/O 任务完成以后,就执行指定的回调函数,并把这个完成的事件放到事件队列的尾部,等待事件循环,当主线程再次循环到该事件时,就直接处理并返回给上层调用。 这个过程就叫 事件循环。

node的事件循环和浏览器的有什么区别吗?

  1. Node.js 的事件循环分为6个阶段
  2. 浏览器和Node 环境下,microtask 任务队列的执行时机不同
  3. Node.js中,microtask 在事件循环的各个阶段之间执行
  4. 浏览器端,microtask 在事件循环的 macrotask 执行完之后执行
  5. 递归的调用process.nextTick()会导致I/O starving,官方推荐使用setImmediate()

express 框架的核心特性

  1. 可以设置中间件来响应 HTTP 请求。
  2. 定义了路由表用于执行不同的 HTTP 请求动作。
  3. 可以通过向模板传递参数来动态渲染 HTML页面

express框架和Koa的区别

  • 编程模型不同:Express的中间件是线型的,Koa 的中间件是U型的(洋葱模型)
  • 对语言特性的使用不同:express使用回调函数next(),koa v2.x 使用async/await 语法

koa-body原理

  • koa-body中间件作用是将post等请求的请求体携带的数据解析到ctx.request.body中
  • 原理:先用type-is这个包判断出请求的数据类型,然后根据不同类型用co-body(请求体解析)和formidable(数据类型是multipart,文件上传解析)来解析,拿到解析结果以后放到ctx.request.body或者ctx.request.files里面

node中的require加载文件的顺序

  1. 如果some_module 是一个核心模块,直接加载,结束。
  2. 如果some_module以“ / ”、“ ./ ”或“ …/ ”开头,按路径加载 some_module,结束。
  3. 假设当前目录为 current_dir,按路径加载 current_dir/node_modules/some_module。
    • 如果加载成功,结束
    • 如果加载失败,令current_dir为其父目录。
    • 重复这一过程,直到遇到根目录,抛出异常,结束。

node模块分几类

内置模块、自定义模块、第三方模块

node模块加载机制(node模块加载的原理)

  • Node.js 中,模块加载过程分为 5 步:

    1. 路径解析(Resolution):根据模块标识找出对应模块(入口)文件的绝对路径
    2. 加载(Loading):如果是 JSON 或 JS 文件,就把文件内容读入内存。如果是内置的原生模块,将其共享库动态链接到当前 Node.js 进程
    3. 包装(Wrapping):将文件内容(JS 代码)包进一个函数,建立模块作用域,exports, require, module等作为参数注入
    4. 执行(Evaluation):传入参数,执行包装得到的函数
    5. 缓存(Caching):函数执行完毕后,将module缓存起来,并把module.exports作为require()的返回值返回
  • 路径解析的详细过程:(require加载文件的顺序)

    • 在加载模块时先去缓存中查找,如果为查找到再进行以下情况判断。
      1. 模块有路径但没有扩展名
        • 按照require传入的path找到对应的路径,根据.js、.json、.node的顺序为path中的文件名加上后缀进行文件查找,如果找到则返回。
        • 如果未找到,则会将path中的文件名视为目录名,找到其package.json文件,通过JSON.parse()解析出包描述对象,再取出main属性指定的文件名进行定位。如果文件缺少扩展名,将会进入扩展名分析的步骤。
        • 如果该目录下没有package.json或者main属性指定的文件名错误,则会将index当做默认的文件名,然后依次查找index.js、index.json、index.node,如果找到则返回,如果未找到则require方法会抛出异常。
      2. 模块有路径且有扩展名
        • 按照其path定位到对应的路径下,根据文件名查找对应的文件,如果找到则返回,如果未找到,require方法会抛出异常。
      3. 模块没有路径且没有扩展名
        • 先去查找核心模块,有就返回
        • 如果没有,去当前目录下的node_modules按照.js、.json、.node的顺序为模块名称加上扩展名然后去查找,如果找到则返回文件。
        • 否则将模块名称视为目录名称,在node_modules/目录名下查找package.json文件,通过JSON.parse()解析出包描述对象,再取出main属性指定的文件名进行定位。
        • 如果文件缺少扩展名,将会进入扩展名分析的步骤。如果该目录下没有package.json或者main属性指定的文件名错误,则会将index当做默认的文件名,然后依次查找index.js、index.json、index.node,如果找到则返回
        • 如果未找到,去父目录中的node_modules中继续找,直到根目录。
        • 如果到根目录还未找到文件,在全局目录查找,根据操作系统的环境变量中设置NODE_PATH变量
      4. 模块没有路径且有扩展名
        • 去当前目录的node_modules下查找该文件名,如果有则返回
        • 如果没有则去父目录中的node_modules下查找,直至到根目录,如果仍旧未找到,则会去全局目录下查找,依旧未找到则require方法会抛出异常。

请介绍你对Nodejs 异步I/O的理解

  • node的异步I/O不等于非阻塞I/O。非阻塞I/O调用后会虽然也是立即返回,但是应用层会不断的重复I/O操作去轮询系统是否完成数据读取,让CPU处理状态判断,对CPU造成资源的浪费。然而在异步I/O里,基于多子线程的方式去解决了非阻塞I/O的问题,应用层(主线程)发起I/O请求后,就不再过问情况了。然后让子线程来完成数据获取,当读写完成后通知主线程。

说一下你对 generator 的了解?

  1. 概念:生成器是一种特殊的函数,它使用function*语法进行定义。在生成器函数内部,可以使用yield关键字来暂停函数的执行,并返回一个包含value和done属性的对象。value表示yield表达式的值,done表示函数是否已经执行完毕。
  2. 作用:生成器提供了一种更灵活、更可控的方式来处理异步编程。通过使用yield关键字,我们可以在函数执行过程中暂停和恢复,并且可以将异步操作以同步方式编写和理解。
  3. 原理:当我们调用生成器函数时,实际上并不会立即执行函数体内部的代码。而是返回一个迭代器对象,该迭代器对象实现了next()方法。每次调用next()方法时,生成器会从上一次暂停的位置继续执行代码,直到遇到下一个yield关键字或者函数结束。

generator的原理

  • 当我们调用生成器函数时,实际上并不会立即执行函数体内部的代码。而是返回一个迭代器对象,该迭代器对象实现了next()方法。每次调用next()方法时,生成器会从上一次暂停的位置继续执行代码,直到遇到下一个yield关键字或者函数结束。

node 如何捕捉错误,内存泄漏怎么排查;

  • 捕获:
    • 同步代码使用try catch
    • 异步代码基本都提供了回调函数来处理错误
    • 使用process模块,因为异常并不是事先准备好的,不能控制其到底在哪儿发生,所以我们可以通过监听应用进程的错误异常,从而捕获到不能预料的错误异常,保证应用不至于奔溃。
  • 排查:可以使用第三方工具memwatch、heapdump,可以分析到内存泄漏的位置。

node线程模型

node线程模型由于两部分组成:1个主线程,一个线程池。主线程内维护了一个事件循环,负责监听网络请求,建立连接、处理请求。如果请求不涉及IO操作,主线程处理请求,并把结果直接返回给客户端;如果请求涉及到IO操作,则把请求交给线程池处理,线程池处理完毕后通知主线程,主线程再把结果返回给客户端。


koa洋葱模型

  • 概念:Koa的洋葱模型是以next()函数为分割点,先由外到内执行Request的逻辑,然后再由内到外执行Response的逻辑,洋葱模型的核心原理主要是借助compose方法。
  • 好处:
    • 更好地封装和复用代码逻辑,每个中间件只需要关注自己的功能。
    • 更清晰的程序逻辑,通过中间件的嵌套可以表明代码的执行顺序。
    • 更好的错误处理,每个中间件可以选择捕获错误或将错误传递给外层。
    • 更高的扩展性,可以很容易地在中间件栈中添加或删除中间件。
  • Koa的洋葱圈模型主要是通过Generator函数和Koa Context对象来实现的。
  • 原理:通过compose函数来组合中间件,实现洋葱圈模型。compose接收一个中间件数组作为参数,执行数组中的中间件,返回一个可以执行所有中间件的函数。

如何封装中间件

在进入具体的业务处理之前,先让过滤器处理


介绍一下你对中间件的理解

  • 中间件就是一个中间流程。目的是为了处理公共的逻辑、业务

使⽤过的koa2中间件

  • koa-router: 提供了全面的路由功能
  • koa-bodyparser: 解析请求体的中间件
  • koa-views: 视图模板渲染中间件,支持ejs, nunjucks等众多模板引擎
  • koa-jwt: 使用JWT认证HTTP`请求。

负载均衡说一下

当系统面临大量用户访问,负载过高的时候,通常会使用增加服务器数量来进行横向扩展,使用集群和负载均衡提高整个系统的处理能力


如果要实现负载均衡,需要注意什么

  1. 选取合适的负载均衡算法:不同的负载均衡算法适用于不同的应用场景,如轮询、最少连接、源IP散列等。选择合适的算法可以确保负载分配的公平性和效率。
  2. 考虑主机的性能:主机的性能是负载均衡的关键,必须确保主机能够承受所分配的负载。要定期监控主机的负载情况,并根据需要调整负载均衡策略。
  3. 考虑主机的可用性:负载均衡应该能够检测到主机的故障,并将流量重新分配到其他可用的主机上。使用心跳机制、健康检查等技术可以提高负载均衡的可用性。
  4. 考虑会话保持:如果应用程序需要保持会话状态,负载均衡器必须能够将同一用户的请求发送到同一台主机上,以确保会话的连续性。可以使用Cookie、URL重写等技术实现会话保持。
  5. 保障数据一致性:如果应用程序需要对数据进行写操作,负载均衡器必须能够确保数据的一致性。可以使用主从复制、数据同步等技术来解决数据一致性的问题。
  6. 考虑安全性:负载均衡器是系统的前端,必须能够保护系统免受网络攻击。可以使用防火墙、入侵检测等技术来提高系统的安全性。
  7. 考虑扩展性:负载均衡器应该能够根据系统的需要进行扩展。可以使用集群、分布式架构等技术来实现负载均衡的扩展。

进程守护需要注意什么

  • 事件延迟,因为 Node.js 主要是事件循环,如果主线程被长时间占用,就会导致事件执行有延迟,而最简单的办法就是使用 setTimeout 来判断。当我们设定 1000ms 执行某个事件,但是真正开始执行的时间大于 1000ms,那么我们就可能存在事件延迟了,而如果这个延迟越来越长,那么就必须进行告警提示开发者需要查看是否有异常事件被卡住,或者服务压力过大。
  • CPU 使用率,这是一个非常重要的指标,当发现 CPU 使用率长期维持在 70% 以上,我们就要考虑是否需要扩容,或者是增加进程的方式来解决这个问题,如果长期在 100% 那么肯定是需要扩容,或者检查内部代码逻辑是否存在问题。
  • 内存变化,Node.js 的内存泄漏还是比较常见的,其最大的问题就是导致垃圾回收时间变长,从而影响 Node.js 的服务性能,最大的影响就是内存达到上限后进行重启,从而中断用户请求,引发在重启过程中的用户请求。
  • 句柄变化,由于服务器的句柄是有上限的,如果无节制地开启句柄,将会导致系统性能损耗,从而影响进程的性能,因此我们必须在未使用句柄时进行释放,而如果长期不释放就会在达到上限时,导致新的请求无法开启新的句柄,从而无法正常提供服务。
  • 进程异常重启次数,也是用来判断我们代码逻辑是否足够健壮的一个点,如果存在异常重启次数,那么一定是我们代码中存在未 catch 住的异常,或者说上面提到的内存泄漏上限问题。
  • 以上指标在达到一定限度的时候,就应该进行告警提示开发者。

node开启多线程怎么做,PM2了解吗

  • 使用cluster模块。单个 Node.js 实例运行在单个线程中。 为了充分利用多核系统,有时需要启用一组 Node.js 进程去处理负载任务。cluster 模块可以创建共享服务器端口的子进程。
  • 介绍: PM2是node进程管理工具,可以利用它来简化很多node应用管理的繁琐任务,如性能监控、自动重启、负载均衡等,而且使用非常简单
    • 主要特性:
      • 启动多子进程,充分使用cpu
      • 子进程之间负载均衡
      • 0秒重启
      • 界面友好
      • 提供进程交互接口

Nodejs能否充分利用多核处理器?

  • 使用Cluster模块。它支持Nodejs应用程序开启多核,允许我们创建多个工作进程,这些进程可以在多个内核上并行运行,并共享一个端口来侦听事件。
  • 每个进程使用IPC与主线程通信,并根据需要将服务器句传递给其他进程。主进程可以侦听端口本身并以循环方式将每个新连接传递给子进程,也可以将端口分配给子进程以便子进程侦听请求。

pm2依据什么重启服务

pm2采用心跳检测查看子进程是否出在活跃状态。每隔数秒向子进程发送心跳包,子进程如果不回复,那么调用kill杀死这个进程,然后再重新cluster.fork()一个新的进程,子进程可以监听错误事件,这时候可以发送消息给主进程,请求杀死自己,并且主进程此时重新调用cluster.fork一个新的子进程


pm2 原理

PM2 的守护进程原理主要是将应用程序作为子进程启动,并在后台监控其运行情况。同时,PM2 本身也会被一个守护进程来监控和管理,以确保整个系统的稳定性和可靠性。
具体如下:

  • 启动应用:当用户使用 PM2 启动应用时,PM2 会创建一个子进程,并将应用程序作为子进程来启动。同时,PM2 会记录该应用程序的相关信息,如 PID(进程 ID)、状态、日志等,并且会将这些信息保存到 PM2 的数据库中。
  • 监控应用:一旦应用程序被启动,PM2 就会监控它的运行情况。如果应用程序意外退出或发生异常,PM2 将会自动重启应用程序。同时,PM2 会定期检查应用程序的资源占用情况,并且可以根据需要调整进程数、CPU 使用率等参数。
  • 守护进程:为了确保 PM2 能够长时间稳定运行,PM2 本身也需要一个守护进程来监控其运行情况。该守护进程会定期检查 PM2 的健康状态,并且在 PM2 出现异常情况时进行相应的处理,例如重启进程、发送警告通知等。
  • 日志管理:PM2 还提供了丰富的日志管理功能,可以将应用程序的日志导出到文件或远程服务器,并且支持实时查看、过滤等操作。这些日志信息对于排查问题、分析业务数据等都非常有用。

pm2怎么做进程管理,进程挂掉怎么处理


不用pm2怎么做进程管理


介绍下node核心模块(主要是stream)


什么是 IOC 机制,如何在 Node 里面使用


NPM的作用是什么

node的包管理工具

npm的简单指令


export和module.exports有什么区别?哪些代码会放在module.exports里?export有哪几种导出方式?加载时间不一样

  • 区别:
    • exports返回的是模块函数,module.exports返回的是模块对象本身,返回的是一个类
    • exports的方法可以直接调用,module.exports需要new对象之后才可以调用

export和export default的区别

  1. export default只能导出一个变量,而export可以导出多个
  2. export 导出的变量可以修改,export default则不行
  3. 语法差异,export const xxx = xxx是合法语句,但是export default则不可以

node的 nextTick 和 vue的 nextTick 区别


process.nextTick和setImmediate有什么区别?

  • 传递给setImmediate函数的回调将在事件队列上的下一次迭代中执行。
  • 另一方面,回调传递给process.nextTick,在下一次迭代之前以及程序中当前运行的操作完成之后执行。咋应用程序启动时,开始遍历事件队列之前调用他的回调。
  • 因此,回调process.nextTick总是在setImmediate之前调用
    代码:

    setImmediate(()=>{
    console.log("first");
    })
    process.nextTick(()=>{
    console.log("second");
    })
    console.log("third");

    将按顺序输出:
    third
    second
    first

V8垃圾回收机制


什么是Nodejs,为什么要使用,有什么特点?

  • 概念:Node是一个基于Chrome JavaScript运行时建立的平台, 用于方便地搭建响应速度快、易于扩展的网络应用
  • 作为web前端开发人员需要了解一门后台语言,对于前端开发人员nodejs 更容易上手
  • 特点:
    1. RESTful API
    2. 单线程
    3. 非阻塞IO
    4. V8虚拟机
    5. 事件驱动

什么是错误优先的回调函数

NodeJS 通常使用回调模式,如果在执行期间发生错误,会把错误作为回调的第一个参数传递到回调函数中

说一下对端口的理解

通过“IP地址+端口号”来区分不同的服务的

什么是域名

域名(英语:Domain Name),又称网域,是由一串用点分隔的名字组成的Internet上某一台计算机或计算机组的名称,用于在数据传输时对计算机的定位标识(有时也指地理位置)

说一下对ip的理解

IP是TCP/IP体系中的网络层协议,IP地址是用来识别网络上的设备,IP地址是由网络地址与主机地址两部分所组成

Node原生api错误处理有了解吗?说一下


介绍下Node EventEmitter


eventEmitter做了什么?

  • nodejs中任何对象发出的事件,都是eventEmitter类的实例,就像http模块所有eventEmitter类都可以使用eventEmitter.on这个函数将事件监听附加到事件上,然后一旦捕捉到这样的事件,就会同步的逐个调用它的监听器,并安排合适执行其关联的回调函数

流是什么?

Stream流逝从源读取或写入数据并将其传输到连续流模板的管道。有4中类型:可读、可写、可读写、先写入,再读出来,每个流也是一个EventEmitter。这意味着流对象可以在流上没有数据、流上有可用数据或流中的数据在程序刷新时发出事件

readFile和createReadStream函数有什么区别?

  • readFile函数异步读取文件的全部内容,并存储在内存中,然后再传递给用户
  • createReadStream使用一个可读的流,逐块读取文件,而不是全部存储在内存中。
  • 与readFile相比,createReadStram使用更少的内存和更快的速度来优化文件读取操作。如果文件相当大,用户不必等待很长时间直到读取整个内容,因为读取时会现象用户发送小块内容

node如何进行跨域通信

  1. 使用socket.io
  2. 使用cors,设置node服务器,配置Access-control-allow-origin

介绍下node文件查找优先级


node如何做错误监控(运行时与其他)。如何生成日志,日志等级

  • 如何监控

    1. 错误类型
      • 当出现语法错误或运行时错误时,会触发js错误
      • 当试图访问一个不存在或没有访问的文件时,会触发系统错误
      • 除了系统错误和js错误外,用户还可以自定义错误
    2. 监控异常日志
      • 异常日志同城用来记录那些意外发生的异常错误。通过日志的记录,可以根据异常信息,去定位bug穿的具体位置,以快速修复问题
  • 如何生成日志

    • 使用日志中间件,日志中间件可以将关键数据按一定格式输出到日志文件中。如:
      • log4j
      • connect
  • 日志等级

    • console.log: 普通日志
    • console.info: 普通信息
    • console.warn: 警告信息
    • console.error: 错误信息

是否了解glob,glob是如何处理文件的,业界是否还有其他解决方案


如何处理Node中未捕获的异常?

可以在进程级别捕获应用程序中未捕获的异常。为此将侦听器附加到process全局对象


Node如何做版本的升级?为什么要用nvm?

  • 提升webpack打包的速度,因为webpack可能会用到新的Api来提升打包速度
  • 因为它可以切换Node版本

图片上传到服务器的过程

type等于file的input框,有个onChange事件,一旦file发生了变化,在高版本的浏览器里,js里有一个FileReader的类,可以调用FileReader.readAsDataURL的方法,去读取到文件的base64的字符串,拿到这个字符串之后,就可以用image标签塞到src里面去,这样就完成了预览功能,当你点击保存的时候,把这个file通过form表单的形式提交给后端,后端传到服务器上去
全版本:当触发onchange事件时,通过form表单提交给后端,后端返回给你一个图片url,拿到后放到url里


mongodb与mysql的区别


Node中的npm与版本管理


token存在localstorage里,过期了怎么处理?

  • token一般放到cookie里,后端可以写入cookie,所以让后端去处理,如果后端不处理,当token失效后,前端就跳转到登录页面即可

pnpm原理