守护进程和平滑重启(八)

Gin-API 创建守护进程

实现函数

/*

Linux Mac 下运行

守护进程是生存期长的一种进程。它们独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。

守护进程必须与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符、控制终端、会话和进程组、工作目录以及文件创建掩码等。这些环境通常是守护进程从执行它的父进程(特别是shell)中继承下来的。

本程序只fork一次子进程,fork第二次主要目的是防止进程再次打开一个控制终端(不是必要的)。因为打开一个控制终端的前台条件是该进程必须是会话组长,再fork一次,子进程ID != sid(sid是进程父进程的sid),所以也无法打开新的控制终端

*/

package daemon

import (

"fmt"

"os"

"os/exec"

"syscall"

"time"

)

//var daemon = flag.Bool("d", false, "run app as a daemon process with -d=true")

func InitProcess() {

if syscall.Getppid() == 1 {

if err := os.Chdir("./"); err != nil {

panic(err)

}

syscall.Umask(0) // TODO TEST

return

}

fmt.Println("go daemon!!!")

fp, err := os.OpenFile("daemon.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)

if err != nil {

panic(err)

}

defer func() {

_ = fp.Close()

}()

cmd := exec.Command(os.Args[0], os.Args[1:]...)

cmd.Stdout = fp

cmd.Stderr = fp

cmd.Stdin = nil

cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} // TODO TEST

if err := cmd.Start(); err != nil {

panic(err)

}

_, _ = fp.WriteString(fmt.Sprintf(

"[PID] %d Start At %s\n", cmd.Process.Pid, time.Now().Format("2006-01-02 15:04:05")))

os.Exit(0)

}

初始化

func main() {

    daemon.InitProcess() 

    // ...

}

Gin-API 平滑重启

创建守护进程之后,我们的程序已经能够在后台正常跑通了,但这样还有个问题,那就是在重启服务时候怎么保证服务不中断?

例如Nginx这种7*24小时接收请求的服务,在程序升级、配置文件更新、或者插件加载的时候就需要重启,为保证重启过程不中断服务,我们会使用平滑重启

平滑重启原理

gin-api服务作为协程启动,做相应的处理并返回数据给客户端;主进程负责监听信号,根据信号进行关闭、重启操作

平滑重启步骤

1、主进程(原进程中的主进程)启动协程处理http请求,主进程开始监听终端信号

2、使用 kill -USR2 $pid 发起停止主进程的动作

3、主进程接收到信号量 12 (SIGUSR2) 后, 启动新的子进程,子进程接管父进程的标准输出、错误输出和socket描述符

4、子进程同样启动协程处理请求,子进程中的主进程继续监听终端信号

5、父进程中的主进程发起关闭协程的动作,该协程处理完所有请求后自动关闭(平滑关闭)

6、父进程中的主进程退出

使用 http.Server

由于gin库函数缺少上下文管理功能,所以我们需要使用http.Server来包裹gin服务,支持对服务的平滑关闭功能

实现方式

func (server *Server) Listen(graceful bool) error {

addr := fmt.Sprintf("%s:%d", server.Host, server.Port)

httpServer := &http.Server{

Addr:    addr,

Handler: server.Router,

}

// 判断是否为 reload

var err error

if graceful {

server.Logger.Info("listening on the existing file descriptor 3")

//子进程的 0 1 2 是预留给 标准输入 标准输出 错误输出

//因此传递的socket 描述符应该放在子进程的 3

f := os.NewFile(3, "")

// 获取 上个服务程序的 socket 的描述符

server.Listener, err = net.FileListener(f)

} else {

server.Logger.Info("listening on a new file descriptor")

server.Listener, err = net.Listen("tcp", httpServer.Addr)

server.Logger.Infof("Actual pid is %d\n", syscall.Getpid())

}

if err != nil {

server.Logger.Error(err)

return err

}

go func() {

// 开启服务

if err := httpServer.Serve(server.Listener); err != nil && err != http.ErrServerClosed {

err = errors.New(fmt.Sprintf("listen error:%v\n", err))

server.Logger.Fatal(err) // 报错退出

}

}()

return server.HandlerSignal(httpServer)

}

func (server *Server) HandlerSignal(httpServer *http.Server) error {

sign := make(chan os.Signal)

signal.Notify(sign, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR2)

for {

// 接收信号量

sig := <-sign

server.Logger.Infof("Signal receive: %v\n", sig)

ctx, _ := context.WithTimeout(context.Background(), time.Second*10)

switch sig {

case syscall.SIGINT, syscall.SIGTERM:

// 关闭服务

server.Logger.Info("Shutdown Api Server")

signal.Stop(sign) // 停止通道

if err := httpServer.Shutdown(ctx); err != nil {

err = errors.New(fmt.Sprintf("Shutdown Api Server Error: %s", err))

return err

}

return nil

case syscall.SIGUSR2:

// 重启服务

server.Logger.Info("Reload Api Server")

// 先启动新服务

if err := server.Reload(); err != nil {

server.Logger.Errorf("Reload Api Server Error: %s", err)

continue

}

// 关闭旧服务

if err := httpServer.Shutdown(ctx); err != nil {

err = errors.New(fmt.Sprintf("Shutdown Api Server Error: %s", err))

return err

}

if err := destroyMgoPool(); err != nil {

return err

}

server.Logger.Info("Reload Api Server Successful")

return nil

}

}

}

func (server *Server) Reload() error {

tl, ok := server.Listener.(*net.TCPListener)

if !ok {

return errors.New("listener is not tcp listener")

}

f, err := tl.File()

if err != nil {

return err

}

// 命令行启动新程序

args := []string{"-graceful"}

cmd := exec.Command(os.Args[0], args...)

cmd.Stdout = os.Stdout        //  1

cmd.Stderr = os.Stderr        //  2

cmd.ExtraFiles = []*os.File{f} //  3

if err := cmd.Start(); err != nil {

return err

}

server.Logger.Infof("Forked New Pid %v: \n", cmd.Process.Pid)

return nil

}

深圳网站建设www.sz886.com

你可能感兴趣的:(守护进程和平滑重启(八))