很多時候對於服務升級的做法簡單粗暴, 就簡單的殺進程啟動新的進程.
還有的好一點就是多個相同的服務依次升級, 保證有服務可用. 但是公平的說這兩種都會丟失請求中的連接.
鑒於這種情況, 在現實中我們可以使用優雅重啟來搞定這個問題. Golang 實現優雅重啟的原理也很簡單:
-
監聽 USR2 信號;
-
收到信號后將服務監聽的文件描述符傳遞給新的子進程;
-
此時新老進程同時接收請求;
-
父進程停止接收新請求, 等待舊請求完成(或超時);
-
父進程退出.
對於上面的原理看似簡單, 其實是分成了兩個大的要點:
-
新老進程同時監聽同一端口, 這個很簡單, Go 很早舊支持;
-
如何等待舊的請求完成, 這個在 Go 1.8 (新增了Server.Shutdown) 之前是需要費一番功夫的.
我們搞定了上面的原理之后, 加上 Go 1.8 的完美等待舊請求的實現,
我實現了一個簡單的優雅重啟庫: https://github.com/douglarek/zerodown.
zerodown
完美兼容基於 Go 標准庫 Server 監聽服務.
對於標准庫的使用, 我們可以象下面一樣使用:
package main import ( "fmt" "log" "net/http" "time" "github.com/douglarek/zerodown" ) func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { time.Sleep(5 * time.Second) fmt.Fprintln(w, "Hello, World!") }) log.Fatalln(zerodown.ListenAndServe(":8080", nil))
對於第三方庫 Gin 我們可以:
package main
import ( "log" "net/http" "time" "github.com/douglarek/zerodown" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.GET("/", func(c *gin.Context) { time.Sleep(5 * time.Second) c.String(http.StatusOK, "Hello, World!") }) log.Fatalln(zerodown.ListenAndServe(":8080", router)) }