在前面編寫案例代碼時,我相信你會想到html
每次更新完代碼,更新完配置文件後
就直接這麼 ctrl+c
真的沒問題嗎,ctrl+c
到底作了些什麼事情呢?git
在這一節中咱們簡單講述 ctrl+c
背後的信號以及如何在Gin
中優雅的重啓服務,也就是對 HTTP
服務進行熱更新github
原文地址:Golang優雅重啓HTTP服務
項目地址:https://github.com/EDDYCJY/go...golang
內核在某些狀況下發送信號,好比在進程往一個已經關閉的管道寫數據時會產生
SIGPIPE
信號
在終端執行特定的組合鍵可使系統發送特定的信號給此進程,完成一系列的動做segmentfault
命令 | 信號 | 含義 |
---|---|---|
ctrl + c | SIGINT | 強制進程結束 |
ctrl + z | SIGTSTP | 任務中斷,進程掛起 |
ctrl + \ | SIGQUIT | 進程結束 和 dump core |
ctrl + d | EOF | |
SIGHUP | 終止收到該信號的進程。若程序中沒有捕捉該信號,當收到該信號時,進程就會退出(經常使用於 重啓、從新加載進程) |
所以在咱們執行ctrl + c
關閉gin
服務端時,會強制進程結束,致使正在訪問的用戶等出現問題api
常見的 kill -9 pid
會發送 SIGKILL
信號給進程,也是相似的結果緩存
本段中反覆出現信號是什麼呢?安全
信號是 Unix
、類 Unix
以及其餘 POSIX
兼容的操做系統中進程間通信的一種有限制的方式服務器
它是一種異步的通知機制,用來提醒進程一個事件(硬件異常、程序執行異常、外部發出信號)已經發生。當一個信號發送給一個進程,操做系統中斷了進程正常的控制流程。此時,任何非原子操做都將被中斷。若是進程定義了信號的處理函數,那麼它將被執行,不然就執行默認的處理函數less
$ kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX
一、替換可執行文件或修改配置文件
二、發送信號量 SIGHUP
三、拒絕新鏈接請求舊進程,但要保證已有鏈接正常
四、啓動新的子進程
五、新的子進程開始 Accet
六、系統將新的請求轉交新的子進程
七、舊進程處理完全部舊鏈接後正常結束
Zero downtime restarts for golang HTTP and HTTPS servers. (for golang 1.3+)
咱們藉助 fvbock/endless 來實現 Golang HTTP/HTTPS
服務從新啓動的零停機
endless server
監聽如下幾種信號量:
fork
子進程和從新啓動hammerTime
endless
正正是依靠監聽這些信號量,完成管控的一系列動做
go get -u github.com/fvbock/endless
打開 gin-blog 的 main.go
文件,修改文件:
package main import ( "fmt" "log" "syscall" "github.com/fvbock/endless" "gin-blog/routers" "gin-blog/pkg/setting" ) func main() { endless.DefaultReadTimeOut = setting.ReadTimeout endless.DefaultWriteTimeOut = setting.WriteTimeout endless.DefaultMaxHeaderBytes = 1 << 20 endPoint := fmt.Sprintf(":%d", setting.HTTPPort) server := endless.NewServer(endPoint, routers.InitRouter()) server.BeforeBegin = func(add string) { log.Printf("Actual pid is %d", syscall.Getpid()) } err := server.ListenAndServe() if err != nil { log.Printf("Server err: %v", err) } }
endless.NewServer
返回一個初始化的 endlessServer
對象,在 BeforeBegin
時輸出當前進程的 pid
,調用 ListenAndServe
將實際「啓動」服務
$ go build main.go
$ ./main [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. ... Actual pid is 48601
啓動成功後,輸出了pid
爲 48601;在另一個終端執行 kill -1 48601
,檢驗先前服務的終端效果
[root@localhost go-gin-example]# ./main [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode) [GIN-debug] GET /auth --> ... [GIN-debug] GET /api/v1/tags --> ... ... Actual pid is 48601 ... Actual pid is 48755 48601 Received SIGTERM. 48601 [::]:8000 Listener closed. 48601 Waiting for connections to finish... 48601 Serve() returning... Server err: accept tcp [::]:8000: use of closed network connection
能夠看到該命令已經掛起,而且 fork
了新的子進程 pid
爲 48755
48601 Received SIGTERM. 48601 [::]:8000 Listener closed. 48601 Waiting for connections to finish... 48601 Serve() returning... Server err: accept tcp [::]:8000: use of closed network connection
大體意思爲主進程(pid
爲48601)接受到 SIGTERM
信號量,關閉主進程的監聽而且等待正在執行的請求完成;這與咱們先前的描述一致
這時候在 postman
上再次訪問咱們的接口,你能夠驚喜的發現,他「復活」了!
Actual pid is 48755 48601 Received SIGTERM. 48601 [::]:8000 Listener closed. 48601 Waiting for connections to finish... 48601 Serve() returning... Server err: accept tcp [::]:8000: use of closed network connection $ [GIN] 2018/03/15 - 13:00:16 | 200 | 188.096µs | 192.168.111.1 | GET /api/v1/tags...
這就完成了一次正向的流轉了
你想一想,每次更新發布、或者修改配置文件等,只須要給該進程發送SIGTERM信號,而不須要強制結束應用,是多麼便捷又安全的事!
endless
熱更新是採起建立子進程後,將原進程退出的方式,這點不符合守護進程的要求
若是你的Golang >= 1.8
,也能夠考慮使用 http.Server
的 Shutdown 方法
package main import ( "fmt" "net/http" "context" "log" "os" "os/signal" "time" "gin-blog/routers" "gin-blog/pkg/setting" ) func main() { router := routers.InitRouter() s := &http.Server{ Addr: fmt.Sprintf(":%d", setting.HTTPPort), Handler: router, ReadTimeout: setting.ReadTimeout, WriteTimeout: setting.WriteTimeout, MaxHeaderBytes: 1 << 20, } go func() { if err := s.ListenAndServe(); err != nil { log.Printf("Listen: %s\n", err) } }() quit := make(chan os.Signal) signal.Notify(quit, os.Interrupt) <- quit log.Println("Shutdown Server ...") ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second) defer cancel() if err := s.Shutdown(ctx); err != nil { log.Fatal("Server Shutdown:", err) } log.Println("Server exiting") }
在平常的服務中,優雅的重啓(熱更新)是很是重要的一環。而 Golang
在 HTTP
服務方面的熱更新也有很多方案了,咱們應該根據實際應用場景挑選最合適的