node中创建服务进程
背景
在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 &');
require('child_process').exec('nuhup node app.js &');
这样可以通过执行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');procexx.exit(0)},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进程需要几个步骤:
- 父进程创建子进程,父进程退出,让子进程成为孤儿进程,ppid=1
- 通过setsid命令或函数在子进程中创建新的会话和进程组
- 设置当前目录
- 设置文件权限,并关闭父进程继承打开的fd
所谓会话和进程组,则是在linux多任务多用户下的概念。不同会话的进程无法通过通信,因此父子进程相隔离。而执行setsid命令则让子进程有了新的特性:
- 子进程脱离父进程所在的session控制,两者独立存在互不影响
- 子进程脱离父进程所在的进程组
- 子进程脱离原先的命令行终端,终端退出不影响子进程
下面再回顾方法一与方法二的区别,发现方法一其实并不是真正的daemon进程,只是通过侦听相关中断信号并设置nop函数(不执行默认的中断行为)保证子进程继续运行而已;而方法二则是标准的deamon进程创建方式,优先使用!
How do I run a node.js app as a background service?
node中创建服务进程的更多相关文章
- node中的Stream-Readable和Writeable解读
在node中,只要涉及到文件IO的场景一般都会涉及到一个类-Stream.Stream是对IO设备的抽象表示,其在JAVA中也有涉及,主要体现在四个类-InputStream.Reader.Outpu ...
- 深入理解jQuery、Angular、node中的Promise
最初遇到Promise是在jQuery中,在jQuery1.5版本中引入了Deferred Object,这个异步队列模块用于实现异步任务和回调函数的解耦.为ajax模块.队列模块.ready事件提供 ...
- Nodejs express中创建ejs项目,解决express下默认创建jade,无法创建ejs问题
最近在看<Node.js开发指南>,看到使用nodejs进行web开发的时候,准备创建ejs项目遇到问题了, 书上命令为: express -t ejs microblog 可是执行后,仍 ...
- node中的可读流和可写流
javascript的一个不足之处是不能处理二进制数据,于是node中引入了Buffer类型.这个类型以一个字节(即8位)为单位,给数据分配存储空间.它的使用类似于Array,但是与Array又有不同 ...
- WIN7中因为服务进程是运行在session0下面的~~第一个登录的用户session为1(WTSGetActiveConsoleSessionId取得session的Id,OpenProcessToken取得进程的令牌)
procedure TsvrExamCtrl.ServiceStart(Sender: TService; var Started: Boolean);var CMD: string;begin ...
- Nodejs express中创建ejs项目 error install Couldn't read dependencies
最近在看<Node.js开发指南>,看到使用nodejs进行web开发的时候,准备创建ejs项目遇到问题了 书上命令为: express -t ejs microblog 可是执行 ...
- node.js在windows下的学习笔记(5)---用NODE.JS创建服务器和客户端
//引入http模块 var http = require('http'); //调用http的createServer的方法,这个方法有一个回调函数,这个回调数 //的作用是当有请求发送给服务器的时 ...
- 重回博客 谈一谈Node中的异步和单线程
重回博客,这个帐号之前注册后就只发了一篇博客.听朋友建议,决定一周两次更新. 第一篇谈论一下最近想的比较多的异步的问题. 传统多线程异步 传统的异步是多线程的,当要同时做两件事的时候,他们是执行在不同 ...
- 使用express+multer实现node中的图片上传
使用express+multer实现node中的图片上传 在前端中,我们使用ajax来异步上传图片,使用file-input来上传图片,使用formdata对象来处理图片数据,post到服务器中 在n ...
随机推荐
- Android自学反思总结(中)
后来在导员的推荐加上自己的好奇心给电脑装上了Ubuntu,因为Android的内核就是Linux,导员想让我们及早接触,及早熟悉,这也是我后来一直冷落Windows的原因,装Ubuntu的过程是艰辛的 ...
- 集合框架(HashSet存储自定义对象保证元素唯一性)
HashSet如何保证元素唯一性的原理 1.HashSet原理 a. 我们使用Set集合都是需要去掉重复元素的, 如果在存储的时候逐个equals()比较, 效率较低,哈希算法提高了去重复的效率, 降 ...
- JAVA源码剖析(容器篇)HashMap解析(JDK7)
Map集合: HashMap底层结构示意图: HashMap是一个“链表散列”,其底层是一个数组,数组里面的每一项都是一条单链表. 数组和链表中每一项存的都是一“Entry对象”,该对象内部拥有key ...
- Gson和Json
一下内容为复制别人的: Gson 是 Google 提供的用来在 Java 对象和 JSON 数据之间进行映射的 Java 类库.可以将一个 JSON 字符串转成一个 Java 对象,或者反过来. j ...
- iOS开发之Block
1.定义 (1) Block是OC中的一种数据类型,在iOS开发中被广泛使用 (2) ^是Block的特有标记 (3) Block的实现代码包含在{}之间 (4) 大多情况下,以内联inline函数的 ...
- 疑问:Spring中构造器、init-method、@PostConstruct、afterPropertiesSet孰先孰后,自动注入发生时间
问题:今天想写一个通用点的方法,根据传入的参数的类型(clazz对象),判断使用哪个mapper来插入mysql数据库. 下面是我的写法: public interface BizNeeqCommon ...
- iOS应用的几个阶段
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launc ...
- 老李分享:《Linux Shell脚本攻略》 要点(二)
老李分享:<Linux Shell脚本攻略> 要点(二) poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课 ...
- 原型链、prototype、_proto_那些事
一.概念 1.Prototype:每一个构造函数都有一个原型对象,这个对象就是Prototype.这个构造函数如何找到他的原型对象呢?每个构造函数都会有一个prototype属性,指向它的原型对象. ...
- Android Studio 安装后首次启动的 Config path ...... is invalid 问题(转)
原文链接:http://m.blog.csdn.net/blog/hnust_xiehonghao/46127775 1. 问题描述: 安装好Android Studio后,启动时弹出如下信息: Co ...