千里溃的技术站

技术改变生活
关心代码质量和用户体验
  1. 首页
  2. 编程语言
  3. Golang
  4. 正文

Go后台守护进程运行设计思路

2021年11月7日 3354点热度 0人点赞 0条评论

前言

目前在构思阶段

现状

Go目前的后台守护进程设计都相对比较简单

目标

  • 实现后台运行
  • 实现daemon(守护进程)
  • 实现daemon模式下的停止(stop)、重启(restart)操作

实现思路

使用一个启动器程序(Launcher)启动需要守护的程序(A)

但是我们不希望出现多出一个启动器(Launcher),能不能只有程序A呢?

关键点

  • 父进程和子进程的感知
  • 守护进程(daemon)

解决思路

父进程和子进程的感知

我们希望编程编译后的程序只有一个,并非启动器。所以实现方案就是采用自己启动自己的方案,并且守护自己的子进程。难点在于子程序要知道自己是子程序,父程序知道自己是父程序。

开源库 github.com/sevlyar/go-daemon 巧妙的使用了环境变量,用来区分子进程和父进程。这种方案对go程序影响更小,产生冲突的可能性更小,也避免了使用者对参数变化的迷惑。其原理是利用的是exec.Cmd的Env属性设置子进程的环境变量时,添加一个特殊的环境变量,用以标记子程序。用这个思路,我们把上面的例子修正一下。模仿C语言里的fork,返回一个可用用于判断是子进程还是父进程的数据。

//示例:self1.go

package main

import (
    "fmt"
    "log"
    "os"
    "os/exec"
    "time"
)

func main() {
    cmd, err := background("/tmp/daemon.log")
    if err != nil {
        log.Fatal("启动子进程失败:", err)
    }

    //根据返回值区分父进程子进程
    if cmd != nil { //父进程
        log.Println("我是父进程:", os.Getpid(), "; 启动了子进程:", cmd.Process.Pid, "; 运行参数", os.Args)
        return //父进程退出
    } else { //子进程
        log.Println("我是子进程:", os.Getpid(), "; 运行参数:",os.Args)
    }

    //以下代码只有子进程会执行
    log.Println("只有子进程会运行:", os.Getpid(), "; 开始...")
    time.Sleep(time.Second * 20) //休眠20秒
    log.Println("只有子进程会运行:", os.Getpid(), "; 结束")
}

//@link https://www.zhihu.com/people/zh-five
func background(logFile string) (*exec.Cmd, error) {
    envName := "XW_DAEMON" //环境变量名称
    envValue := "SUB_PROC" //环境变量值

    val := os.Getenv(envName) //读取环境变量的值,若未设置则为空字符串
    if val == envValue {      //监测到特殊标识, 判断为子进程,不再执行后续代码
        return nil, nil
    }

    /*以下是父进程执行的代码*/

    //因为要设置更多的属性, 这里不使用`exec.Command`方法, 直接初始化`exec.Cmd`结构体
    cmd := &exec.Cmd{
        Path: os.Args[0],
        Args: os.Args, //注意,此处是包含程序名的
        Env:  os.Environ(), //父进程中的所有环境变量
    }

    //为子进程设置特殊的环境变量标识
    cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", envName, envValue))

    //若有日志文件, 则把子进程的输出导入到日志文件
    if logFile != "" {
        stdout, err := os.OpenFile(logFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
        if err != nil {
            log.Println(os.Getpid(), ": 打开日志文件错误:", err)
            return nil, err
        }
        cmd.Stderr = stdout
        cmd.Stdout = stdout
    }

    //异步启动子进程
    err := cmd.Start()
    if err != nil {
        return nil, err
    }

    return cmd, nil
}

实现daemon模式下的停止(stop)、重启(restart)操作

如果需要在daemon模式下的停止、重启的操作就需要一个办法给守护进程发送对应的操作动作,由于Go是可以协程运行的,所以可以在守护进程里面启动RPC监听协程后在启动业务进程,即可在RPC监听协程里面对业务进程进行操作。

不过会出现RPC监听占用端口,所以可以使用Unix socket实现通讯,在程序所在目录创建 Unix socket描述文件

参考

  • 知乎:如何让go程序以后台进程或daemon方式运行
  • 葉子:Unix domain socket 跨进程通信
本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: 暂无
最后更新:2021年11月25日

hepeichun

人类改变世界,技术改变生活!

点赞
< 上一篇
下一篇 >

hepeichun

人类改变世界,技术改变生活!

文章目录
  • 前言
  • 现状
  • 目标
  • 实现思路
  • 关键点
  • 解决思路
    • 父进程和子进程的感知
    • 实现daemon模式下的停止(stop)、重启(restart)操作
  • 参考
最新 热点 随机
最新 热点 随机
解决Windows无法访问\\TOWER 无法访问SMB共享的问题 多方法解决设置width:100%再设置margin或padding溢出的问题 swoole Windows 开发(swoole-cli 开发 hyperf) Go 打包静态资源(文件或文件夹)到二进制 30个高效开发方法🔥 uniapp swiper组件current属性动态赋值无效问题的探究
30个高效开发方法🔥 解决Windows无法访问\\TOWER 无法访问SMB共享的问题 Go 打包静态资源(文件或文件夹)到二进制 ffmpeg -stimeout 超时设置不生效问题 Go后台守护进程运行设计思路 swoole Windows 开发(swoole-cli 开发 hyperf)
标签聚合
DI hyperf swoole PHP ffmpeg 依赖注入

COPYRIGHT © 2021-2022 hepeichun.com. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

皖ICP备15003431号-2

皖公网安备34010402703848号