Golang實(shí)現(xiàn)程序優(yōu)雅退出的方法詳解
1. 背景
項(xiàng)目開發(fā)過程中,隨著需求的迭代,代碼的發(fā)布會頻繁進(jìn)行,在發(fā)布過程中,如何讓程序做到優(yōu)雅的退出?
為什么需要優(yōu)雅的退出?
- 你的 http 服務(wù),監(jiān)聽端口沒有關(guān)閉,客戶的請求發(fā)過來了,但處理了一半,可能造成臟數(shù)據(jù)。
- 你的協(xié)程 worker 的一個(gè)任務(wù)運(yùn)行了一半,程序退出了,結(jié)果不符合預(yù)期。
如下我們以 http 服務(wù),gRPC 服務(wù),單獨(dú)的 woker 協(xié)程為例子,一步步說明平滑關(guān)閉的寫法。
2. 常見的幾種平滑關(guān)閉
為了解決退出可能出現(xiàn)的潛在問題,平滑關(guān)閉一般做如下一些事情
- 關(guān)閉對外的監(jiān)聽端口,拒絕新的連接
- 關(guān)閉異步運(yùn)行的協(xié)程
- 關(guān)閉依賴的資源
- 等待如上資源關(guān)閉
- 然后平滑關(guān)閉

2.1 http server 平滑關(guān)閉
原來的寫法
// 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)閉的寫法
// startHttpServer start http server
func startHttpServer() {
mux := http.NewServeMux()
// mux.Handle("/metrics", promhttp.Handler())
srv := &http.Server{
Addr: ":1608",
Handler: mux,
}
// 注冊平滑關(guān)閉,退出時(shí)會調(diào)用 srv.Shutdown(ctx)
quit.GetQuitEvent().RegisterQuitCloser(srv)
if err := srv.ListenAndServe(); err != nil {
log.Fatal("startHttpServer ListenAndServe error: " + err.Error())
}
}
把平滑關(guān)閉注冊到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,
}
// 把平滑退出注冊到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)閉
原來的寫法
// 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)閉的寫法
// 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注冊到退出事件中
quit.GetQuitEvent().RegisterStopFunc(grpcServer.GracefulStop)
quit.WaitSignal()
} 2.3 worker 協(xié)程平滑關(guān)閉
單獨(dú)的協(xié)程啟停,可以通過計(jì)數(shù)的方式注冊到退出事件處理器中。
1.啟動協(xié)程 增加計(jì)數(shù)
quit.GetQuitEvent().AddGoroutine()
2.停止協(xié)程 減計(jì)數(shù)
quit.GetQuitEvent().DoneGoroutine()
3.常駐后臺運(yùn)行的協(xié)程退出的條件改成退出事件是否結(jié)束的條件
!quit.GetQuitEvent().HasFired()
4.常駐后臺運(yùn)行的協(xié)程若通過 select 處理 chan,同時(shí)增加退出事件的chan
case <-quit.GetQuitEvent().Done()
// myWorker my worker
type myWorker struct {
}
// RunWorkerWithChan run Goroutine worker
func (m *myWorker) RunWorkerWithChan() {
// 啟動一個(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í),收到退出信號
case <-quit.GetQuitEvent().Done():
break
//case msg := <- m.YouChan:
// handle msg
}
}
}
// RunWorker run Goroutine worker
func (m *myWorker) RunWorker() {
// 啟動一個(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()
// 注冊平滑關(guān)閉,退出時(shí)會調(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 集成其他框架怎么做
退出信號處理由某一框架接管,尋找框架如何注冊退出函數(shù),優(yōu)秀的框架一般都會實(shí)現(xiàn)安全實(shí)現(xiàn)退出的機(jī)制。
如下將退出事件注冊到某一框架的平滑關(guān)閉函數(shù)中
func startMyServer() {
// ...
// xxx框架退出函數(shù)注冊退出事件
xxx.RegisterQuitter(func() {
quit.GetQuitEvent().GracefulStop()
})
}以上就是Golang實(shí)現(xiàn)程序優(yōu)雅退出的方法詳解的詳細(xì)內(nèi)容,更多關(guān)于Golang程序退出的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Golang實(shí)現(xiàn)web文件共享服務(wù)的示例代碼
這篇文章主要介紹了Golang實(shí)現(xiàn)web文件共享服務(wù)的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-10-10
一文帶你搞懂Golang結(jié)構(gòu)體內(nèi)存布局
結(jié)構(gòu)體在Go語言中是一個(gè)很重要的部分,在項(xiàng)目中會經(jīng)常用到。這篇文章主要帶大家看一下結(jié)構(gòu)體在內(nèi)存中是怎么分布的?通過對內(nèi)存布局的了解,可以幫助我們寫出更優(yōu)質(zhì)的代碼。感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助2022-10-10
golang使用sync.singleflight解決熱點(diǎn)緩存穿透問題
在go的sync包中,有一個(gè)singleflight包,里面有一個(gè)?singleflight.go文件,代碼加注釋,一共200行出頭,通過?singleflight可以很容易實(shí)現(xiàn)緩存和去重的效果,避免重復(fù)計(jì)算,接下來我們就給大家詳細(xì)介紹一下sync.singleflight如何解決熱點(diǎn)緩存穿透問題2023-07-07
go語言搬磚之go jmespath實(shí)現(xiàn)查詢json數(shù)據(jù)
這篇文章主要為大家介紹了go語言搬磚之go jmespath實(shí)現(xiàn)查詢json數(shù)據(jù),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06

