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

经过对如下 10 个面试题的分享,助您更好的理解 Node.js 的进程和线程相关知识html

做者简介:五月君,Nodejs Developer,热爱技术、喜欢分享的 90 后青年,公众号 “Nodejs技术栈”,Github 开源项目 www.nodejs.rednode

快速导航

  • 什么是进程和线程?之间的区别?参考:Interview1
  • 什么是孤儿进程?参考:Interview2
  • 建立多进程时,代码里有 app.listen(port) 在进行 fork 时,为何没有报端口被占用?参考:Interview3
  • 什么是 IPC 通讯,如何创建 IPC 通讯?什么场景下须要用到 IPC 通讯?参考:Interview4
  • Node.js 是单线程仍是多线程?进一步会提问为何是单线程?参考:Interview5
  • 关于守护进程,是什么、为何、怎么编写?参考:Interview6
  • 实现一个简单的命令行交互程序?参考:Interview7
  • 如何让一个 js 文件在 Linux 下成为一个可执行命令程序?参考:Interview8
  • 进程的当前工做目录是什么? 有什么做用?参考:Interview9
  • 多进程或多个 Web 服务之间的状态共享问题?参考:Interview10

做者简介:五月君,Nodejs Developer,热爱技术、喜欢分享的 90 后青年,公众号 “Nodejs技术栈”,Github 开源项目 www.nodejs.redgit

Interview1

什么是进程和线程?之间的区别?github

关于线程和进程是服务端一个很基础的概念,在文章 Node.js进阶之进程与线程 中介绍了进程与线程的概念以后又给出了在 Node.js 中的进程和线程的实际应用,对于这块不是很理解的建议先看下。面试

Interview2

什么是孤儿进程?数据库

父进程建立子进程以后,父进程退出了,可是父进程对应的一个或多个子进程还在运行,这些子进程会被系统的 init 进程收养,对应的进程 ppid 为 1,这就是孤儿进程。经过如下代码示例说明。api

// master.js
const fork = require('child_process').fork;
const server = require('net').createServer();
server.listen(3000);
const worker = fork('worker.js');

worker.send('server', server);
console.log('worker process created, pid: %s ppid: %s', worker.pid, process.pid);
process.exit(0); // 建立子进程以后,主进程退出,此时建立的 worker 进程会成为孤儿进程
复制代码
// worker.js
const http = require('http');
const server = http.createServer((req, res) => {
	res.end('I am worker, pid: ' + process.pid + ', ppid: ' + process.ppid); // 记录当前工做进程 pid 及父进程 ppid
});

let worker;
process.on('message', function (message, sendHandle) {
	if (message === 'server') {
		worker = sendHandle;
		worker.on('connection', function(socket) {
			server.emit('connection', socket);
		});
	}
});
复制代码

孤儿进程 示例源码浏览器

控制台进行测试,输出当前工做进程 pid 和 父进程 ppidbash

$ node master
worker process created, pid: 32971 ppid: 32970
复制代码

因为在 master.js 里退出了父进程,活动监视器所显示的也就只有工做进程。服务器

图片描述

再次验证,打开控制台调用接口,能够看到工做进程 32971 对应的 ppid 为 1(为 init 进程),此时已经成为了孤儿进程

$ curl http://127.0.0.1:3000
I am worker, pid: 32971, ppid: 1
复制代码

Interview3

建立多进程时,代码里有 app.listen(port) 在进行 fork 时,为何没有报端口被占用?

先看下端口被占用的状况

// master.js
const fork = require('child_process').fork;
const cpus = require('os').cpus();

for (let i=0; i<cpus.length; i++) {
    const worker = fork('worker.js');
    console.log('worker process created, pid: %s ppid: %s', worker.pid, process.pid);
}
复制代码
//worker.js
const http = require('http');
http.createServer((req, res) => {
	res.end('I am worker, pid: ' + process.pid + ', ppid: ' + process.ppid);
}).listen(3000);
复制代码

多进程端口占用冲突 示例源码

以上代码示例,控制台执行 node master.js 只有一个 worker 能够监听到 3000 端口,其他将会抛出 Error: listen EADDRINUSE :::3000 错误

那么多进程模式下怎么实现多端口监听呢?答案仍是有的,经过句柄传递 Node.js v0.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 就是句柄,能够是 TCP套接字、TCP服务器、UDP套接字等,为了解决上面多进程端口占用问题,咱们将主进程的 socket 传递到子进程,修改代码,以下所示:

//master.js
const fork = require('child_process').fork;
const cpus = require('os').cpus();
const server = require('net').createServer();
server.listen(3000);
process.title = 'node-master'

for (let i=0; i<cpus.length; i++) {
    const worker = fork('worker.js');
    worker.send('server', server);
    console.log('worker process created, pid: %s ppid: %s', worker.pid, process.pid);
}
复制代码
// worker.js
const http = require('http');
http.createServer((req, res) => {
	res.end('I am worker, pid: ' + process.pid + ', ppid: ' + process.ppid);
})

let worker;
process.title = 'node-worker'
process.on('message', function (message, sendHandle) {
	if (message === 'server') {
		worker = sendHandle;
		worker.on('connection', function(socket) {
			server.emit('connection', socket);
		});
	}
});
复制代码

句柄传递解决多进程端口占用冲突问题 示例源码

验证一番,控制台执行 node master.js 如下结果是咱们预期的,多进程端口占用问题已经被解决了。

$ node master.js
worker process created, pid: 34512 ppid: 34511
worker process created, pid: 34513 ppid: 34511
worker process created, pid: 34514 ppid: 34511
worker process created, pid: 34515 ppid: 34511
复制代码

关于多进程端口占用问题,cnode 上有篇文章也能够看下 经过源码解析 Node.js 中 cluster 模块的主要功能实现

Interview4

什么是 IPC 通讯,如何创建 IPC 通讯?什么场景下须要用到 IPC 通讯?

IPC (Inter-process communication) ,即进程间通讯技术,因为每一个进程建立以后都有本身的独立地址空间,实现 IPC 的目的就是为了进程之间资源共享访问,实现 IPC 的方式有多种:管道、消息队列、信号量、Domain Socket,Node.js 经过 pipe 来实现。

看一下 Demo,未使用 IPC 的状况

// pipe.js
const spawn = require('child_process').spawn;
const child = spawn('node', ['worker.js'])
console.log(process.pid, child.pid); // 主进程id3243 子进程3244
复制代码
// worker.js
console.log('I am worker, PID: ', process.pid);
复制代码

控制台执行 node pipe.js,输出主进程id、子进程id,可是子进程 worker.js 的信息并无在控制台打印,缘由是新建立的子进程有本身的stdio 流。

$ node pipe.js
41948 41949
复制代码

建立一个父进程和子进程之间传递消息的 IPC 通道实现输出信息

修改 pipe.js 让子进程的 stdio 和当前进程的 stdio 之间创建管道连接,还能够经过 spawn() 方法的 stdio 选项创建 IPC 机制,参考 options.stdio

// pipe.js
const spawn = require('child_process').spawn;
const child = spawn('node', ['worker.js'])
child.stdout.pipe(process.stdout);
console.log(process.pid, child.pid);
复制代码

父子进程 IPC 通讯 源码示例

再次验证,控制台执行 node pipe.js,worker.js 的信息也打印了出来

$ 42473 42474
I am worker, PID:  42474
复制代码

关于父进程与子进程是如何通讯的?

参考了深刻浅出 Node.js 一书,父进程在建立子进程以前会先去建立 IPC 通道并一直监听该通道,以后开始建立子进程并经过环境变量(NODE_CHANNEL_FD)的方式将 IPC 频道的文件描述符传递给子进程,子进程启动时根据传递的文件描述符去连接 IPC 通道,从而创建父子进程之间的通讯机制。

图片描述

父子进程 IPC 通讯交互图

Interview5

Node.js 是单线程仍是多线程?进一步会提问为何是单线程?

第一个问题,Node.js 是单线程仍是多线程?这个问题是个基本的问题,在以往面试中偶尔提到仍是有不知道的,Javascript 是单线程的,可是作为其在服务端运行环境的 Node.js 并不是是单线程的。

第二个问题,Javascript 为何是单线程?这个问题须要从浏览器提及,在浏览器环境中对于 DOM 的操做,试想若是多个线程来对同一个 DOM 操做是否是就乱了呢,那也就意味着对于DOM的操做只能是单线程,避免 DOM 渲染冲突。在浏览器环境中 UI 渲染线程和 JS 执行引擎是互斥的,一方在执行时都会致使另外一方被挂起,这是由 JS 引擎所决定的。

Interview6

关于守护进程,是什么、为何、怎么编写?

守护进程运行在后台不受终端的影响,什么意思呢?Node.js 开发的同窗们可能熟悉,当咱们打开终端执行 node app.js 开启一个服务进程以后,这个终端就会一直被占用,若是关掉终端,服务就会断掉,即前台运行模式。若是采用守护进程进程方式,这个终端我执行 node app.js 开启一个服务进程以后,我还能够在这个终端上作些别的事情,且不会相互影响。

建立步骤

  1. 建立子进程
  2. 在子进程中建立新会话(调用系统函数 setsid)
  3. 改变子进程工做目录(如:“/” 或 “/usr/ 等)
  4. 父进程终止

Node.js 编写守护进程 Demo 展现

index.js 文件里的处理逻辑使用 spawn 建立子进程完成了上面的第一步操做。设置 options.detached 为 true 可使子进程在父进程退出后继续运行(系统层会调用 setsid 方法),参考 options_detached,这是第二步操做。options.cwd 指定当前子进程工做目录若不作设置默认继承当前工做目录,这是第三步操做。运行 daemon.unref() 退出父进程,参考 options.stdio,这是第四步操做。

// index.js
const spawn = require('child_process').spawn;

function startDaemon() {
    const daemon = spawn('node', ['daemon.js'], {
        cwd: '/usr',
        detached : true,
        stdio: 'ignore',
    });

    console.log('守护进程开启 父进程 pid: %s, 守护进程 pid: %s', process.pid, daemon.pid);
    daemon.unref();
}

startDaemon()
复制代码

daemon.js 文件里处理逻辑开启一个定时器每 10 秒执行一次,使得这个资源不会退出,同时写入日志到子进程当前工做目录下

// /usr/daemon.js
const fs = require('fs');
const { Console } = require('console');

// custom simple logger
const logger = new Console(fs.createWriteStream('./stdout.log'), fs.createWriteStream('./stderr.log'));

setInterval(function() {
	logger.log('daemon pid: ', process.pid, ', ppid: ', process.ppid);
}, 1000 * 10);
复制代码

守护进程实现 Node.js 版本 源码地址

运行测试

$ node index.js
守护进程开启 父进程 pid: 47608, 守护进程 pid: 47609
复制代码

打开活动监视器查看,目前只有一个进程 47609,这就是咱们须要进行守护的进程

图片描述

守护进程阅读推荐

守护进程总结

在实际工做中对于守护进程并不陌生,例如 PM二、Egg-Cluster 等,以上只是一个简单的 Demo 对守护进程作了一个说明,在实际工做中对守护进程的健壮性要求仍是很高的,例如:进程的异常监听、工做进程管理调度、进程挂掉以后重启等等,这些还须要咱们去不断思考。

Interview7

采用子进程 child_process 的 spawn 方法,以下所示:

const spawn = require('child_process').spawn;
const child = spawn('echo', ["简单的命令行交互"]);
child.stdout.pipe(process.stdout); // 将子进程的输出作为当前进程的输入,打印在控制台
复制代码
$ node execfile
简单的命令行交互
复制代码

Interview8

如何让一个 js 文件在 Linux 下成为一个可执行命令程序?

  1. 新建 hello.js 文件,头部须加上 #!/usr/bin/env node,表示当前脚本使用 Node.js 进行解析
  2. 赋予文件可执行权限 chmod +x chmod +x /${dir}/hello.js,目录自定义
  3. 在 /usr/local/bin 目录下建立一个软链文件 sudo ln -s /${dir}/hello.js /usr/local/bin/hello,文件名就是咱们在终端使用的名字
  4. 终端执行 hello 至关于输入 node hello.js
#!/usr/bin/env node

console.log('hello world!');
复制代码

终端测试

$ hello
hello world!
复制代码

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 或者 数据库来作数据共享。

首发于慕课网,www.imooc.com/article/288…

相关文章
相关标签/搜索