go如何優(yōu)雅關(guān)閉Graceful?Shutdown服務(wù)
Shutdown方法
Go1.8之后有了Shutdown方法,用來優(yōu)雅的關(guān)閉(Graceful Shutdown)服務(wù)。
func (srv *Server) Shutdown(ctx context.Context) error
這個(gè)方法會(huì)在調(diào)用時(shí)進(jìn)行下述操作:
1.關(guān)閉所有open listeners
2.關(guān)閉所有idle connections
3.無限期等待connections回歸idle狀態(tài)
4.之后關(guān)閉服務(wù)
注:3的無限期等待可以用context的超時(shí)來解決。
RegisterOnShutdown方法
Go1.9之后有了RegisterOnShutdown方法,用來在優(yōu)雅的關(guān)閉服務(wù)的同時(shí)處理一些退出任務(wù)。
func (srv *Server) RegisterOnShutdown(f func())
關(guān)于RegisterOnShutdown如何生效,我們看代碼。
type Server struct { ... onShutdown []func() } func (srv *Server) RegisterOnShutdown(f func()) { srv.mu.Lock() srv.onShutdown = append(srv.onShutdown, f) srv.mu.Unlock() } func (srv *Server) Shutdown(ctx context.Context) error { ... srv.mu.Lock() ... for _, f := range srv.onShutdown { go f() } srv.mu.Unlock() ... }
由代碼可知,在通過RegisterOnShutdown進(jìn)行注冊(cè)之后,這些函數(shù)被放入一個(gè)函數(shù)切片中,并在Shutdown被調(diào)用的時(shí)候,為每個(gè)函數(shù)啟動(dòng)了一個(gè)goroutine進(jìn)行處理。
但由于沒有進(jìn)行后續(xù)處理,于是這里有個(gè)小問題,Shutdown是不等待這些處理函數(shù)結(jié)束的,就可能比這些處理函數(shù)先完成并進(jìn)而結(jié)束程序。
package main import ( "context" "log" "net/http" "os" "os/signal" "time" ) func main() { var srv http.Server srv.RegisterOnShutdown(func() { time.Sleep(3 * time.Second) log.Println("Exit func") }) idleConnsClosed := make(chan struct{}) go func() { sigint := make(chan os.Signal, 1) signal.Notify(sigint, os.Interrupt) <-sigint // We received an interrupt signal, shut down. if err := srv.Shutdown(context.Background()); err != nil { // Error from closing listeners, or context timeout: log.Printf("HTTP server Shutdown: %v", err) } close(idleConnsClosed) }() if err := srv.ListenAndServe(); err != http.ErrServerClosed { // Error starting or closing listener: log.Fatalf("HTTP server ListenAndServe: %v", err) } <-idleConnsClosed }
運(yùn)行時(shí)Ctrl+c之后,服務(wù)立刻結(jié)束了,并未等待log.Println("Exit func")的輸出。
sync.WaitGroup處理退出函數(shù)
為了解決這個(gè)問題我們使用sync.WaitGroup來處理這些退出函數(shù),于是我們有了下面的代碼(解耦并增加了一些日志):
package main import ( "context" "log" "net/http" "os" "os/signal" "sync" "time" ) func main() { var srv http.Server var wg sync.WaitGroup register := func(f func()) { wg.Add(1) log.Println("Exit func register") srv.RegisterOnShutdown(func() { defer wg.Done() f() log.Println("Exit func done") }) } register(func() { time.Sleep(3 * time.Second) log.Println("Called on Shutdown") }) idleConnsClosed := make(chan struct{}) go func() { sigint := make(chan os.Signal, 1) signal.Notify(sigint, os.Interrupt) <-sigint // We received an interrupt signal, shut down. log.Println("Shutdown start") if err := srv.Shutdown(context.Background()); err != nil { // Error from closing listeners, or context timeout: log.Printf("HTTP server Shutdown: %v", err) } close(idleConnsClosed) }() if err := srv.ListenAndServe(); err != http.ErrServerClosed { // Error starting or closing listener: log.Fatalf("HTTP server ListenAndServe: %v", err) } <-idleConnsClosed log.Println("Shutdown finish") wg.Wait() }
我們?cè)赗egisterOnShutdown時(shí)使用了time.Sleep(3 * time.Second)來模擬一個(gè)長(zhǎng)時(shí)間的退出程序,并且在"Shutdown finish"之后進(jìn)行等待,等待退出程序完成任務(wù)。
執(zhí)行日志如下:
2023/05/12 16:48:53 Exit func register
^C2023/05/12 16:48:54 Shutdown start
2023/05/12 16:48:54 Shutdown finish
2023/05/12 16:48:57 Called on Shutdown
2023/05/12 16:48:57 Exit func done
可以看到這個(gè)服務(wù)本身很輕,Shutdown start之后立刻就finish并來到wg.Wait(),并等待register的函數(shù)完成之后再退出程序。
另外,如果在服務(wù)運(yùn)行的過程中需要啟動(dòng)一些其他的任務(wù),也可以使用
wg.Add(1) go func() { defer wg.Done() ... }
來保證任務(wù)在服務(wù)Shutdown之后可以順利完成。
以上就是go如何優(yōu)雅關(guān)閉Graceful Shutdown服務(wù)的詳細(xì)內(nèi)容,更多關(guān)于go關(guān)閉Graceful Shutdown的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Golang學(xué)習(xí)筆記之安裝Go1.15版本(win/linux/macos/docker安裝)
這篇文章主要介紹了Golang學(xué)習(xí)筆記之安裝Go1.15版本(win/linux/macos/docker安裝),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12golang實(shí)現(xiàn)整型和字節(jié)數(shù)組之間的轉(zhuǎn)換操作
這篇文章主要介紹了golang實(shí)現(xiàn)整型和字節(jié)數(shù)組之間的轉(zhuǎn)換操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12go使用SQLX操作MySQL數(shù)據(jù)庫的教程詳解
sqlx 是 Go 語言中一個(gè)流行的操作數(shù)據(jù)庫的第三方包,它提供了對(duì) Go 標(biāo)準(zhǔn)庫 database/sql 的擴(kuò)展,簡(jiǎn)化了操作數(shù)據(jù)庫的步驟,下面我們就來學(xué)習(xí)一下go如何使用SQLX實(shí)現(xiàn)MySQL數(shù)據(jù)庫的一些基本操作吧2023-11-11go?mod?tidy報(bào)錯(cuò):zip:?not?a?valid?zip?file解決辦法
這篇文章主要給大家介紹了關(guān)于go?mod?tidy報(bào)錯(cuò):zip:?not?a?valid?zip?file的解決辦法,go mod是進(jìn)行代碼管理,這錯(cuò)誤是因?yàn)楸镜胤种Ш瓦h(yuǎn)程分支沖突,本文通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-01-01