当我们写完一个服务端程序,需要上线部署的时候,或多或少都会和操作系统的守护进程打交道,毕竟谁也不希望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 -- 把程序作为守护进程启动的更多相关文章

  1. Redis使用守护进程启动sentinel并指定其日志目录

    正常redis-server可以通过配置文件来指定守护进程启动以及指定日志路径,但sentinel就不一样了.正常启动redis的sentinel时,进程会直接在前台跑,一退出sentinel进程就关 ...

  2. python程序配置守护进程

    参考博客 python Supervisor 使用与配置_a35155的博客-CSDN博客 Ubuntu系统下:apt-get install supervisor,通过这种方式安装后,自动设置为开机 ...

  3. 为程序启用 守护进程-- supervisior

    待补充... Add this to your /etc/supervisord.conf: [rpcinterface:supervisor] supervisor.rpcinterface_fac ...

  4. Ubuntu上使用systemd创建服务文件来启动和监视底层网络应用程序实现守护进程

    在Linux上使用Nginx设置ASP.NET Core的托管环境,并部署到它 创建服务文件 创建服务定义文件: sudo vim /etc/systemd/system/kestrel-basic. ...

  5. 文盘Rust -- struct 中的生命周期

    最近在用rust 写一个redis的数据校验工具.redis-rs中具备 redis::ConnectionLike trait,借助它可以较好的来抽象校验过程.在开发中,不免要定义struct 中的 ...

  6. Kafka集群部署 (守护进程启动)

    1.Kafka集群部署 1.1集群部署的基本流程 下载安装包.解压安装包.修改配置文件.分发安装包.启动集群 1.2集群部署的基础环境准备 安装前的准备工作(zk集群已经部署完毕)  关闭防火墙 c ...

  7. linus 下redis守护进程启动

    修改配置文件 sudo vim /usr/src/redis/redis.conf // 具体的安装目录不一样,以安装的时候为准 # 将daemonize 改为yes daemonize yes 重新 ...

  8. Python编写守护进程程序

    Python编写守护进程程序思路 1. fork子进程,父进程退出通常,我们执行服务端程序的时候都会通过终端连接到服务器,成功连接后会加载shell环境,终端和shell都是进程,shell进程是终端 ...

  9. PHP程序守护进程化

    一般Server程序都是运行在系统后台,这与普通的交互式命令行程序有很大的区别.glibc里有一个函数daemon.调用此函数,就可使当前进程脱离终端变成一个守护进程,具体内容参见man daemon ...

随机推荐

  1. P7727 风暴之眼 Eye of the Storm (树形 DP)

    谨 以 此 文 表 达 笔 者 个 人 观 点 , 如 有 冒 犯 官 解 , 可 在 评 论 区 诉 说 _{^{_{谨以此文表达笔者个人观点,如有冒犯官解,可在评论区诉说}}} 谨以此文表达笔者个 ...

  2. ABC206 F - Interval Game 2 (区间DP,博弈论,SG函数)

    题面 题意很简单 A l i c e \tt Alice Alice 和 B o b \tt Bob Bob 在博弈.摆在他们面前有 N \rm N N 个区间 [ l i , r i ) \rm[l ...

  3. Android 自动取色并设置沉浸式状态栏

    Android 自动取色并设置沉浸式状态栏 - Stars-One的杂货小窝 最近在进行产品的优化,也是研究了下沉浸式状态栏的实现方法及自动取色,记录一下笔记 设置沉浸式状态栏 1.添加依赖 这里,是 ...

  4. 自定义View3-水波纹扩散(仿支付宝咻一咻)实现代码、思想

    PS:自定义view篇-水波纹实现 效果:水波纹扩散 场景:雷达.按钮点击效果.搜索等 实现:先上效果图,之前记得支付宝有一个咻一咻,当时就是水波纹效果,实现起来一共两步,第一画内圆,第二画多个外圆, ...

  5. hadoop 文件参数配置

    准备环境(省略) 上传实验所需的压缩包 配置网络信息 修改主机名 配置域名解析 关闭防火墙与SELinux(在所有节点上执行)代码如下: systemctl disable --now firewal ...

  6. Typora 最后免费版本也不能用了?简单一招搞定

    作者:小牛呼噜噜 | https://xiaoniuhululu.com 计算机内功.JAVA底层.面试相关资料等更多精彩文章在公众号「小牛呼噜噜 」 Typora是一款优秀的 Markdown 编辑 ...

  7. KingbaseES interval 分区表介绍

    KingbaseES从V008R006C005B0041版本开始支持Oracle的Interval分区表功能. Interval分区表是一种特殊的范围分区表.当执行INSERT或者UPDATE时,若数 ...

  8. KingbaseES R6 集群repmgr.conf参数'recovery'测试案例(三)

    案例三:测试'recovery = manual' 1.查看集群节点状态信息: [kingbase@node1 bin]$ ./repmgr cluster show ID | Name | Role ...

  9. Java 多线程:基础

    Java 多线程:基础 作者:Grey 原文地址: 博客园:Java 多线程:基础 CSDN:Java 多线程:基础 顺序.并行与并发 顺序(sequential)用于表示多个操作『依次』处理.比如把 ...

  10. [Python]-pydicom模块处理DICOM数据

    在处理医疗数据时,经常要跟DICOM文件打交道.在使用Python处理时,不得不提常用的pydicom模块. import pydicom DICOM文件读取 pydicom.read_file()读 ...