Go項(xiàng)目實(shí)現(xiàn)優(yōu)雅關(guān)機(jī)與平滑重啟功能
前言
優(yōu)雅關(guān)機(jī)就是服務(wù)端關(guān)機(jī)命令發(fā)出后不是立即關(guān)機(jī),而是等待當(dāng)前還在處理的請(qǐng)求全部處理完畢后再退出程序,是一種對(duì)客戶端友好的關(guān)機(jī)方式。而執(zhí)行Ctrl+C關(guān)閉服務(wù)端時(shí),會(huì)強(qiáng)制結(jié)束進(jìn)程導(dǎo)致正在訪問(wèn)的請(qǐng)求出現(xiàn)問(wèn)題。
什么是優(yōu)雅關(guān)機(jī)?
優(yōu)雅關(guān)機(jī)就是服務(wù)端關(guān)機(jī)命令發(fā)出后不是立即關(guān)機(jī),而是等待當(dāng)前還在處理的請(qǐng)求全部處理完畢后再退出程序,是一種對(duì)客戶端友好的關(guān)機(jī)方式。而執(zhí)行Ctrl+C
關(guān)閉服務(wù)端時(shí),會(huì)強(qiáng)制結(jié)束進(jìn)程導(dǎo)致正在訪問(wèn)的請(qǐng)求出現(xiàn)問(wèn)題。
實(shí)現(xiàn)原理
Go 1.8版本之后, http.Server 內(nèi)置的 Shutdown() 方法就支持優(yōu)雅地關(guān)機(jī),說(shuō)明一下Shutdown工作的機(jī)制:當(dāng)程序檢測(cè)到中斷信號(hào)時(shí),我們調(diào)用http.server種的shutdown方法,該方法將阻止新的請(qǐng)求進(jìn)來(lái),同時(shí)保持當(dāng)前的連接,知道當(dāng)前連接完成則終止程序!
實(shí)現(xiàn)優(yōu)雅重啟
package main import ( "context" "fmt" "github.com/spf13/viper" "go.uber.org/zap" "log" "net/http" "os" "os/signal" "syscall" "time" ) func main() { //啟動(dòng)服務(wù)(優(yōu)雅關(guān)機(jī)) srv := &http.Server{ Addr: fmt.Sprintf(":%d", viper.GetInt("app.port")), Handler: r, } go func() { // 開(kāi)啟一個(gè)goroutine啟動(dòng)服務(wù) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("listen: %s\n", err) } }() // 等待中斷信號(hào)來(lái)優(yōu)雅地關(guān)閉服務(wù)器,為關(guān)閉服務(wù)器操作設(shè)置一個(gè)5秒的超時(shí) quit := make(chan os.Signal, 1) // 創(chuàng)建一個(gè)接收信號(hào)的通道 // kill 默認(rèn)會(huì)發(fā)送 syscall.SIGTERM 信號(hào) // kill -2 發(fā)送 syscall.SIGINT 信號(hào),我們常用的Ctrl+C就是觸發(fā)系統(tǒng)SIGINT信號(hào) // kill -9 發(fā)送 syscall.SIGKILL 信號(hào),但是不能被捕獲,所以不需要添加它 // signal.Notify把收到的 syscall.SIGINT或syscall.SIGTERM 信號(hào)轉(zhuǎn)發(fā)給quit signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) // 此處不會(huì)阻塞 <-quit // 阻塞在此,當(dāng)接收到上述兩種信號(hào)時(shí)才會(huì)往下執(zhí)行 zap.L().Info("Shutdown Server ...") // 創(chuàng)建一個(gè)5秒超時(shí)的context ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // 5秒內(nèi)優(yōu)雅關(guān)閉服務(wù)(將未處理完的請(qǐng)求處理完再關(guān)閉服務(wù)),超過(guò)5秒就超時(shí)退出 if err := srv.Shutdown(ctx); err != nil { zap.L().Fatal("Server Shutdown: ", zap.Error(err)) } zap.L().Info("Server exiting") }
實(shí)現(xiàn)平滑重啟
import ( "log" "net/http" "time" "github.com/fvbock/endless" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "hello xiaosheng !") }) // 默認(rèn)endless服務(wù)器會(huì)監(jiān)聽(tīng)下列信號(hào): // syscall.SIGHUP,syscall.SIGUSR1,syscall.SIGUSR2,syscall.SIGINT,syscall.SIGTERM和syscall.SIGTSTP // 接收到 SIGHUP 信號(hào)將觸發(fā)`fork/restart` 實(shí)現(xiàn)優(yōu)雅重啟(kill -1 pid會(huì)發(fā)送SIGHUP信號(hào)) // 接收到 syscall.SIGINT或syscall.SIGTERM 信號(hào)將觸發(fā)優(yōu)雅關(guān)機(jī) // 接收到 SIGUSR2 信號(hào)將觸發(fā)HammerTime // SIGUSR1 和 SIGTSTP 被用來(lái)觸發(fā)一些用戶自定義的hook函數(shù) if err := endless.ListenAndServe(":8080", router); err!=nil{ log.Fatalf("listen: %s\n", err) } log.Println("Server exiting...")
測(cè)試
我們通過(guò)執(zhí)行kill -1 pid命令發(fā)送syscall.SIGINT來(lái)通知程序優(yōu)雅重啟,具體做法如下:
- 打開(kāi)終端,go build -o graceful_restart編譯并執(zhí)行./graceful_restart,終端輸出當(dāng)前pid(假設(shè)為43682)
- 將代碼中處理請(qǐng)求函數(shù)返回的hello gin!修改為hello q1mi!,再次編譯go build -o graceful_restart
- 打開(kāi)一個(gè)瀏覽器,訪問(wèn)127.0.0.1:8080/,此時(shí)瀏覽器白屏等待服務(wù)端返回響應(yīng)。
- 在終端迅速執(zhí)行kill -1 43682命令給程序發(fā)送syscall.SIGHUP信號(hào)
- 等第3步瀏覽器收到響應(yīng)信息hello gin!后再次訪問(wèn)127.0.0.1:8080/會(huì)收到hello q1mi!的響應(yīng)。
- 在不影響當(dāng)前未處理完請(qǐng)求的同時(shí)完成了程序代碼的替換,實(shí)現(xiàn)了優(yōu)雅重啟。
但是需要注意的是,此時(shí)程序的PID變化了,因?yàn)閑ndless 是通過(guò)fork子進(jìn)程處理新請(qǐng)求,待原進(jìn)程處理完當(dāng)前請(qǐng)求后再退出的方式實(shí)現(xiàn)優(yōu)雅重啟的。所以當(dāng)你的項(xiàng)目是使用類似supervisor的軟件管理進(jìn)程時(shí)就不適用這種方式了。
總結(jié)
無(wú)論是優(yōu)雅關(guān)機(jī)還是優(yōu)雅重啟歸根結(jié)底都是通過(guò)監(jiān)聽(tīng)特定系統(tǒng)信號(hào),然后執(zhí)行一定的邏輯處理保障當(dāng)前系統(tǒng)正在處理的請(qǐng)求被正常處理后再關(guān)閉當(dāng)前進(jìn)程。使用優(yōu)雅關(guān)機(jī)還是使用優(yōu)雅重啟以及怎么實(shí)現(xiàn),這就需要根據(jù)項(xiàng)目實(shí)際情況來(lái)決定了。
到此這篇關(guān)于Go實(shí)現(xiàn)優(yōu)雅關(guān)機(jī)與平滑重啟 的文章就介紹到這了,更多相關(guān)Go關(guān)機(jī)與重啟 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang正整數(shù)指定規(guī)則排序算法問(wèn)題分析
這篇文章主要介紹了Golang正整數(shù)指定規(guī)則排序算法問(wèn)題,結(jié)合實(shí)例形式分析了Go語(yǔ)言排序算法操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2017-01-01夯實(shí)Golang基礎(chǔ)之?dāng)?shù)據(jù)類型梳理匯總
這篇文章主要8為大家介紹了夯實(shí)Golang基礎(chǔ)之?dāng)?shù)據(jù)類型梳理匯總,有需要的朋友可以借鑒參考下,希望能夠有所幫助2023-10-10Go語(yǔ)言基礎(chǔ)go build命令用法及示例詳解
這篇文章主要為大家介紹了Go語(yǔ)言基礎(chǔ)go build命令用法及示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2021-11-11Golang新提案:panic?能不能加個(gè)?PanicError?
這篇文章主要為大家介紹了Golang的新提案關(guān)于panic能不能加個(gè)PanicError的問(wèn)題分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12Go語(yǔ)言使用組合的方式實(shí)現(xiàn)多繼承的方法
這篇文章主要介紹了Go語(yǔ)言使用組合的方式實(shí)現(xiàn)多繼承的方法,實(shí)例分析了多繼承的原理與使用組合方式來(lái)實(shí)現(xiàn)多繼承的技巧,需要的朋友可以參考下2015-02-02使用GO語(yǔ)言實(shí)現(xiàn)Mysql數(shù)據(jù)庫(kù)CURD的簡(jiǎn)單示例
本文主要介紹了使用GO語(yǔ)言實(shí)現(xiàn)Mysql數(shù)據(jù)庫(kù)CURD的簡(jiǎn)單示例,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08