Go 平滑重啓

【導讀】線上業務對服務要求高,如何實現服務不強制停止,平滑重啓?本文介紹了一種 go 服務優雅退出的實現。

問題背景

生產環境重要且複雜,許多的操作需要在任何場景都要保證正常運行。

如果我們對線上服務進行更新的步驟如下:

  1. kill -9服務

  2. 再啓動服務

那麼將不可避免的出現以下兩個問題:

一般有三種方案處理以上問題:

  1. 生產環境會通過四層 (lb)-> 七層 (gateway)-> 服務,那麼可以通過流量調度的方式實現平滑重啓

  2. k8s 管理

  3. 程序自身完成平滑重啓(本章介紹)

什麼事平滑重啓

進程在不關閉其所監聽端口的情況下進行重啓,並且重啓的整個過程保證所有請求都能被正確處理。

主要步驟:

  1. 原進程(父進程)先fork一個子進程,同時讓fork出來的子進程繼承父進程所監聽的socket

  2. 子進程完成初始化後,開始接收socket的請求。

  3. 父進程停止接收新的請求,並將當下的請求處理完,等待連接空閒後,平滑退出。

信號(Signal)

服務的平滑重啓,主要依賴進程接收的信號(實現進程間通信),這裏簡單的介紹Golang中信號的處理:

發送信號

在 Linux 下運行man kill可以查看此命令的介紹和用法。

kill -- terminate or signal a process
The kill utility sends a signal to the processes specified by the pid operands.
Only the super-user may send signals to other users' processes.

常用信號類型

信號的默認行爲:

cjhnKC

信號接收測試

package main

import (
    "log"
    "os"
    "os/signal"
    "syscall"
)

func main() {
    sigs := make(chan os.Signal)
    signal.Notify(sigs, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGUSR2)

    // 監聽所有信號
    log.Println("listen sig")
    signal.Notify(sigs)

    // 打印進程id
    log.Println("PID:", os.Getppid())


    s := <-sigs
    log.Println("退出信號", s)
}
go run main.go
## --> listen sig
## --> PID: 4604

kill -s HUP 4604
# --> Hangup: 1

實現案例

demo:

func main() {
   sigs := make(chan os.Signal)
   signal.Notify(sigs, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGUSR2)

   // 監聽所有信號
   log.Println("listen sig")
   signal.Notify(sigs)


   // 打印進程id
   log.Println("PID:", os.Getppid())
   go func() {
      for s := range sigs {
         switch s {
         case syscall.SIGHUP:
            log.Println("startNewProcess...")
            startNewProcess()
            log.Println("shutdownParentProcess...")
            shutdownParentProcess()
         case syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT:
            log.PrintLn("Program Exit...", s)
         case syscall.SIGUSR1:
            log.Println("usr1 signal", s)
         case syscall.SIGUSR2:
            log.Println("usr2 signal", s)
         default:
            log.Println("other signal", s)
         }
      }
   }()

   <-sigs
}

推薦組件

Facebookarchive/grace https://github.com/facebookarchive/grace

shutdown 優雅退出

go 1.8.x 後,golang 在 http 里加入了 shutdown 方法,用來控制優雅退出。

package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    s := http.NewServeMux()
    s.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        time.Sleep(3 * time.Second)
        log.Println(w, "Hello world!")
    })
    server := &http.Server{
        Addr:    ":8090",
        Handler: s,
    }
    go server.ListenAndServe()

    listenSignal(context.Background(), server)
}

func listenSignal(ctx context.Context, httpSrv *http.Server) {
    sigs := make(chan os.Signal, 1)
    signal.Notify(sigs, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)

    select {
    case <-sigs:
        log.Println("notify sigs")
        httpSrv.Shutdown(ctx)
        log.Println("http shutdown")
    }
}

轉自:WilburXu

segmentfault.com/a/1190000038461407

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/LcYdLEkJc1qMCn-LESzfkw