Golang實(shí)現(xiàn)程序優(yōu)雅退出的方法詳解
1. 背景
項(xiàng)目開(kāi)發(fā)過(guò)程中,隨著需求的迭代,代碼的發(fā)布會(huì)頻繁進(jìn)行,在發(fā)布過(guò)程中,如何讓程序做到優(yōu)雅的退出?
為什么需要優(yōu)雅的退出?
- 你的 http 服務(wù),監(jiān)聽(tīng)端口沒(méi)有關(guān)閉,客戶(hù)的請(qǐng)求發(fā)過(guò)來(lái)了,但處理了一半,可能造成臟數(shù)據(jù)。
- 你的協(xié)程 worker 的一個(gè)任務(wù)運(yùn)行了一半,程序退出了,結(jié)果不符合預(yù)期。
如下我們以 http 服務(wù),gRPC 服務(wù),單獨(dú)的 woker 協(xié)程為例子,一步步說(shuō)明平滑關(guān)閉的寫(xiě)法。
2. 常見(jiàn)的幾種平滑關(guān)閉
為了解決退出可能出現(xiàn)的潛在問(wèn)題,平滑關(guān)閉一般做如下一些事情
- 關(guān)閉對(duì)外的監(jiān)聽(tīng)端口,拒絕新的連接
- 關(guān)閉異步運(yùn)行的協(xié)程
- 關(guān)閉依賴(lài)的資源
- 等待如上資源關(guān)閉
- 然后平滑關(guān)閉
2.1 http server 平滑關(guān)閉
原來(lái)的寫(xiě)法
// startHttpServer start http server func startHttpServer() { mux := http.NewServeMux() // mux.Handle("/metrics", promhttp.Handler()) if err := http.ListenAndServe(":1608", mux); err != nil { log.Fatal("startHttpServer ListenAndServe error: " + err.Error()) } }
帶平滑關(guān)閉的寫(xiě)法
// startHttpServer start http server func startHttpServer() { mux := http.NewServeMux() // mux.Handle("/metrics", promhttp.Handler()) srv := &http.Server{ Addr: ":1608", Handler: mux, } // 注冊(cè)平滑關(guān)閉,退出時(shí)會(huì)調(diào)用 srv.Shutdown(ctx) quit.GetQuitEvent().RegisterQuitCloser(srv) if err := srv.ListenAndServe(); err != nil { log.Fatal("startHttpServer ListenAndServe error: " + err.Error()) } }
把平滑關(guān)閉注冊(cè)到http.Server的關(guān)閉函數(shù)中
// startHttpServer start http server func startHttpServer() { mux := http.NewServeMux() // mux.Handle("/metrics", promhttp.Handler()) srv := &http.Server{ Addr: ":1608", Handler: mux, } // 把平滑退出注冊(cè)到http.Server中 srv.RegisterOnShutdown(quit.GetQuitEvent().GracefulStop) if err := srv.ListenAndServe(); err != nil { log.Fatal("startHttpServer ListenAndServe error: " + err.Error()) } }
2.2 gRPC server 平滑關(guān)閉
原來(lái)的寫(xiě)法
// startGrpcServer start grpc server func startGrpcServer() { listen, err := net.Listen("tcp", "0.0.0.0:9999") if err != nil { log.Fatalf("Failed to listen: %v", err) return } grpcServer := grpc.NewServer() // helloBoot.GrpcRegister(grpcServer) go grpcServer.Serve(listen) defer grpcServer.GracefulStop() // ... }
帶平滑關(guān)閉的寫(xiě)法
// startGrpcServer start grpc server func startGrpcServer() { listen, err := net.Listen("tcp", "0.0.0.0:9999") if err != nil { log.Fatalf("Failed to listen: %v", err) return } grpcServer := grpc.NewServer() // helloBoot.GrpcRegister(grpcServer) go grpcServer.Serve(listen) // 把 grpc 的GracefulStop注冊(cè)到退出事件中 quit.GetQuitEvent().RegisterStopFunc(grpcServer.GracefulStop) quit.WaitSignal() }
2.3 worker 協(xié)程平滑關(guān)閉
單獨(dú)的協(xié)程啟停,可以通過(guò)計(jì)數(shù)的方式注冊(cè)到退出事件處理器中。
1.啟動(dòng)協(xié)程 增加計(jì)數(shù)
quit.GetQuitEvent().AddGoroutine()
2.停止協(xié)程 減計(jì)數(shù)
quit.GetQuitEvent().DoneGoroutine()
3.常駐后臺(tái)運(yùn)行的協(xié)程退出的條件改成退出事件是否結(jié)束的條件
!quit.GetQuitEvent().HasFired()
4.常駐后臺(tái)運(yùn)行的協(xié)程若通過(guò) select 處理 chan,同時(shí)增加退出事件的chan
case <-quit.GetQuitEvent().Done()
// myWorker my worker type myWorker struct { } // RunWorkerWithChan run Goroutine worker func (m *myWorker) RunWorkerWithChan() { // 啟動(dòng)一個(gè)Goroutine時(shí),增加Goroutine數(shù) quit.GetQuitEvent().AddGoroutine() defer func() { // 一個(gè)Goroutine退出時(shí),減少Goroutine數(shù) quit.GetQuitEvent().DoneGoroutine() }() // 退出時(shí),此次退出 for !quit.GetQuitEvent().HasFired() { select { // 退出時(shí),收到退出信號(hào) case <-quit.GetQuitEvent().Done(): break //case msg := <- m.YouChan: // handle msg } } } // RunWorker run Goroutine worker func (m *myWorker) RunWorker() { // 啟動(dòng)一個(gè)Goroutine時(shí),增加Goroutine數(shù) quit.GetQuitEvent().AddGoroutine() defer func() { // 一個(gè)Goroutine退出時(shí),減少Goroutine數(shù) quit.GetQuitEvent().DoneGoroutine() }() // 退出時(shí),此次退出 for !quit.GetQuitEvent().HasFired() { // ... } }
2.4 實(shí)現(xiàn) io.Closer 接口的自定義服務(wù)平滑關(guān)閉
實(shí)現(xiàn) io.Closer 接口的結(jié)構(gòu)體,增加到退出事件處理器中
// startMyService start my service func startMyService() { srv := NewMyService() // 注冊(cè)平滑關(guān)閉,退出時(shí)會(huì)調(diào)用 srv.Close() quit.GetQuitEvent().RegisterCloser(srv) srv.Run() } // myService my service type myService struct { isStop bool } // NewMyService new func NewMyService() *myService { return &myService{} } // Close my service func (m *myService) Close() error { m.isStop = true return nil } // Run my service func (m *myService) Run() { for !m.isStop { // .... } }
2.5 集成其他框架怎么做
退出信號(hào)處理由某一框架接管,尋找框架如何注冊(cè)退出函數(shù),優(yōu)秀的框架一般都會(huì)實(shí)現(xiàn)安全實(shí)現(xiàn)退出的機(jī)制。
如下將退出事件注冊(cè)到某一框架的平滑關(guān)閉函數(shù)中
func startMyServer() { // ... // xxx框架退出函數(shù)注冊(cè)退出事件 xxx.RegisterQuitter(func() { quit.GetQuitEvent().GracefulStop() }) }
以上就是Golang實(shí)現(xiàn)程序優(yōu)雅退出的方法詳解的詳細(xì)內(nèi)容,更多關(guān)于Golang程序退出的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用golang實(shí)現(xiàn)在屏幕上打印進(jìn)度條的操作
這篇文章主要介紹了使用golang實(shí)現(xiàn)在屏幕上打印進(jìn)度條的操作,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03Golang開(kāi)發(fā)中如何解決共享變量問(wèn)題
Go提供了傳統(tǒng)通過(guò)共享變量,也就是共享內(nèi)存的方式來(lái)實(shí)現(xiàn)并發(fā)。這篇文章會(huì)介紹 Go提供的相關(guān)機(jī)制,對(duì)Golang共享變量相關(guān)知識(shí)感興趣的朋友一起看看吧2021-09-09深入理解Go語(yǔ)言設(shè)計(jì)模式之函數(shù)式選項(xiàng)模式
在 Go 語(yǔ)言中,函數(shù)選項(xiàng)模式(Function Options Pattern)是一種常見(jiàn)且強(qiáng)大的設(shè)計(jì)模式,用于構(gòu)建可擴(kuò)展、易于使用和靈活的 API,本文就來(lái)看看它的具體用法吧2023-05-05Go語(yǔ)言Gin框架前后端分離項(xiàng)目開(kāi)發(fā)實(shí)例
本文主要介紹了Go語(yǔ)言Gin框架前后端分離項(xiàng)目開(kāi)發(fā)工程化實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-11-11Go實(shí)現(xiàn)map轉(zhuǎn)json的示例詳解
這篇文章主要為大家詳細(xì)介紹了如何利用Go語(yǔ)言實(shí)現(xiàn)map轉(zhuǎn)json的功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-09-09