文盘Rust -- 把程序作为守护进程启动

京东云开发者
• 阅读 387

当我们写完一个服务端程序,需要上线部署的时候,或多或少都会和操作系统的守护进程打交道,毕竟谁也不希望shell关闭既停服。今天我们就来聊聊这个事儿。

最早大家部署应用的通常操作是 “nohup xxxx &”,别说像weblogic 或者其他java 容器有启动脚本,里面其实也差不多;很喜欢 nginx的 -d 参数,或者像redis 配置文件里可以指定是否以守护进程启动。看起来很优雅。

那么,使用rust 写一个服务端程序能不能优雅的使用一个参数指定应用 daemon 模式启动,同时使用stop 方式优雅的停机呢?我们通过一个例子来说说基本的实现方式。

实例代码依然集成在interactcli-rs工程中。

首先来模拟一个启动的服务进程 /src/server/server.rs

    for i in 0..1000 {
        println!("{}", prefix.clone() + &i.to_string());
        thread::sleep(Duration::from_secs(1));
    }
}

程序每秒输出一个字符串,持续999秒,这个时间足够验证实验结果了。

后台启动有两个实现,分别是利用forkdaemonize,这两个crate 实现原理类似,但在使用上稍有不同。

/src/cmd/cmdserver.rs,构建了两个启动子命令,分别来调用 fork 和 daemonize的守护进程启动实现.

    clap::Command::new("server")
        .about("server")
        .subcommand(server_start_byfork())
        .subcommand(server_start_bydaemonize())
}

pub fn server_start_byfork() -> Command {
    clap::Command::new("byfork")
        .about("start daemon by fork crate")
        .arg(
            Arg::new("daemon")
                .short('d')
                .long("daemon")
                .action(ArgAction::SetTrue)
                .help("start as daemon")
                .required(false),
        )
}
pub fn server_start_bydaemonize() -> Command {
    clap::Command::new("bydaemonize")
        .about("start daemon by daemonize crate")
        .arg(
            Arg::new("daemon")
                .short('d')
                .long("daemon")
                .action(ArgAction::SetTrue)
                .help("start as daemon")
                .required(false),
        )
}

server 的子命令 byfork 启动 通过 fork 实现的功能,bydaemonize 则调用通过 daemonize 的功能实现。

命令解析的代码在 /src/cmd/rootcmd.rs 文件中。

先来看看基于 fork 的实现:

    println!("start by fork");
    if startbyfork.get_flag("daemon") {
        let args: Vec<String> = env::args().collect();
        if let Ok(Fork::Child) = daemon(true, false) {
            // 启动子进程
            let mut cmd = Command::new(&args[0])
            for idx in 1..args.len() {
                let arg = args.get(idx).expect("get cmd arg error!");
                // 去除后台启动参数,避免重复启动
                if arg.eq("-d") || arg.eq("-daemon") {
                    continue;
                }
                cmd.arg(arg);

            let child = cmd.spawn().expect("Child process failed to start.");
            fs::write("pid", child.id().to_string()).unwrap();
            println!("process id is:{}", std::process::id());
            println!("child id is:{}", child.id());
        }
        println!("{}", "daemon mod");
        process::exit(0);
    }
    start("by_fork:".to_string());
}

首先,通过 Fork::daemon 函数派生出一个子进程;然后解析一下当前命令,去掉 -d 参数,构建一个启动命令,子命令启动,退出父进程。这基本符合操作系统创建守护进程的过程 -- 两次 fork。

再来看看基于 daemonize 的实现:

            println!("start by daemonize");
            let base_dir = env::current_dir().unwrap();
            if startbydaemonize.get_flag("daemon") {
                let stdout = File::create("/tmp/daemon.out").unwrap();
                let stderr = File::create("/tmp/daemon.err").unwrap();

                println!("{:?}", base_dir);

                let daemonize = Daemonize::new()
                    .pid_file("/tmp/test.pid") // Every method except `new` and `start`
                    .chown_pid_file(true) // is optional, see `Daemonize` documentation
                    .working_directory(base_dir.as_path()) // for default behaviour.          
                    .umask(0o777) // Set umask, `0o027` by default.
                    .stdout(stdout) // Redirect stdout to `/tmp/daemon.out`.
                    .stderr(stderr) // Redirect stderr to `/tmp/daemon.err`.
                    .privileged_action(|| "Executed before drop privileges");

                match daemonize.start() {
                    Ok(_) => {
                        println!("Success, daemonized");
                    }
                    Err(e) => eprintln!("Error, {}", e),
                }
            }
            println!("pid is:{}", std::process::id());
            fs::write("pid", process::id().to_string()).unwrap();
            start("by_daemonize:".to_string());
        }

首先获取当前的工作目录,默认情况下 daemonize 会将工作目录设置为 "/",为了避免权限问题,我们获取当前目录作为守护进程的工作目录。不知道是什么原因,在配置了pid_file 后,启动守护进程时并没在文件中有记录 pid。不过也没关系,我们可以在外部获取并记录守护进程的pid。

两种方式启动的守护进程均可在关闭shell的情况下维持进程运行。

从实现上来讲,不论是 fork 还是 daemonize 都是 通过unsafe 方式调用了 libc api,类 unix 系统大多跑起来没问题,windows 系统作者没有验证。

本期关于守护进程的话题就聊到这儿。

咱们下期见。

作者:贾世闻

点赞
收藏
评论区
推荐文章
GoCoding GoCoding
3年前
Supervisor 开始
Supervisor是Linux/Unix操作系统上的进程管理工具。本文介绍了于Ubuntu18上如何使用Supervisor开机启动、保活守护自己的服务进程。安装建议系统方式安装,可开机启动。bashsudoaptinstallsupervisorySystemd查看服务状态:bash$sudosystemctlstatu
如何优雅地关闭SpringBoot应用程序?听我给你讲
前言Hi,大家好,我是麦洛,今天来聊聊如何优雅地关闭SpringBoot应用程序,有需要交流的朋友,可以私信我或者加我微信:miloleex都可以哈在我们日常开发中,我们如何启停服务?可能下面的命令在熟悉不过了。shellpsef|grep8888kill98888./startup.sh;tailf../logs/catalina.out暴利
Easter79 Easter79
3年前
think
Supervisor的安装与使用入门在linux或者unix操作系统中,守护进程(Daemon)是一种运行在后台的特殊进程,它独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。由于在linux中,每个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端被称为这些进程的控制终端,当控制终端被关闭的时候,相
Stella981 Stella981
3年前
CentOS 7安装Supervisor设置开机启动
SupervisorSupervistor是一个优秀的进程管理程序。它可以为系统UNIX服务器的运维人员,提供监控、操作服务进程的能力,可以作为长期运行的程序的坚实的守护进程。\Supvervisor本文为CentOS7系统的安装指南,成文于2019年02月16日,主要提供以下两个安装操作过程:
Stella981 Stella981
3年前
K8s(7)
仪表板是基于Web的Kubernetes用户界面。您可以使用仪表板将容器化应用程序部署到Kubernetes集群,对容器化应用程序进行故障排除,以及管理集群资源。您可以使用仪表板来概述群集上运行的应用程序,以及创建或修改单个Kubernetes资源(例如部署,作业,守护进程等)。例如,您可以使用部署向导扩展部署,启动滚动更新,重新启动Pod或部署新应用程序
Stella981 Stella981
3年前
Linux查找所有正在运行的守护进程(daemon)
pseoppid,pid,sid,stat,tty,comm |awk'{if($2$3&&$5"?"){print$0};}'首先,要注意,守护进程(daemon)和后台进程(backgroundprocess)有区别。守护进程是一种后台进程,但是,同时,它必须具备以下特性:1\.没
Wesley13 Wesley13
3年前
Java 并发编程:进程、线程、并行与并发
一谈到Java并发编程,我们一般就会联想起进程、线程、并行、并发等等概念。那么这些概念都代表什么呢?进程与线程有什么关系?并发与并行又是什么关系呢?进程与线程进程是指程序的一次动态执行过程,通常我们说计算机中正在执行的程序就是进程,每个程序都会对应着一个进程。一个进程包含了从代码加载到执行完成的一个完整过程,它是操作系统资源分配最小单
Wesley13 Wesley13
3年前
Java并发编程:进程、线程、并行与并发
一谈到Java并发编程,我们一般就会联想起进程、线程、并行、并发等等概念。那么这些概念都代表什么呢?进程与线程有什么关系?并发与并行又是什么关系呢?01 进程与线程进程是指程序的一次动态执行过程,通常我们说计算机中正在执行的程序就是进程,每个程序都会对应着一个进程。一个进程包含了从代码加载到执行完成的一个完整过程,它是操作系
Stella981 Stella981
3年前
Linux进程间的通信方式和原理
进程的概念进程是操作系统的概念,每当我们执行一个程序时,对于操作系统来讲就创建了一个进程,在这个过程中,伴随着资源的分配和释放。可以认为进程是一个程序的一次执行过程。进程通信的概念进程用户空间是相互独立的,一般而言是不能相互访问的。但很多情况下进程间需要互相通信,来完成系统的某项功能。进程通过与内核及其
Stella981 Stella981
3年前
Python实现守护进程
概念守护进程(Daemon)也称为精灵进程是一种生存期较长的一种进程。它们独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。他们常常在系统引导装入时启动,在系统关闭时终止。unix系统有很多守护进程,大多数服务器都是用守护进程实现的,例如inetd守护进程。需要了解的相关概念进程(process)