守護進程和平滑重啟(八)

Gin-API 創(chuàng)建守護進程

實現(xiàn)函數(shù)

/*

Linux Mac 下運行

守護進程是生存期長的一種進程。它們獨立于控制終端并且周期性的執(zhí)行某種任務(wù)或等待處理某些發(fā)生的事件。

守護進程必須與其運行前的環(huán)境隔離開來。這些環(huán)境包括未關(guān)閉的文件描述符、控制終端、會話和進程組、工作目錄以及文件創(chuàng)建掩碼等。這些環(huán)境通常是守護進程從執(zhí)行它的父進程(特別是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 平滑重啟

創(chuàng)建守護進程之后,我們的程序已經(jīng)能夠在后臺正常跑通了,但這樣還有個問題,那就是在重啟服務(wù)時候怎么保證服務(wù)不中斷?

例如Nginx這種7*24小時接收請求的服務(wù),在程序升級、配置文件更新、或者插件加載的時候就需要重啟,為保證重啟過程不中斷服務(wù),我們會使用平滑重啟

平滑重啟原理

gin-api服務(wù)作為協(xié)程啟動,做相應(yīng)的處理并返回數(shù)據(jù)給客戶端;主進程負(fù)責(zé)監(jiān)聽信號,根據(jù)信號進行關(guān)閉、重啟操作

平滑重啟步驟

1、主進程(原進程中的主進程)啟動協(xié)程處理http請求,主進程開始監(jiān)聽終端信號

2、使用 kill -USR2 $pid 發(fā)起停止主進程的動作

3、主進程接收到信號量 12 (SIGUSR2) 后, 啟動新的子進程,子進程接管父進程的標(biāo)準(zhǔn)輸出、錯誤輸出和socket描述符

4、子進程同樣啟動協(xié)程處理請求,子進程中的主進程繼續(xù)監(jiān)聽終端信號

5、父進程中的主進程發(fā)起關(guān)閉協(xié)程的動作,該協(xié)程處理完所有請求后自動關(guān)閉(平滑關(guān)閉)

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

使用 http.Server

由于gin庫函數(shù)缺少上下文管理功能,所以我們需要使用http.Server來包裹gin服務(wù),支持對服務(wù)的平滑關(guān)閉功能

實現(xiàn)方式

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 是預(yù)留給 標(biāo)準(zhǔn)輸入 標(biāo)準(zhǔn)輸出 錯誤輸出

//因此傳遞的socket 描述符應(yīng)該放在子進程的 3

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

// 獲取 上個服務(wù)程序的 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() {

// 開啟服務(wù)

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:

// 關(guān)閉服務(wù)

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:

// 重啟服務(wù)

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

// 先啟動新服務(wù)

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

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

continue

}

// 關(guān)閉舊服務(wù)

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

}

深圳網(wǎng)站建設(shè)www.sz886.com

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容