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

在node中创建服务进程

时间:2023-04-03 12:26:15 Node.js

背景在node项目部署中,往往涉及到三方:本地客户端、跳板机、服务端(集群)。通过git触发gitlabhook脚本后,需要在跳板机中执行相应的ssh命令,执行shell文件启动node服务器。这就需要一个普通的命令setsid,这样当执行ssh命令,shell退出时,节点服务器仍然正常运行,此时节点服务进程就是最典型的守护进程(后台服务进程)。那么,如何在node项目中创建守护进程呢?最简单的方法是使用类似于上面的方法:require('child_process').exec('setsidnodeapp.js>/dev/null2>&1&');这可以通过执行shell守护进程来实现。不过本文的重点并不是介绍这种实现守护进程的“命令行”方式,本文将详细介绍守护进程的创建原理,见??下文。目标是在当前的业务中。之所以需要创建守护进程,是为了保证创建进程的父进程(ctrl+c)被打断或者父进程的执行不会影响守护进程的执行。下面分别介绍两种实现方式,在实现原理的细节上有一些区别。下面的所有讨论都是在linux环境下进行的。实现1在Linux系统中,父进程创建子进程。如果此时父进程退出,则子进程成为孤儿进程,其ppid变为1,即成为init进程的子进程。在node环境下,如果不对子进程的stdio做一些特殊的处理,父进程不会真正退出,而是要等到子进程执行完毕才会退出。这样做的原因是node在创建子进程时,会默认将子进程的输出通过管道传递给父进程的流(childProcess.stdout,childProcess.stderr),提供子进程消息的输出在父进程能力。因此,解决这个问题,可以重新分配子进程的stdio:file:parent.jsletcp=require('child_process');constsp=cp.spawn('node',['./c.js'],{stdio:[process.stdin,process.stdout,process.stderr]});setTimeout(()=>{console.log('parentout')},5000);--------------file:c.jssetTimeout(()=>{console.log('childrenexit');},10000)通过在parent.js中设置子进程的stdio为当前终端(实际继承从父进程stdio),这样父进程在5s后退出,此时子进程的ppid变为1,10s后子进程退出。上述实现只满足“父进程正常退出,子进程成为守护进程”的情况。一旦父进程被“ctrl+c”终止,子进程仍然会退出,这还是和node的底层实现有关。默认情况下,“ctrl+c”会触发SIGINT信号,父进程收到信号后发送给子进程。如果子进程有SIGINT监听函数,则执行该函数,否则执行exit系统调用,子进程退出。因此,如果希望子进程收到SIGINT信号后不退出,不处理即可:file:c.jsprocess.on('SIGINT',function(){console.log('childsigint');});setTimeout(()=>{console.log('childrenexit');},10000)可以满足我们原来指定的目标:“父进程退出或被中断,子进程仍然正常运行”。实现2Node官方提供了创建守护进程的相关API。如果不仔细阅读文档,是不容易发现这个特性的。child_process模块中有一个spawn函数。通过spawn,可以执行shell命令和相关选项。同时spawn提供了一些创建子进程的选项,其中“detached”选项与我们的需求密切相关。detached选项允许node帮助我们在本地创建一个守护进程。将datached设置为true可以创建一个新的会话和进程组。子进程的pid是新建进程组的组pid,与setsid作用相同。此时子进程和它的父进程属于两个会话,所以父进程的退出和中断信号不会传递给子进程,子进程也不会接收到父进程的中断信号和不会自然退出。当父进程结束时,子进程成为孤儿进程,被init进程接收,ppid置1。文件:parent.jsletcp=require('child_process');constsp=cp.spawn('node',['./c.js'],{分离:true,stdio:[process.stdin,process.stdout,process.stdout]});sp.unref();setTimeout(()=>{console.log('parentout')},5000);----------------------file:c.jssetTimeout(()=>{console.log('childrenexit');},100000)此时c.js文件没有设置SIGINT事件监听函数,在parent进程中断后,依然会正常运行,正是因为它和parent进程属于两个会话。sp.unref()函数在parent.js文件中设置为“避免父进程等待子进程退出”。那么为什么会出现上述情况呢?这个跟node的事件循环有关。让父进程的事件循环排除对ChildProcess子进程对象的引用,这样父进程就可以单独退出了。总结一下为什么上面介绍的两种方法都能实现daemon进程?这就得回到系统层面去分析了。在linux系统中创建daemon进程需要几个步骤:父进程创建子进程,父进程退出,子进程成为孤儿进程,ppid=1通过在子进程中创建新的session和进程组setsid命令或函数设置当前目录设置文件权限,并关闭从父进程继承的fd。所谓session和processgroup是linux多任务多用户下的概念。不同会话中的进程不能通信,所以父子进程是隔离的。执行setsid命令赋予了子进程一个新的特性:子进程脱离了父进程的session控制,两者独立存在,互不影响。子进程从父进程所在的进程组中分离出来。退出不影响子进程。我们来回顾一下方法一和方法二的区别,我们发现方法一并不是真正的守护进程。它只是监听相关的中断信号并设置nop函数(不执行默认的中断行为)来保证子进程继续运行。它只是在运行;而第二种方法是创建守护进程的标准方法,这是首选!