Chinaunix首页 | 论坛 | 博客
  • 博客访问: 92082
  • 博文数量: 165
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1655
  • 用 户 组: 普通用户
  • 注册时间: 2022-09-26 14:37
文章分类

全部博文(165)

文章存档

2024年(2)

2023年(95)

2022年(68)

我的朋友

分类: 云计算

2022-11-02 10:45:55

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

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

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

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

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

点击(此处)折叠或打开

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


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

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

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


点击(此处)折叠或打开

  1. pub fn new_server_cmd() -> Command {
  2.     clap::Command::new("server")
  3.         .about("server")
  4.         .subcommand(server_start_byfork())
  5.         .subcommand(server_start_bydaemonize())
  6. }

  7. pub fn server_start_byfork() -> Command {
  8.     clap::Command::new("byfork")
  9.         .about("start daemon by fork crate")
  10.         .arg(
  11.             Arg::new("daemon")
  12.                 .short('d')
  13.                 .long("daemon")
  14.                 .action(ArgAction::SetTrue)
  15.                 .help("start as daemon")
  16.                 .required(false),
  17.         )
  18. }
  19. pub fn server_start_bydaemonize() -> Command {
  20.     clap::Command::new("bydaemonize")
  21.         .about("start daemon by daemonize crate")
  22.         .arg(
  23.             Arg::new("daemon")
  24.                 .short('d')
  25.                 .long("daemon")
  26.                 .action(ArgAction::SetTrue)
  27.                 .help("start as daemon")
  28.                 .required(false),
  29.         )
  30. }


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

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

先来看看基于 fork 的实现:


点击(此处)折叠或打开

  1. if let Some(startbyfork) = server.subcommand_matches("byfork") {
  2.     println!("start by fork");
  3.     if startbyfork.get_flag("daemon") {
  4.         let args: Vec<String> = env::args().collect();
  5.         if let Ok(Fork::Child) = daemon(true, false) {
  6.             // 启动子进程
  7.             let mut cmd = Command::new(&args[0])
  8.             for idx in 1..args.len() {
  9.                 let arg = args.get(idx).expect("get cmd arg error!");
  10.                 // 去除后台启动参数,避免重复启动
  11.                 if arg.eq("-d") || arg.eq("-daemon") {
  12.                     continue;
  13.                 }
  14.                 cmd.arg(arg);
  15.             
  16.             let child = cmd.spawn().expect("Child process failed to start.");
  17.             fs::write("pid", child.id().to_string()).unwrap();
  18.             println!("process id is:{}", std::process::id());
  19.             println!("child id is:{}", child.id());
  20.         }
  21.         println!("{}", "daemon mod");
  22.         process::exit(0);
  23.     }
  24.     start("by_fork:".to_string());
  25. }

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

再来看看基于 daemonize 的实现:


点击(此处)折叠或打开

  1. if let Some(startbydaemonize) = server.subcommand_matches("bydaemonize") {
  2.             println!("start by daemonize");
  3.             let base_dir = env::current_dir().unwrap();
  4.             if startbydaemonize.get_flag("daemon") {
  5.                 let stdout = File::create("/tmp/daemon.out").unwrap();
  6.                 let stderr = File::create("/tmp/daemon.err").unwrap();

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

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

  16.                 match daemonize.start() {
  17.                     Ok(_) => {
  18.                         println!("Success, daemonized");
  19.                     }
  20.                     Err(e) => eprintln!("Error, {}", e),
  21.                 }
  22.             }
  23.             println!("pid is:{}", std::process::id());
  24.             fs::write("pid", process::id().to_string()).unwrap();
  25.             start("by_daemonize:".to_string());
  26.         }


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

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

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

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

咱们下期见。

作者:贾世闻


阅读(162) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~