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