Go?Web開發(fā)之Gin多服務(wù)配置及優(yōu)雅關(guān)閉平滑重啟實(shí)現(xiàn)方法
如何自定義Gin服務(wù)配置及其啟動(dòng)多個(gè)服務(wù)?
描述: 在Gin的生產(chǎn)環(huán)境中通常會(huì)自定義HTTP配置以達(dá)到最優(yōu)性能,此處我們簡(jiǎn)單一下 Server 結(jié)構(gòu)體中可配置的參數(shù)項(xiàng)。
// A Server defines parameters for running an HTTP server. // The zero value for Server is a valid configuration. type Server struct { // 配置監(jiān)聽地址:端口,默認(rèn)是:8080 Addr string // 要調(diào)用的處理程序,http.DefaultServeMux如果為nil Handler Handler // 如果為true,則將“OPTIONS*”請(qǐng)求傳遞給Handler DisableGeneralOptionsHandler bool // 提供TLS配置 TLSConfig *tls.Config //讀取整個(gè)請(qǐng)求(包括正文)的最長(zhǎng)持續(xù)時(shí)間。 ReadTimeout time.Duration // 讀取整請(qǐng)求(Header)的最長(zhǎng)持續(xù)時(shí)間。 ReadHeaderTimeout time.Duration // 超時(shí)寫入響應(yīng)之前的最長(zhǎng)持續(xù)時(shí)間 WriteTimeout time.Duration // 啟用保持活動(dòng)時(shí)等待下一個(gè)請(qǐng)求的最長(zhǎng)時(shí)間 IdleTimeout time.Duration // 控制服務(wù)器解析請(qǐng)求標(biāo)頭的鍵和值(包括請(qǐng)求行)時(shí)讀取的最大字節(jié)數(shù) (通常情況下不進(jìn)行設(shè)置) MaxHeaderBytes int // 在發(fā)生ALPN協(xié)議升級(jí)時(shí)接管所提供TLS連接的所有權(quán)。 TLSNextProto map[string]func(*Server, *tls.Conn, Handler) // 指定了一個(gè)可選的回調(diào)函數(shù),當(dāng)客戶端連接更改狀態(tài)時(shí)調(diào)用該函數(shù) ConnState func(net.Conn, ConnState) // 為接受連接的錯(cuò)誤、處理程序的意外行為以及潛在的FileSystem錯(cuò)誤指定了一個(gè)可選的記錄器 ErrorLog *log.Logger // 返回/此服務(wù)器上傳入請(qǐng)求的基本上下文 BaseContext func(net.Listener) context.Context // 指定一個(gè)函數(shù)來(lái)修改用于新連接c的上下 ConnContext func(ctx context.Context, c net.Conn) context.Context // 當(dāng)服務(wù)器處于關(guān)閉狀態(tài)時(shí)為true inShutdown atomic.Bool disableKeepAlives atomic.Bool nextProtoOnce sync.Once // guards setupHTTP2_* init nextProtoErr error // result of http2.ConfigureServer if used mu sync.Mutex listeners map[*net.Listener]struct{} activeConn map[*conn]struct{} onShutdown []func() listenerGroup sync.WaitGroup }
模塊更新
go get -u golang.org/x/sync/errgroup go mod tidy
示例代碼:
package main import ( "log" "net/http" "time" "github.com/gin-gonic/gin" "golang.org/x/sync/errgroup" ) // 處理屬于同一總體任務(wù)的子任務(wù)的goroutine的集合 var ( g errgroup.Group ) // s2 Gin 服務(wù)的 Handler func router02() http.Handler { e := gin.New() e.Use(gin.Recovery()) e.GET("/", func(c *gin.Context) { c.JSON( http.StatusOK, gin.H{ "code": http.StatusOK, "msg": "Welcome server 02 blog.weiyigeek.top", }, ) }) return e } func main() { // Default返回一個(gè)Engine實(shí)例,該實(shí)例已連接Logger和Recovery中間件。 router := gin.Default() // Gin 服務(wù)s1.用于運(yùn)行HTTP服務(wù)器的參數(shù) (常規(guī)參數(shù)) s1 := &http.Server{ // Gin運(yùn)行的監(jiān)聽端口 Addr: ":8080", // 要調(diào)用的處理程序,http.DefaultServeMux如果為nil Handler: router, // ReadTimeout是讀取整個(gè)請(qǐng)求(包括正文)的最長(zhǎng)持續(xù)時(shí)間。 ReadTimeout: 5 * time.Second, // WriteTimeout是超時(shí)寫入響應(yīng)之前的最長(zhǎng)持續(xù)時(shí)間 WriteTimeout: 10 * time.Second, // MaxHeaderBytes控制服務(wù)器解析請(qǐng)求標(biāo)頭的鍵和值(包括請(qǐng)求行)時(shí)讀取的最大字節(jié)數(shù) (通常情況下不進(jìn)行設(shè)置) MaxHeaderBytes: 1 << 20, } // Go在一個(gè)新的goroutine中調(diào)用給定的函數(shù),此處將Go語(yǔ)言的并發(fā)體現(xiàn)的淋漓盡致。 g.Go(func() error { return s1.ListenAndServe() }) // 配置Gin中間件 // Recovery返回一個(gè)中間件,該中間件可以從任何exception中恢復(fù),并在出現(xiàn)exception時(shí)寫入500。 router.Use(gin.Recovery()) // 服務(wù)s1的路由 router.GET("/", func(c *gin.Context) { c.JSON( http.StatusOK, gin.H{ "code": http.StatusOK, "msg": "Welcome server 01 www.weiyigeek.top", }, ) }) // Gin 服務(wù)s1.定義了不同的監(jiān)聽端口以及Handler s2 := &http.Server{ Addr: ":8081", Handler: router02(), ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, } g.Go(func() error { return s2.ListenAndServe() }) if err := g.Wait(); err != nil { log.Fatal(err) } }
執(zhí)行結(jié)果:
如何優(yōu)雅的關(guān)閉或者重啟Gin應(yīng)用程序?
1.使用 chan 通道監(jiān)聽中斷信號(hào)(SIGINT和SIGTERM)
描述: 在Go Gin中,可以使用以下代碼實(shí)現(xiàn)優(yōu)雅地重啟或停止, 確保所有連接都被正確關(guān)閉,避免數(shù)據(jù)丟失或損壞。
代碼示例:
package main import ( "context" "log" "net/http" "os" "os/signal" "syscall" "time" "github.com/gin-gonic/gin" ) func main() { // 創(chuàng)建 Gin 實(shí)例 router := gin.Default() // 添加路由 router.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "Hello, World! weiyigeek.top") }) // 創(chuàng)建 HTTP Server srv := &http.Server{ Addr: ":8080", Handler: router, } // 開啟一個(gè)goroutine啟動(dòng)服務(wù) 啟動(dòng) HTTP Server go func() { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("listen: %s\n", err) } }() // 等待中斷信號(hào) quit := make(chan os.Signal) // 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í)行 log.Println("Shutdown Server ...") // 創(chuàng)建一個(gè) 5 秒的超時(shí)上下文 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // 關(guān)閉 HTTP Server // // 5秒內(nèi)優(yōu)雅關(guān)閉服務(wù)(將未處理完的請(qǐng)求處理完再關(guān)閉服務(wù)),超過(guò)5秒就超時(shí)退出 if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server Shutdown:", err) } log.Println("Server exiting") }
代碼解析:
首先創(chuàng)建了一個(gè)Gin實(shí)例和一個(gè)HTTP Server,然后啟動(dòng)HTTP Server。接下來(lái),使用signal.Notify()
函數(shù)監(jiān)聽中斷信號(hào)(SIGINT和SIGTERM),當(dāng)接收到中斷信號(hào)時(shí),服務(wù)器會(huì)進(jìn)入優(yōu)雅關(guān)閉流程,即先關(guān)閉HTTP Server
,然后等待5秒鐘,最后退出程序。
在關(guān)閉HTTP Server時(shí),我們使用了srv.Shutdown()
函數(shù),它會(huì)優(yōu)雅地關(guān)閉HTTP Server并等待所有連接關(guān)閉。如果在5秒鐘內(nèi)沒有關(guān)閉完所有連接,函數(shù)會(huì)返回錯(cuò)誤。
知識(shí)補(bǔ)充:
使用os/signal包實(shí)現(xiàn)對(duì)信號(hào)的處理, 最常見的信號(hào)列表。
2.使用 os/exec 包來(lái)執(zhí)行Gin平滑重啟
描述: 在Linux的Go-gin環(huán)境中我們可以使用 os/exec 包來(lái)執(zhí)行重啟命令,然后在 Gin 中定義一個(gè)路由,使得訪問(wèn)該路由時(shí)會(huì)執(zhí)行重啟命令。
代碼示例:
package main import ( "fmt" "net/http" "os" "os/exec" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() // 重啟的路由 /restart r.GET("/restart", func(c *gin.Context) { cmd := exec.Command("killall", "-HUP", "appweiyigeek") err := cmd.Run() if err != nil { fmt.Println("Error executing restart command:", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to restart Gin server."}) return } c.JSON(http.StatusOK, gin.H{"message": "Gin server restarted successfully."}) }) r.Run(":8080") }
編譯執(zhí)行:
go build ./main.go -o appweiyigeek ./appweiyigeek
在上面的例子中,我們定義了一個(gè)路由 /restart,當(dāng)訪問(wèn)該路由時(shí),它會(huì)執(zhí)行 killall -HUP appweiyigeek
命令來(lái)重啟 Gin 服務(wù), 這里的appweiyigeek
應(yīng)該替換為你實(shí)際的 Gin 應(yīng)用程序的名稱。
溫馨提示: 此種重啟方式可能會(huì)導(dǎo)致請(qǐng)求失敗或者超時(shí),因?yàn)樗鼤?huì)強(qiáng)制關(guān)閉正在處理的連接, 如果你需要更加優(yōu)雅的重啟方式,可以考慮使用優(yōu)雅重啟的方式。
3.使用 fvbock/endless 包實(shí)現(xiàn)訪問(wèn)指定路由平滑重啟Gin服務(wù)
描述: 由于endless在windows環(huán)境是不支持,所以博主針對(duì)下述代碼在Linux環(huán)境下載并編譯成二進(jìn)制文件打包到Linux環(huán)境運(yùn)行進(jìn)行驗(yàn)證。
依賴下載:
go get -u github.com/fvbock/endless go mod tidy
代碼示例:
package main import ( "fmt" "log" "net/http" "os/exec" "strconv" "syscall" "github.com/fvbock/endless" "github.com/gin-gonic/gin" ) func main() { pid := syscall.Getpid() // 1.默認(rèn)的Gin引擎 router := gin.Default() // 傳統(tǒng)方式 // server := &http.Server{ // Addr: ":8080", // Handler: router, // ReadTimeout: 5 * time.Second, // WriteTimeout: 10 * time.Second, // } // 2.獲取 Pid router.GET("/pid", func(c *gin.Context) { pid = syscall.Getpid() fmt.Println("Pid:", pid) c.JSON(http.StatusOK, gin.H{ "code": http.StatusOK, "msg": fmt.Sprintf("Gin Server Pid -> %d.", pid), }) }) // 3.重啟 Gin 服務(wù) router.POST("/restart", func(c *gin.Context) { pid = syscall.Getpid() fmt.Println("Restarting Gin Server.......", pid) err := exec.Command("kill", "-1", strconv.Itoa(pid)).Run() if err != nil { fmt.Println("Error executing restart command:", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to restart Gin server."}) return } c.JSON(http.StatusOK, gin.H{"message": "Gin server restarted successfully.", "pid": pid}) }) // 4.使用endless偵聽TCP網(wǎng)絡(luò)地址addr,然后使用處理程序調(diào)用Serve來(lái)處理傳入連接上的請(qǐng)求 err := endless.ListenAndServe(":8080", router) if err != nil || err != http.ErrServerClosed { log.Println("err:", err) } // 5.引入了endless擴(kuò)展,將原本的Run方式啟動(dòng)項(xiàng)目改成了ListenAndServe方式所有此處主席掉 // router.Run(":8080") }
編譯構(gòu)建:
# 切換編譯在Linux平臺(tái)的64位可執(zhí)行程序環(huán)境 go env -w CGO_ENABLED=0 GOOS=linux GOARCH=amd64 # 編譯 go build -o endless-test-1 .\main.go # 執(zhí)行驗(yàn)證 chmod +x endless-test-1 nohup ./endless-test-1 & [1] 1147978
執(zhí)行效果:
# GET 請(qǐng)求 10.20.176.101:8080/pid
# POST 請(qǐng)求 10.20.176.101:8080/restart
請(qǐng)求restart
后可以看見go-gin已經(jīng)平滑重啟了是不是很方便,效果如下。
以上就是Go Web開發(fā)之Gin多服務(wù)配置及優(yōu)雅關(guān)閉平滑重啟實(shí)現(xiàn)方法的詳細(xì)內(nèi)容,更多關(guān)于Go Web Gin多服務(wù)配置的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go語(yǔ)言操作MySql數(shù)據(jù)庫(kù)的詳細(xì)指南
數(shù)據(jù)的持久化是程序中必不可少的,所以編程語(yǔ)言中對(duì)數(shù)據(jù)庫(kù)的操作是非常重要的一塊,這篇文章主要給大家介紹了關(guān)于Go語(yǔ)言操作MySql數(shù)據(jù)庫(kù)的相關(guān)資料,需要的朋友可以參考下2023-10-10Go標(biāo)準(zhǔn)庫(kù)日志打印及同時(shí)輸出到控制臺(tái)與文件
Go語(yǔ)言內(nèi)置的log包實(shí)現(xiàn)了簡(jiǎn)單的日志服務(wù),下面這篇文章主要給大家介紹了關(guān)于Go標(biāo)準(zhǔn)庫(kù)日志打印及同時(shí)輸出到控制臺(tái)與文件的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-11-11Golang?單元測(cè)試和基準(zhǔn)測(cè)試實(shí)例詳解
這篇文章主要為大家介紹了Golang?單元測(cè)試和基準(zhǔn)測(cè)試實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08使用Golang獲取音視頻時(shí)長(zhǎng)信息的示例代碼
這篇文章主要介紹了如何使用Golang獲取音視頻時(shí)長(zhǎng)信息,文中通過(guò)代碼示例講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-03-03GO語(yǔ)言求100以內(nèi)的素?cái)?shù)
這篇文章主要介紹了GO語(yǔ)言求100以內(nèi)的素?cái)?shù),主要通過(guò)篩選法來(lái)實(shí)現(xiàn),涉及GO語(yǔ)言基本的循環(huán)與函數(shù)調(diào)用方法,需要的朋友可以參考下2014-12-12