详解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 相关文章推荐
ExtJS 2.2.1的grid控件在ie6中的显示问题
May 04 Javascript
JavaScript中的undefined学习总结
Nov 30 Javascript
js实现特定位取反原理及示例
Jun 30 Javascript
jquery实现表单验证并阻止非法提交
Jul 09 Javascript
AngularJS 最常用的功能汇总
Feb 17 Javascript
vue2.0结合Element实现select动态控制input禁用实例
May 12 Javascript
js循环map 获取所有的key和value的实现代码(json)
May 09 Javascript
vue中子组件调用兄弟组件方法
Jul 06 Javascript
详解使用element-ui table组件的筛选功能的一个小坑
Nov 02 Javascript
vue如何实现自定义底部菜单栏
Jul 01 Javascript
使用VueCli3+TypeScript+Vuex一步步构建todoList的方法
Jul 25 Javascript
javascript设计模式 ? 简单工厂模式原理与应用实例分析
Apr 09 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
php基于表单密码验证与HTTP验证用法实例
2015/01/06 PHP
php生成毫秒时间戳的实例讲解
2017/09/22 PHP
PHP+Session防止表单重复提交的解决方法
2018/04/09 PHP
在laravel-admin中列表中禁止某行编辑、删除的方法
2019/10/03 PHP
JAVASCRIPT下判断IE与FF的比较简单的方式
2008/10/17 Javascript
JS实现鼠标箭头变成一个燃烧烛光效果的方法
2015/02/28 Javascript
jQuery删除一个元素后淡出效果展示删除过程的方法
2015/03/18 Javascript
JavaScript使用concat连接数组的方法
2015/04/06 Javascript
js根据手机客户端浏览器类型,判断跳转官网/手机网站多个实例代码
2016/04/30 Javascript
jquery+ajax实现直接提交表单实例分析
2016/06/17 Javascript
浅谈js对象的创建和对6种继承模式的理解和遐想
2016/10/16 Javascript
AngularJS指令与指令之间的交互功能示例
2016/12/14 Javascript
BootStrap Fileinput初始化时的一些参数
2016/12/30 Javascript
浅谈Vue.js中如何实现自定义下拉菜单指令
2019/01/06 Javascript
微信小程序把百度地图坐标转换成腾讯地图坐标过程详解
2019/07/10 Javascript
vue自定义标签和单页面多路由的实现代码
2020/05/03 Javascript
Vue3 实现双盒子定位Overlay的示例
2020/12/22 Vue.js
[48:18]DOTA2-DPC中国联赛 正赛 RNG vs Dynasty BO3 第二场 1月29日
2021/03/11 DOTA
Python中的filter()函数的用法
2015/04/27 Python
举例详解Python中yield生成器的用法
2015/08/05 Python
python实现mysql的读写分离及负载均衡
2018/02/04 Python
解决pycharm运行程序出现卡住scanning files to index索引的问题
2019/06/27 Python
Django搭建项目实战与避坑细节详解
2020/12/06 Python
星空联盟C# .net笔试题
2014/12/05 面试题
医学生自荐信
2013/12/03 职场文书
求职简历中的自我评价分享
2013/12/08 职场文书
怎样拟定创业计划书
2014/05/01 职场文书
纪检干部先进事迹材料
2014/08/23 职场文书
党员民主评议总结
2014/10/20 职场文书
后勤工作个人总结
2015/02/28 职场文书
2015社区健康教育工作总结
2015/05/20 职场文书
开票证明
2015/06/23 职场文书
搞笑的婚礼主持词
2015/06/29 职场文书
新教师2015年度工作总结
2015/07/22 职场文书
2016企业先进集体事迹材料
2016/02/25 职场文书
分享几个JavaScript运算符的使用技巧
2021/04/24 Javascript