详解node中创建服务进程


Posted in Javascript onMay 09, 2017

背景

在node工程部署中,常常涉及到三方:本地客户端、跳板机和服务器(集群)。在通过git触发gitlab hook脚本后,需要在跳板机中执行相应的ssh命令执行shell文件启动node服务器,这需要使用一个常用的命令setsid,这样当ssh命令执行完毕shell退出后,node服务器仍正常运行,此时node服务进程就是一个最典型的daemon进程(后台服务进程)。

那么,在node项目中,如何创建一个daemon进程呢?最简单的方式,其实就是采用类似上文中介绍的方式:

require('child_process').exec('setsid node app.js >/dev/null 2>&1 &');

这样可以通过执行shell的方式实现daemon进程。不过本文的重点并不是介绍这种“命令行”的方式实现daemon进程,而且本文会详细讲述daemon进程的创建原理,且看下文。

目标

在当前业务中,之所以需要创建daemon进程就是为了保证中断创建该进程的父进程(ctrl+c)或者父进程执行完毕后并不影响daemon进程的执行。下文介绍两种实现方式,实现原理细节上有些出入。

下文中的所有讨论都是在linux环境下进行。

实现一

在linux系统中,父进程创建出子进程,此时父进程若退出,此时子进程则变为孤儿进程,其ppid变为1,即成为init进程的子进程。在node环境下,如果不针对子进程的stdio做一些特殊处理父进程其实不会真正退出,而是直到子进程执行完毕后再退出。之所以出现这种情况是由于node创建子进程时默认会通过pipe方式将子进程的输出导流到父进程的stream中(childProcess.stdout、childProcess.stderr),提供在父进程中输出子进程消息的能力。

因此,解决此种问题可给子进程的stdio重新赋值:

file: parent.js

let cp = require('child_process');
const sp = cp.spawn('node',['./c.js'],{
  stdio: [process.stdin,process.stdout,process.stderr]
});

setTimeout(()=>{console.log('parent out')},5000);

--------------
file: c.js

setTimeout(()=>{
  console.log('children exit');
},10000)

通过在parent.js中设置子进程的stdio为当前终端(其实继承了父进程的stdio),这样父进程在5s后退出,此时子进程的ppid变为1,10s后子进程退出。

上述实现只满足“父进程正常退出,子进程成为守护进程”的情况,一旦通过“ctrl+c”的方式终端父进程,子进程仍会退出,这还是与node底层实现有关。默认“ctrl+c”触发SIGINT信号,父进程接受信号后发送给子进程,如果子进程存在SIGINT侦听函数,则会执行该函数,否则执行exit系统调用子进程退出。因此,如果要让子进程在接收到SIGINT信号不退出,只需要不作处理即可:

file: c.js

process.on('SIGINT',function(){
  console.log('child sigint');
});

setTimeout(()=>{
  console.log('children exit');
},10000)

以上实现,可以满足我们最初指定的目标:“父进程退出或者中断,子进程仍正常运行”。

 实现二

node官方提供了创建daemon进程的相关API,如果不仔细阅读文档还真不容易发现该特性。在child_process模块中有个spawn函数,通过spawn可以执行shell命令及其相关选项,同时spawn提供了创建子进程的一些选项,其中“detached”选项则与我们的需求密切相关。

detached选项可以让node原生帮我们创建一个daemon进程,设置datached为true可以创建一个新的session和进程组,子进程的pid为新创建进程组的组pid,这与setsid起到相同的作用。此时的子进程已经和其父进程属于两个session,因此父进程的退出和中断信号不会传递给子进程,子进程不会接受到父进程的中断信号自然也不会退出。当父进程结束之后,子进程变为孤儿进程从而被init进程接收,ppid设置为1。

file: parent.js

let cp = require('child_process');
const sp = cp.spawn('node',['./c.js'],{
  detached: true,
  stdio: [process.stdin,process.stdout,process.stdout]
});

sp.unref();
setTimeout(()=>{console.log('parent out')},5000);

----------------------
file: c.js

setTimeout(()=>{
  console.log('children exit');
},100000)

此时,c.js文件并未设置SIGINT事件侦听函数,在父进程中断后仍会正常运行,正是由于其和父进程分属于两个session。

在parent.js文件中设置了sp.unref()函数,目的是“避免父进程等待子进程退出”。那么为何会出现上述情况呢?这与node的事件循环有关,让父进程的事件循环排除对ChildProcess子进程对象的引用,可以使父进程单独退出。

总结

为什么上文介绍的两个方法都可以实现daemon进程呢?这还得回到系统层面进行分析。在linux系统创建一个daemon进程需要几个步骤:

1.父进程创建子进程,父进程退出,让子进程成为孤儿进程,ppid=1

2.通过setsid命令或函数在子进程中创建新的会话和进程组

3.设置当前目录

4.设置文件权限,并关闭父进程继承打开的fd

所谓会话和进程组,则是在linux多任务多用户下的概念。不同会话的进程无法通过通信,因此父子进程相隔离。而执行setsid命令则让子进程有了新的特性:

  1. 子进程脱离父进程所在的session控制,两者独立存在互不影响
  2. 子进程脱离父进程所在的进程组
  3. 子进程脱离原先的命令行终端,终端退出不影响子进程

下面再回顾方法一与方法二的区别,发现方法一其实并不是真正的daemon进程,只是通过侦听相关中断信号并设置nop函数(不执行默认的中断行为)保证子进程继续运行而已;而方法二则是标准的deamon进程创建方式,优先使用!

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Javascript & DHTML 实例编程(教程)(三)初级实例篇1—上传文件控件实例
Jun 02 Javascript
JQuery开发的数独游戏代码
Oct 29 Javascript
jQuery结合PHP+MySQL实现二级联动下拉列表[实例]
Nov 15 Javascript
js 连接数据库如何操作数据库中的数据
Nov 23 Javascript
JavaScript中定义函数的三种方法
Mar 12 Javascript
WEB前端开发框架Bootstrap3 VS Foundation5
May 16 Javascript
JS 日期与时间戮相互转化的简单实例
Jun 22 Javascript
js封装成插件_Canvas统计图插件编写实例
Sep 12 Javascript
微信{"errcode":48001,"errmsg":"api unauthorized, hints: [ req_id: 1QoCla0699ns81 ]"}
Oct 12 Javascript
微信小程序的线程架构【推荐】
May 14 Javascript
js模拟F11页面全屏显示
Sep 17 Javascript
图解JS原型和原型链实现原理
Sep 15 Javascript
微信小程序 自动登陆PHP源码实例(源码下载)
May 08 #Javascript
jquery.uploadifive插件怎么解决上传限制图片或文件大小问题
May 08 #jQuery
微信小程序 标签传入数据
May 08 #Javascript
Webpack实现按需打包Lodash的几种方法详解
May 08 #Javascript
JS正则表达式验证中文字符
May 08 #Javascript
bootstrap table表格插件使用详解
May 08 #Javascript
将input框中输入内容显示在相应的div中【三种方法可选】
May 08 #Javascript
You might like
thinkphp实现图片上传功能分享
2014/03/04 PHP
windows服务器中检测PHP SSL是否开启以及开启SSL的方法
2014/04/25 PHP
WHOOPS PHP调试库的使用
2017/09/29 PHP
PHP 图片合成、仿微信群头像的方法示例
2019/10/25 PHP
几个比较经典常用的jQuery小技巧
2010/03/01 Javascript
jQuery参数列表集合
2011/04/06 Javascript
javaScript 计算两个日期的天数相差(示例代码)
2013/12/27 Javascript
js控制当再次点击按钮时的间隔时间
2014/06/03 Javascript
node.js中的forEach()是同步还是异步呢
2015/01/29 Javascript
js禁止页面刷新与后退的方法
2015/06/08 Javascript
Javascript实现div的toggle效果实例分析
2015/06/09 Javascript
在JavaScript中操作时间之getYear()方法的使用教程
2015/06/11 Javascript
星期几的不同脚本写法(推荐)
2016/06/01 Javascript
AngularJS实现页面定时刷新
2017/03/14 Javascript
jquery dataTable 后台加载数据并分页实例代码
2017/06/07 jQuery
Angularjs 双向绑定时字符串的转换成数字类型的问题
2017/06/12 Javascript
vue获取input输入值的问题解决办法
2017/10/17 Javascript
vue实现在表格里,取每行的id的方法
2018/03/09 Javascript
Node.js Windows Binary二进制文件安装方法
2019/05/16 Javascript
微信小程序实现语音识别转文字功能及遇到的坑
2019/08/02 Javascript
Vue实现将数据库中带html标签的内容输出(原始HTML(Raw HTML))
2019/10/28 Javascript
前端vue-cli项目中使用img图片和background背景图的几种方法
2019/11/13 Javascript
JS实现点餐自动选择框(案例分析)
2019/12/10 Javascript
在Python的Flask框架中实现单元测试的教程
2015/04/20 Python
PyQt5 QSerialPort子线程操作的实现
2018/04/21 Python
解决pycharm导入本地py文件时,模块下方出现红色波浪线的问题
2020/06/01 Python
python 生成正态分布数据,并绘图和解析
2020/12/21 Python
浅谈Html5移动端ios/Android兼容性总结
2018/06/01 HTML / CSS
意大利在线购买隐形眼镜网站:VisionDirect.it
2019/03/18 全球购物
大学毕业通用个人的求职信
2013/12/08 职场文书
大学生活自我评价
2014/04/09 职场文书
2014年度培训工作总结
2014/11/27 职场文书
先进个人自荐书
2015/03/06 职场文书
汉字听写大会观后感
2015/06/12 职场文书
python脚本框架webpy模板控制结构
2021/11/20 Python
Golang 链表的学习和使用
2022/04/19 Golang