從PHP遷移至Golang - 熱更新篇

上篇大致提到的Golang的熱更新,本篇將詳細論述。

1、什麼是熱更新

網絡上有這麼一個例子來形容熱更新,我覺得很形象很貼切:

一架行駛在高速上的大卡車,行駛過程中突然遭遇爆胎,熱更新則是要求在不停車的情況下將車胎修補好,且補胎過程中卡車需要保持正常行駛。

軟件的熱更新就是指在保持系統正常運行的情況下對系統進行更新升級。常見的情況有:系統服務升級、修復現有邏輯、服務配置更新等。

2、熱更新原理

先來看下Nginx熱更新是如何做的?
Nginx支持運行中接收信號,方便開發者控制進程。

  • 1)首先備份原有的Nginx二進制文件,並用新編譯好的Nginx二進制文件替換舊的
  • 2)然後向master進程發送USR2信號。此時Nginx進程會啓動一個新版本Nginx,該新版本Nginx進程會發起一個新的master進程與work進程。即此時會有兩個Nginx實例在運行,一起處理新來的請求。
  • 3)再向原master進程發送WINCH信號,它會逐漸關閉相關work進程,此時原master進程仍保持監聽新請求但不會發送至其下work進程,而是交給新的work進程
  • 4)最後等到所有原work進程全部關閉,向原master進程發送QUIT信號,終止原master進程,至此,完成Nginx熱升級。

:在*nix系統中,信號(Signal)是一種進程間通信機制,它給應用程序提供一種異步的軟件中斷,使應用程序有機會接受其他程序或終端發送的命令(即信號)。

同樣地,Golang熱更新也可以採取類似的處理。如上篇所述,都是利用用戶自定義信號USR2

:Plugin包方式的Golang熱更新本文暫不討論。

3、熱更新實現

Golang熱更新可以細分爲服務熱『更新』(即熱升級,類比Nginx的restart命令)與配置文件熱更新(類比Nginx的restart命令)。接下來從實現細節處依次討論。

3.1 服務熱更新

大致流程如下:

  • 1)Golang服務進程運行時監聽USR2信號
  • 2)進程收到USR2信號後,fork子進程(啓動新版本服務),並將當前socket句柄等進程環境交給它
  • 3)新進程開始監聽socket請求
  • 4)等待舊服務連接停止

主要代碼示例如下:
監聽USR2信號

func (a *app) signalHandler(wg *sync.WaitGroup) {
    ch := make(chan os.Signal, 10)
    signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR2)
    for {
        sig := <-ch
        switch sig {
        case syscall.SIGINT, syscall.SIGTERM:
            // 確保接收到INT/TERM信號時可以觸發Golang標準的進程終止行爲
            signal.Stop(ch)
            a.term(wg)
            return
        case syscall.SIGUSR2:
            err := a.preStartProcess()
            if err != nil {
                a.errors <- err
            }
            // 發起新進程
            if _, err := a.net.StartProcess(); err != nil {
                a.errors <- err
            }
        }
    }
}

複製當前進程socket連接,發起新進程

execSpec := &syscall.ProcAttr{
Env: os.Environ(),
Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()},
}
fork, err := syscall.ForkExec(os.Args[0], os.Args, execSpec)
...

詳細源碼可見:https://scalingo.com/articles...

以上僅爲代碼示例,目前已經成熟的開源實現主要有:endless和facebook的grace,原理基本類似,fork一個子進程,子進程監聽原有父進程socket端口,父進程優雅退出。

在實際的生產環境中推薦使用以上開源庫,關於熱更新開源庫的使用非常方便,下面是facebook的grace庫的例子:
引入github.com/facebookgo/grace/gracehttp

func main() {
    app := gin.New()// 項目中時候的是gin框架
    router.Route(app)
    var server *http.Server
    server = &http.Server{
        Addr:    ":8080",
        Handler: app,
    }
    gracehttp.Serve(server)
}

利用go build命令編譯,生成服務的可執行文件。
然後再用shell封裝一下服務命令,生成restat.sh命令文件

#!/bin/sh

ps aux | grep wingo
count=`ps -ef | grep "wingo" | grep -v "grep" | wc -l`
echo ""

if [ 0 == $count ]; then
    echo "Wingo starting..."
    sudo ./wingo &
    echo "Wingo started"
else
    echo "Wingo Restarting..."
    sudo kill -USR2 $(ps -ef | grep "wingo" | grep -v grep | awk '{print $2}')
    echo "Wingo Restarted"
fi

sleep 1

ps aux | grep wingo

:其中wingo爲服務的二進制名稱。

於是,便可通過執行./restart.sh命令,達到對服務的熱升級目的。

3.2 配置文件熱更新

配置文件熱更新是指在不停止服務的情況下,重新加載服務所有配置文件。
與3.1服務熱升級原理一樣,利用用戶自定義信號USR1,即可實現服務的配置文件熱更新。

  • 1)服務監聽USR1信號
  • 2)服務接收到USR2信號後,停止接受新的連接,等待當前連接停止,重新載入配置文件,重啓服務器,從而實現相對平滑的不停服的更改。

主要代碼實現:

// LoadAllConf 調用加載配置文件函數
// load爲具體加載配置文件方法
func LoadAllConf(load func(bool)) {
    load(true)
    listenSIGUSR1(load)
}

// listenSIGUSR1 監聽SIGUSR1信號
func listenSIGUSR1(f func(bool)) {
    s := make(chan os.Signal, 1)
    signal.Notify(s, syscall.SIGUSR1)
    go func() {
        for {
            <-s
            f(false)
            log.Println("Reloaded")
        }
    }()
}

詳細源碼可見:https://www.openmymind.net/Go...

利用go build命令編譯,生成服務的可執行文件。
然後再用shell封裝一下配置重載命令,生成reload.sh命令文件

#!/bin/sh

ps aux | grep wingo
echo ""

echo "Wingo Reloading..."
sudo kill -USR1 $(ps -ef | grep "wingo" | grep -v grep | awk '{print $2}')
echo "Wingo Reloaded"
echo ""

sleep 1

ps aux | grep wingo

於是,便可通過執行./reload.sh命令,達到對服務的配置文件熱升級目的。

4、總結

本文主要描述了Golang服務熱升級與配置文件熱更新原理與主要代碼實現,本質上也不是什麼新內容,如果之前讀過《Unix環境高級編程》,就會覺得很親切。底層原理基本上是利用了信號這個軟件中斷機制,在運行中改變常駐進程的行爲。

References

https://scalingo.com/articles...
http://kuangchanglang.com/gol...
https://blog.csdn.net/black_O...
https://www.openmymind.net/Go...
https://blog.csdn.net/qq_1543...
https://wrfly.kfd.me/posts/%E...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章