当前位置: 首页 > 后端技术 > Node.js

分享Nodejs进程相关的10道面试题

时间:2023-04-03 14:04:04 Node.js

通过以下10道面试题的分享,帮助大家更好的了解Node.js进程和线程的相关知识分享90后青春,公众号《Nodejs技术栈》,Github开源项目https://www.nodejs.red快速导航什么是进程和线程?之间的区别?参考:Interview1什么是孤儿进程?参考:Interview2创建多进程时,代码中有app.listen(port)。fork时为什么不报端口被占用?参考:Interview3什么是IPC通信,如何建立IPC通信?什么场景下需要使用IPC通信?参考:Interview4Node.js是单线程还是多线程?进一步的问题会问为什么是单线程的?参考:Interview5关于daemons,写什么,为什么,怎么写?参考:Interview6实现一个简单的命令行交互程序?参考:Interview7如何在Linux下将一个js文件制作成可执行的命令程序?参考:Interview8进程的当前工作目录是什么?它有什么作用?参考:Interview9多个web服务之间的多进程或状态共享问题?参考:Interview10作者简介:MayJun,NodejsDeveloper,90后,热爱技术,热爱分享,公众号《Nodejs技术栈》,Github开源项目https://www.nodejs.redInterview1什么是进程和线程?之间的区别?线程和进程是服务器端非常基本的概念。在Node.js进阶进程与线程一文中介绍了进程和线程的概念后,给出了进程和线程在Node.js中的实际应用。不懂这块的请先看看。Interview2什么是孤儿进程?父进程创建子进程后,父进程退出,但父进程对应的一个或多个子进程仍在运行。这些子进程会被系统的init进程收养。对应的进程ppid为1,为孤儿进程。通过以下代码示例说明。//master.jsconstfork=require('child_process').fork;constserver=require('net').createServer();server.listen(3000);constworker=fork('worker.js');worker.send('server',server);console.log('workerprocesscreated,pid:%sppid:%s',worker.pid,process.pid);process.exit(0);//创建子进程之后主进程退出,此时创建的worker进程会成为孤儿进程//worker.jsconsthttp=require('http');constserver=http.createServer((req,res)=>{res.end('我是worker,pid:'+process.pid+',ppid:'+process.ppid);//记录当前worker进程pid和父进程ppid});letworker;process.on('message',function(message,sendHandle){if(message==='server'){worker=sendHandle;worker.on('connection',function(socket){server.emit('connection',socket);});}});孤儿进程示例源码控制台测试,输出当前工作进程pid和父进程ppid$nodemasterworkerprocesscreated,pid:32971ppid:32970自父进程在master.js中退出,活动监视器显示只有工作进程。再次验证,打开console调用界面,可以看到worker进程32971对应的ppid为1(是init进程),此时已经成为孤儿进程。$curlhttp://127.0.0.1:3000我是worker,pid:32971,ppid:1Interview3创建多进程时,代码中有app.listen(port)。fork时为什么不报端口被占用?先看端口被占用的情况//master.jsconstfork=require('child_process').fork;constcpus=require('os').cpus();for(leti=0;i{res.end('我是工人,pid:'+process.pid+',ppid:'+process.ppid);}).listen(3000);多进程端口占用冲突例子源码例子上面,控制台执行nodemaster.js只有一个worker可以监听3000端口,其余的会抛出Error:listenEADDRINUSE:::3000somanyerrors如何实现多端口在过程模式下监控?答案仍然存在。Node.jsv0.5.9支持进程间发送句柄的功能。如何发送?如下图:/***http://nodejs.cn/api/child_process.html#child_process_subprocess_send_message_sendhandle_options_callback*message*sendHandle*/subprocess.send(message,sendHandle)父子进程之间建立IPC通道后,通过子进程对象的send方法发送消息。第二个参数sendHandle是handle,可以是TCPsocket,TCPserver,UDPsocket等。为了解决上面的多进程端口占用问题,我们将主进程的socket传给子进程处理并修改代码如下://master.jsconstfork=require('child_process').fork;constcpus=require('os').cpus();constserver=require('net').createServer();server.listen(3000);process.title='node-master'for(leti=0;i{res.end('我是worker,pid:'+process.pid+',ppid:'+process.ppid);})letworker;process.title='node-worker'进程。on('message',function(message,sendHandle){if(message==='server'){worker=sendHandle;worker.on('connection',function(socket){server.emit('connection',socket);});}});验证进程端口占用冲突示例源码。控制台执行nodemaster.js。以下结果是我们所期望的。解决了多进程端口占用问题。$nodemaster.jsworkerprocesscreated,pid:34512ppid:34511workerprocesscreated,pid:34513ppid:34511workerprocesscreated,pid:34514ppid:34511workerprocesscreated,pid:34515ppid:34511关于多进程端口占用的问题,有一篇关于cnode的文章。也可以阅读Node.js中cluster模块的源码分析实现的主要功能Interview4什么是IPC通信,如何建立IPC通信?什么场景下需要使用IPC通信?IPC(Inter-processcommunication),即进程间通信技术,由于每个进程在创建后都有自己独立的地址空间,所以实现IPC的目的就是共享进程间的资源访问。IPC的实现方式有很多种:管道、消息队列、信号量、DomainSocket,Node.js是通过管道实现的。看看Demo,不使用IPC//pipe.jsconstspawn=require('child_process').spawn;constchild=spawn('node',['worker.js'])console.log(process.pid,孩子.pid);//主进程id3243子进程3244//worker.jsconsole.log('我是worker,PID:',process.pid);console执行nodepipe.js,输出主进程id,子进程id,但是console上并没有打印子进程worker.js的信息,因为新创建的子进程有自己的stdio流。$nodepipe.js4194841949创建父进程和子进程之间传递消息的IPC通道,实现输出信息修改pipe.js,在子进程的stdio和当前进程的stdio之间建立管道链接,以及同样通过spawn()方法中的stdio选项建立IPC机制,参考options.stdio//pipe.jsconstspawn=require('child_process').spawn;constchild=spawn('node',['worker.js'])child.stdout.pipe(process.stdout);console.log(process.pid,child.pid);再次验证父子进程IPC通信源码示例,控制台执行nodepipe.js,同时打印worker.js的信息。$4247342474我是worker,PID:42474父进程如何与子进程通信?简单的参考Node.js这本书,父进程在创建子进程之前会先创建IPC通道并监听通道,然后开始创建子进程,并通过IPC通道的文件描述符传递环境变量(NODE_CHANNEL_FD)对于子进程,当子进程启动时,根据传入的文件描述符链接IPC通道,从而建立父子进程间的通信机制。父子进程IPC通信交互图

访谈5Node.js是单线程还是多线程?进一步的问题会问为什么是单线程的?第一个问题,Node.js是单线程还是多线程?这个问题是一个基础问题,在之前的面试中偶尔会提到。Javascript是单线程的,但是它在服务端的运行环境Node.js不是单线程的。第二个问题,Javascript为什么是单线程的?这个问题需要从浏览器入手。在浏览器环境下,对于DOM的操作,想象一下,如果多个线程对同一个DOM进行操作,会很乱,这意味着对DOM的操作只能是单线程,避免DOM渲染冲突。在浏览器环境下,UI渲染线程和JS执行引擎是互斥的,一个的执行会导致另一个被挂起,这是由JS引擎决定的。Interview6关于daemon进程,写什么,为什么,怎么写?守护进程在后台运行,不受终端影响。这是什么意思?开发过Node.js的同学可能不陌生。当我们打开终端执行nodeapp.js启动一个服务进程时,终端会一直被占用。如果我们关闭终端,服务就会中断,即前台运行模式。如果使用daemon进程的方式,我在这个终端执行nodeapp.js启动一个服务进程后,我也可以在这个终端做其他的事情,互不影响。创建步骤创建子进程在子进程中创建新会话(调用系统函数setsid)更改子进程的工作目录(如:“/”或“/usr/等)父进程终止Node.js写一个守护进程DemoShowindex.js文件中的处理逻辑使用spawn创建子进程来完成上面的第一步,设置options.detached为true可以让子进程在父进程退出后继续运行(system层会调用setsid方法。参考options_detached,这是第二步。options.cwd指定子进程当前工作目录。如果不设置,默认继承当前工作目录。这是第三步.运行daemon.unref()退出父进程,参考options.stdio,这是第四步Step操作。//index.jsconstspawn=require('child_process').spawn;functionstartDaemon(){constdaemon=spawn('node',['daemon.js'],{cwd:'/usr',detached:true,stdio:'忽略',});console.log('守护进程启动父进程pid:%s,守护进程pid:%s',process.pid,daemon.pid);daemon.unref();}startDaemon()daemon.js文件中的处理逻辑启动一个定时器每10秒执行一次,这样这个资源就不会退出,同时将日志写入当前工作目录子进程///usr/daemon.jsconstfs=require('fs');const{Console}=require('console');//自定义简单记录器constlogger=newConsole(fs.createWriteStream('./stdout.log'),fs.createWriteStream('./stderr.log'));setInterval(function(){logger.log('daemonpid:',process.pid,',ppid:',process.ppid);},1000*10);守护进程实现Node.js版本源码地址运行测试$nodeindex.js守护进程启动父进程pid:47608,守护进程pid:47609打开活动监视器查看,只有一个进程47609,这是我们需要守护进程守护进程阅读推荐守护进程实现(Node.js版)守护进程实现(C语言版)守护进程总结在实际工作中,你对守护进程并不陌生,比如PM2、Egg-集群等,以上只是一个简单的demo来讲解daemon进程,实际工作中对daemon进程的健壮性要求还是很高的,例如:进程异常监控,工作进程管理调度,进程挂掉后重启等这些还是需要我们不断思考Interview7使用子进程child_process的spawn方法,如下所示:constspawn=require('child_process').spawn;constchild=spawn('echo',["简单命令行交互"]);child.stdout.pipe(process.stdout);//将子进程的输出作为当前进程的输入,打印在控制台上$nodeexecfile简单命令行交互Interview8如何在Linux下让一个js文件成为可执行的命令程序?新建一个hello.js文件,在头部添加#!/usr/bin/env节点,表示当前脚本使用Node.js解析并使文件可执行权限chmod+xchmod+x/${dir}/hello.js,目录自定义在/usr/local/bin目录下创建软链接文件sudoln-s/${dir}/hello.js/usr/local/bin/hello,文件名就是名字我们在终端中使用。在终端执行hello相当于输入nodehello.js#!/usr/bin/envnodeconsole.log('helloworld!');终端测试$hellohelloworld!Interview9进程的当前工作目录是什么?作用是什么?进程的当前工作目录可以通过process.cwd()命令获取。process.chdir()命令被重置。例如spawn命令创建的子进程可以指定cwd选项来设置子进程的工作目录。效果如何?比如通过fs读取一个文件,如果设置为相对路径,则会相对于当前进程启动的目录进行查找,所以如果启动目录设置错误,将得不到正确的结果。还有一种情况,程序中引用的第三方模块也是根据当前进程启动的目录查找的。//示例process.chdir('/Users/may/Documents/test/')//设置当前进程目录console.log(process.cwd());//获取当前进程目录Interview10多进程或多个Web服务之间状态共享问题?在多进程模式下,每个进程都是相互独立的。比如session在用户登录后保存,如果是在服务进程中保存,那么如果我有4个工作进程,就没有必要为每个进程保存一份,假设重启服务,数据也会丢失。多个Web服务也是如此。还会出现我在A机器上创建了一个Session,负载分摊到B机器后,我需要再创建一个。一般的做法是通过Redis或者数据库来共享数据。