Go語言如何在Web服務(wù)中實(shí)現(xiàn)優(yōu)雅關(guān)機(jī)
在構(gòu)建 Web 服務(wù)時(shí),我們往往會(huì)遇到一個(gè)棘手的問題:當(dāng)我們想要停止服務(wù)時(shí),如何確保正在處理的請(qǐng)求能夠順利完成,而不是突然中斷? 這種技術(shù)被稱為“優(yōu)雅關(guān)機(jī)”,它可以確保在服務(wù)關(guān)閉時(shí),所有的請(qǐng)求都被妥善處理。
在這篇文章中,我們將通過一個(gè)簡單的例子來演示如何在 Go 語言中使用 Gin 框架實(shí)現(xiàn)優(yōu)雅關(guān)機(jī)。
什么是優(yōu)雅關(guān)機(jī)?
優(yōu)雅的關(guān)機(jī)是指在關(guān)閉服務(wù)之前,先讓服務(wù)處理完當(dāng)前正在處理的請(qǐng)求,然后再關(guān)閉服務(wù)。這樣可以保證服務(wù)不會(huì)丟失請(qǐng)求,也不會(huì)影響到正在處理的請(qǐng)求。這種方式可以提高用戶體驗(yàn),防止服務(wù)中斷造成的數(shù)據(jù)丟失或不一致。 而執(zhí)行 Ctrl + C
或者 kill -2 pid
命令關(guān)閉服務(wù),是不會(huì)等待服務(wù)處理完請(qǐng)求的,這樣就會(huì)導(dǎo)致服務(wù)丟失請(qǐng)求。
如何實(shí)現(xiàn)優(yōu)雅的關(guān)機(jī)?
Go 1.8 版本之后,http.Server
內(nèi)置的 Shutdown()
方法就支持優(yōu)雅地關(guān)機(jī)。
代碼實(shí)現(xiàn)
我們來看一個(gè)具體的代碼示例,通過這個(gè)例子我們將展示如何實(shí)現(xiàn)優(yōu)雅關(guān)機(jī)。
package main import ( "context" "log" "net/http" "os" "os/signal" "syscall" "time" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() // 定義一個(gè)簡單的路由 router.GET("/ping", func(c *gin.Context) { // 模擬一個(gè)耗時(shí)操作,比如數(shù)據(jù)庫查詢或外部API調(diào)用 time.Sleep(5 * time.Second) c.JSON(http.StatusOK, gin.H{ "message": "pong", }) }) // 配置 HTTP 服務(wù)器 srv := &http.Server{ Addr: ":8080", Handler: router, } // 啟動(dòng)服務(wù)器 go func() { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("Failed to start server: %v", err) } }() // 等待中斷信號(hào)來優(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í)行 log.Println("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ù)),超過 5 秒就超時(shí)退出 if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server Shutdown: ", err) } log.Println("Server exiting") }
代碼解析
1. 路由定義和服務(wù)啟動(dòng)
router := gin.Default() router.GET("/ping", func(c *gin.Context) { time.Sleep(5 * time.Second) c.JSON(http.StatusOK, gin.H{ "message": "pong", }) })
首先,我們創(chuàng)建了一個(gè)簡單的 Gin 路由,并定義了一個(gè) /ping
接口。當(dāng)訪問這個(gè)接口時(shí),服務(wù)器會(huì)模擬一個(gè)耗時(shí) 5 秒的操作,然后返回一個(gè) JSON 響應(yīng)。這段代碼展示了一個(gè)可能需要優(yōu)雅關(guān)機(jī)的典型場(chǎng)景:服務(wù)器可能正在處理耗時(shí)的請(qǐng)求,如果此時(shí)直接關(guān)機(jī),請(qǐng)求會(huì)被中斷。
2. HTTP 服務(wù)器配置和啟動(dòng)
srv := &http.Server{ Addr: ":8080", Handler: router, } go func() { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("Failed to start server: %v", err) } }()
我們使用 http.Server
結(jié)構(gòu)體配置并啟動(dòng)了一個(gè) HTTP 服務(wù)器。服務(wù)器在一個(gè)單獨(dú)的 goroutine 中運(yùn)行,這樣主程序可以繼續(xù)執(zhí)行,而不必等待服務(wù)器啟動(dòng)完成。
3. 捕獲系統(tǒng)信號(hào)
quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit
為了實(shí)現(xiàn)優(yōu)雅關(guān)機(jī),我們需要捕獲系統(tǒng)信號(hào)。這里使用了 os/signal
包來監(jiān)聽 syscall.SIGINT
和 syscall.SIGTERM
信號(hào)。當(dāng)用戶按下 Ctrl+C
或者通過 kill
命令發(fā)送信號(hào)時(shí),這些信號(hào)會(huì)被捕獲并發(fā)送到 quit
通道,程序會(huì)隨即從阻塞狀態(tài)中恢復(fù),繼續(xù)執(zhí)行后續(xù)代碼。
4. 實(shí)現(xiàn)優(yōu)雅關(guān)機(jī)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server Shutdown: ", err) }
在捕獲到關(guān)機(jī)信號(hào)后,我們使用 http.Server
的 Shutdown
方法來實(shí)現(xiàn)優(yōu)雅關(guān)機(jī)。Shutdown
方法接受一個(gè) context
參數(shù),這個(gè) context
設(shè)置了一個(gè)超時(shí)時(shí)間。在這里,我們?cè)O(shè)置了一個(gè) 5 秒的超時(shí)時(shí)間,意味著服務(wù)器將在 5 秒內(nèi)等待未完成的請(qǐng)求處理完畢,然后關(guān)閉。如果超過了設(shè)定的超時(shí)時(shí)間,服務(wù)器將退出,程序也會(huì)正常結(jié)束。
如何驗(yàn)證優(yōu)雅關(guān)機(jī)的效果?
要驗(yàn)證優(yōu)雅關(guān)機(jī)的效果,可以按照以下步驟操作:
- 打開終端,運(yùn)行 go run gin_shutdown.go
- 打開瀏覽器,并訪問
http://127.0.0.1:8080/ping
此時(shí)瀏覽器應(yīng)該會(huì)白屏等待服務(wù)端返回響應(yīng) - 在剛剛打開的終端上迅速按下 Ctrl+C 命令,此時(shí)會(huì)自動(dòng)給程序發(fā)送 syscall.SIGINT 信號(hào)
- 此時(shí)程序并不會(huì)立即退出,而是會(huì)等上面的第 2 步的響應(yīng)返回之后再退出,從而實(shí)現(xiàn)優(yōu)雅關(guān)機(jī)的效果
總結(jié)
優(yōu)雅關(guān)機(jī)是構(gòu)建健壯 Web 服務(wù)的一個(gè)重要技術(shù)點(diǎn),它確保了在服務(wù)關(guān)閉時(shí)所有正在處理的請(qǐng)求都能被妥善完成。在本文中,我們通過 Gin 框架演示了如何在 Go 中實(shí)現(xiàn)優(yōu)雅關(guān)機(jī)。通過這種方式,我們可以提升用戶體驗(yàn),減少由于服務(wù)中斷導(dǎo)致的各種潛在問題。
到此這篇關(guān)于Go語言如何在Web服務(wù)中實(shí)現(xiàn)優(yōu)雅關(guān)機(jī)的文章就介紹到這了,更多相關(guān)Go優(yōu)雅關(guān)機(jī)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語言實(shí)現(xiàn)IP段范圍校驗(yàn)示例
這篇文章主要介紹了Go語言實(shí)現(xiàn)IP段范圍校驗(yàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09解決Go語言中高頻次和高并發(fā)下隨機(jī)數(shù)重復(fù)的問題
在Golang中,獲取隨機(jī)數(shù)的方法一般會(huì)介紹有兩種,一種是基于math/rand的偽隨機(jī),一種是基于crypto/rand的真隨機(jī),math/rand由于其偽隨機(jī)的原理,經(jīng)常會(huì)出現(xiàn)重復(fù)的隨機(jī)數(shù),導(dǎo)致在需要進(jìn)行隨機(jī)的業(yè)務(wù)出現(xiàn)較多的重復(fù)問題,所以本文給大家介紹了較好的解放方案2023-12-12使用gorm.Scopes函數(shù)實(shí)現(xiàn)復(fù)用查詢邏輯示例
這篇文章主要為大家介紹了使用gorm.Scopes函數(shù)實(shí)現(xiàn)復(fù)用查詢邏輯示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12GoLang調(diào)用鏈可視化go-callvis使用介紹
與鏈路追蹤(Tracing)不同,Tracing關(guān)注復(fù)雜的分布式環(huán)境中各個(gè)服務(wù)節(jié)點(diǎn)間的調(diào)用關(guān)系,主要用于服務(wù)治理。而我們本次探索的代碼調(diào)用鏈路則是代碼方法級(jí)別的調(diào)用關(guān)系,主要用于代碼設(shè)計(jì)2023-02-02golang 40行代碼實(shí)現(xiàn)通用協(xié)程池
golang協(xié)程機(jī)制很方便的解決了并發(fā)編程的問題,但是協(xié)程并不是沒有開銷的,所以也需要適當(dāng)限制一下數(shù)量。這篇文章主要介紹了golang 40行代碼實(shí)現(xiàn)通用協(xié)程池,需要的朋友可以參考下2018-08-08