nodejs面经

怎么看待nodejs可支持高并发

1、nodejs的单线程架构模型

nodejs并不是真正的单线程架构,其中还有I/O线程(网络I/O、磁盘I/O),这些线程是由更底层的libuv处理,js运行在v8上是单线程的。

 单线程的好坏

优势:

  1. 省去了线程切换的开销
  2. 线程的同步、冲突问题不需要担心

劣势:

  1. 无法充分利用CPU资源
  2. 单线程崩溃程序就无了
  3. 只能用一个cpu,一旦其被某个计算占用,后续请求就会被一直挂起

2、Node.js中的事件循环机制

事件循环允许Node.js执行非阻塞I/O操作.尽管JavaScript是单线程的.通过尽可能将操作卸载到系统内核。由于大多数现代内核都是多线程的,因此它们可以处理在后台执行的多个操作。当其中一个操作完成时,内核会告诉Node.js,以便可以将相应的回调添加到轮询队列中以最终执行

 EventLoop 中的 6个步骤都是相对于宏任务(Macro)的讲述的。NodeJs执行完成一个宏任务后会立即清空当前队列中产生的所有微任务。

timers

执行setTimeout(),setInterval()回调函数

pending callbacks

执行I/O回调

idle,prepare

尽在NodeJs内部调用,无法操作

poll

获取新的I/O事件

  • 如果 轮询 队列 不是空的 ,事件循环将循环访问回调队列并同步执行它们,直到队列已用尽,或者达到了与系统相关的硬性限制。

  • 如果 轮询 队列 是空的 ,还有两件事发生:

    • 如果脚本被 setImmediate() 调度,则事件循环将结束 poll(轮询) 阶段,并继续 check(检查) 阶段以执行那些被调度的脚本。
    • 如果脚本 未被 setImmediate()调度,则事件循环将等待回调被添加到队列中,然后立即执行。

poll可以提前结束使事件循环,提升效率

check

执行setImeediate回调

close callbacks

执行socket的close事件回调

执行一些列关闭的回调函数,socket.on( 'close' , . .  )


  • 什么是同步异步

    • 同步
      • 等待被调用方执行完毕才能继续执行
      • 会阻塞后面代码的执行
    • 异步
      • 不需要一直等待被调用方响应,调用方的主动轮询和被调用方的主动通知
      • 不会阻塞后面代码的执行
    • 区别:调用过程中是主动等待还是被动通知,是否阻塞
  • 什么是阻塞非阻塞

    • 区别:调用状态,调用方在获取结果的过程中是干等还是互不耽误
    • 异步非阻塞是节约调用方时间的(nodejs 一大特点)

典型

setTimeout(function timeout () { console.log('timeout'); },0);

setImmediate(function immediate () { console.log('immediate'); });

这里打印输出出来的结果,并没有什么固定的先后顺序,偏向于随机。(event loop启动需要时间)

答:首先进入的是timers阶段,如果我们的机器性能一般,那么进入timers阶段,1ms已经过去了 ==(setTimeout(fn, 0)等价于setTimeout(fn, 1))==,那么setTimeout的回调会首先执行。

如果没有到1ms,那么在timers阶段的时候,下限时间没到,setTimeout回调不执行,事件循环来到了poll阶段,这个时候队列为空,于是往下继续,先执行了setImmediate()的回调函数,之后在下一个事件循环再执行setTimemout的回调函数。

问题总结:而我们在==执行启动代码==的时候,进入timers的时间延迟其实是==随机的==,并不是确定的,所以会出现两个函数执行顺序随机的情况。

 3、nodejs 是异步非阻塞的 

比如有个客户端请求A进来,需要读取文件,读取文件后将内容整合,最后数据返回给客户端。但在读取文件的时候另一个请求进来了,那处理的流程是怎么样的?

  • 请求A进入服务器,线程开始处理该请求
  • A 请求需要读取文件,ok,交给文件 IO 处理,但是处理得比较慢,需要花 3 秒,这时候 A 请求就挂起(这个词可能不太恰当),等待通知,而等待的实现就是由事件循环机制实现的,
  • 在A请求等待的时候,cpu 是已经被释放的,这时候B请求进来了, cpu 就去处理B请求
  • 两个请求间,并不存在互相竞争的状在 js 引擎上执行的,执行栈一直卡态。那什么时候会出现请求阻塞呢?涉及到大量计算的时候,因为计算是着,别的函数就没法执行,举个栗子,构建一个层级非常深的大对象,反复对这个这个对象 JSON.parse(JSON.stringify(bigObj))

 nodejs怎么创建进程

 cluster 模块可以创建共享服务器端口的子进程

// 1、引入cluster
const { default: cluster } = require('cluster')
const http=require('http')
// 2、获取核数
const cpuNums=require('os').cpus().length
// 3、用fork开启进程
if(cluster.isPrimary){
    for(let i=0;i<cpuNums;i++){
        cluster.fork()
        cluster.on('exit',(worker,code,signal)=>{
            console.log(`worker ${worker.process.pid} died`)
        })
    }
}else{
    // 子线程操作
    http.createServer((req,res)=>{
        res.end('hello')
    }).listen(8001,()=>{
        console.log('running at 8001')
        
    })
}

 进程间通信

线程 

在一个进程的前提下开启多个线程

const {
    Worker, isMainThread, parentPort, workerData
} = require('worker_threads');
const worker = new Worker(__filename, {
    workerData: script
});
  • 线程间如何传输数据: parentPort postMessage on 发送监听消息
  • 共享内存: SharedArrayBuffer 通过这个共享内存

可开辟线程执行大量计算,返回结果


nodejs特点

  1. 事件驱动
    1. 主线程通过event loop事件循环触发的方式来运行程序
  2. 非阻塞异步I/O模型
    1. node 具有异步 I/O 特性,每当有 I/O 请求发生时,node 会提供给该请求一个 I/O 线程。然后 node 就不管这个 I/O 的操作过程了,而是继续执行主线程上的事件,只需要在该请求返回回调时再处理即可。也就是 node 省去了许多等待请求的时间

  3. 轻量高效

exports与modeul.exports

node中每一个模块都有一个自己的module对象

对外到处成员只要将其挂载到module.exports中

exports是module.exports的优化写法,exoprts=module.exports

  • 当一个模块需要导出单个成员的时候,直接给 exports 赋值是不管用的。exports 赋值会断开和 module.exports 之间的引用。同理,给 module.exports 重新赋值也会断开


模块加载规则

  • 优先从缓存中加载
    • 如果已经 require 过,不会重复执行加载,直接可以拿到里面的接口对象
    • 目的是避免重复加载,提高模块加载效率
  • 判断模块标识符 require('模块标识符')
    • 核心模块
    • 自定义模块(路径形式的模块标识)
      • ./,../或/xxx,d:/a/foo.js
      • 首位的/是绝对路径,代表当前文件模块所属磁盘根目录c:/
    • 第三方模块

package-lock.json作用(存放包的下载信息,地址,版本等)

  1. 记住依赖信息,提升加载速度
  2. 为了系统的稳定性考虑,锁定版本号,防止自动升级

package.json文件(包的信息,依赖包的下载索引)

作用是:

  1. 保存第三方包的依赖信息,比 node_modules 清晰。package.json 文件相当于给他人使用时,提供了一份安装所有依赖包的自动下载索引
    • dependencies:在生产环境中需要用到的依赖
    • devDependencies:在开发、测试环境中用到的依赖

NodeJs运行机制

  • V8引擎解析JavaScript脚本。
  • 解析后的代码,调用Node API。
  • libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。
  • V8引擎再将结果返回给用户。

js-》v8-》node-》v8-》js


. 为什么JavaScript是单线程?

  • 防止DOM渲染冲突的问题;
  • Html5中的Web Worker可以实现多线程

什么是任务队列

  • 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
  • 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
  • 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
  • 主线程不断重复上面的第三步。

path.join和path.resolve

  • path.join():所有给定的 path 片段连接到一起,然后规范化生成的路径
  • path.resolve():方法会将路径或路径片段的序列解析为绝对路径,解析为相对于当前目录的绝对路径,相当于cd命令
  • join是把各个path片段连接在一起, resolve把当成根目录
path.join('/a', '/b') // '/a/b'
path.resolve('/a', '/b') //'/b'
复制代码
  • join是直接拼接字段,resolve是解析路径并返回
path.join("a","b")  // "a/b"
path.resolve("a", "b") // "/Users/tree/Documents/infrastructure/KSDK/src/a/b"

  注册路由时 app.get、app.use、app.all 的区别是什么?

app.use

是express用来调用中间件的方法。中间件通常不处理请求和响应,一般只处理输入数据,并将其交给队列中的下一个处理程序,比如下面这个例子app.use('/user'),那么只要路径以 /user 开始即可匹配,如 /user/tree 就可以匹配

app.all

是路由中指代所有的请求方式,用作路由处理,匹配完整路径,在app.use之后 可以理解为包含了app.get、app.post等的定义,比如app.all('/user/tree'),能同时覆盖:get('/user/tree') 、 post('/user/tree')、 put('/user/tree') ,不过相对于app.use()的前缀匹配,它则是匹配具体的路由

  两个node程序之间怎样交互?

通过fork,原理是子程序用process.on来监听父程序的消息,用 process.send给子程序发消息,父程序里用child.on,child.send进行交互,来实现父进程和子进程互相发送消息

 父亲调用child.send,子调用process.on接受

子调用process.send,父调用child.on接受

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值