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

rust,程序,作为,守护,进程,启动 · 浏览次数 : 441

小编点评

## 服务端程序守护进程启动和停止示例 **使用 fork 的实现:** ```rust 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) } ``` **使用 daemonize 的实现:** ```rust 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) } ``` **使用 fork 的优点:** * 更易于实现,代码简洁。 * 可以通过 `flag` 参数控制守护进程的运行方式。 **使用 daemonize 的优点:** * 更安全,因为它不会将进程的 `pid` 写入文件,避免权限问题。 * 可以更灵活地设置守护进程的运行方式。 **注意:** * `fork` 和 `daemonize` 两种方法都需要在运行守护进程之前获取 `pid`。 * `daemonize` 需要在使用 `pid_file` 时设置 `mode` 参数。

正文

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

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

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

实例代码依然集成在[interactcli-rs](https://github.com/jiashiwen/interactcli-rs)工程中。

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

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

 

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

后台启动有两个实现,分别是利用[fork](github.com/immortal/fork) 或 [daemonize](github.com/knsd/daemonize),这两个crate 实现原理类似,但在使用上稍有不同。

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

 

pub fn new_server_cmd() -> Command {
    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 的实现: 

if let Some(startbyfork) = server.subcommand_matches("byfork") {
    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 的实现:

 

if let Some(startbydaemonize) = server.subcommand_matches("bydaemonize") {
            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 系统作者没有验证。

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

咱们下期见。

作者:贾世闻

 

与文盘Rust -- 把程序作为守护进程启动相似的内容:

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

当我们写完一个服务端程序,需要上线部署的时候,或多或少都会和操作系统的守护进程打交道,毕竟谁也不希望shell关闭既停服。今天我们就来聊聊这个事儿。 最早大家部署应用的通常操作是 “nohup xxxx &”,别说像weblogic 或者其他java 容器有启动脚本,里面其实也差不多;很喜欢 ngi

文盘Rust -- 生命周期问题引发的 static hashmap 锁

2021年上半年,撸了个rust cli开发的框架,基本上把交互模式,子命令提示这些cli该有的常用功能做进去了。项目地址:[https://github.com/jiashiwen/interactcli-rs。](https://github.com/jiashiwen/interactcli-

文盘Rust -- r2d2 实现redis连接池

作者:贾世闻 我们在开发应用后端系统的时候经常要和各种数据库、缓存等资源打交道。这一期,我们聊聊如何访问redis 并将资源池化。 在一个应用后端程序访问redis主要要做的工作有两个,单例和池化。 在后端应用集成redis,我们主要用到以下几个crate:​ ​once_cell​​​、​ ​re

文盘Rust -- rust 连接云上数仓 starwift

最近想看看 rust 如何集成 clickhouse,又犯了好吃懒做的心理(不想自己建环境),刚好京东云发布了兼容ck 的云原生数仓 Starwfit,于是搞了个实例折腾一番。 Starwfit 是京东云自主研发的新一代云原生数据仓库,通过存算分离降低了存储成本,同时兼具性能和扩展弹性。其写入和查询速度可达到传统数据仓库的数倍,为用户提供实时数据分析能力。广泛应用于流量分析、精准营销、用户画像、广

文盘Rust -- 领域交互模式如何实现

书接上文,上回说到如何通过interactcli-rs四步实现一个命令行程序。但是 shell 交互模式在有些场景下用户体验并不是很好。比如我们要连接某个服务,比如 mysql 或者 redis 这样的服务。如果每次交互都需要输入地址、端口、用户名等信息,交互起来太麻烦。通常的做法是一次性输入和连接相关的信息或者由统一配置文件进行管理,然后进入领域交互模式,所有的命令和反馈都和该领域相关。inte

文盘Rust -- 本地库引发的依赖冲突

clickhouse 的原生 rust 客户端目前比较好的有两个clickhouse-rs 和 clickhouse.rs 。两个库在单独使用时没有任何问题,但是,在同一工程同时引用时会报错。本篇内容主要讲解如何用rust语言解决本地库引发的依赖冲突问题

文盘Rust -- 安全连接 TiDB/Mysql

最近在折腾rust与数据库集成,选了Tidb Cloud Serverless Tier 作为数据源。Tidb 无疑是近五年来最优秀的国产开源分布式数据库,Tidb Cloud Serverless Tier作为pingcap旗下的云产品方便又经济,这次使用还有一些小惊喜。

文盘Rust -- FFI 浅尝

rust FFI 是rust与其他语言互调的桥梁,通过FFI rust 可以有效继承 C 语言的历史资产。本期通过几个例子来聊聊rust与C 语言交互的具体步骤

文盘Rust -- Mutex解决并发写文件乱序问题

在实际开发过程中,我们可能会遇到并发写文件的场景,如果处理不当很可能出现文件内容乱序问题。下面我们通过一个示例程序描述这一过程并给出解决该问题的方法。

文盘Rust -- tokio绑定cpu实践

tokio 是 rust 生态中流行的异步运行时框架。在实际生产中我们如果希望 tokio 应用程序与特定的 cpu core 绑定该怎么处理呢?这次我们来聊聊这个话题。