首頁 > 軟體

Golang實現程式優雅退出的方法詳解

2022-06-15 18:01:56

1. 背景

專案開發過程中,隨著需求的迭代,程式碼的釋出會頻繁進行,在釋出過程中,如何讓程式做到優雅的退出?

為什麼需要優雅的退出?

  • 你的 http 服務,監聽埠沒有關閉,客戶的請求發過來了,但處理了一半,可能造成髒資料。
  • 你的協程 worker 的一個任務執行了一半,程式退出了,結果不符合預期。

如下我們以 http 服務,gRPC 服務,單獨的 woker 協程為例子,一步步說明平滑關閉的寫法。

2. 常見的幾種平滑關閉

為了解決退出可能出現的潛在問題,平滑關閉一般做如下一些事情

  • 關閉對外的監聽埠,拒絕新的連線
  • 關閉非同步執行的協程
  • 關閉依賴的資源
  • 等待如上資源關閉
  • 然後平滑關閉

2.1 http server 平滑關閉

原來的寫法

// 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())
    }
} 

帶平滑關閉的寫法

// startHttpServer start http server
func startHttpServer() {
    mux := http.NewServeMux()
    // mux.Handle("/metrics", promhttp.Handler())
    srv := &http.Server{
        Addr:    ":1608",
        Handler: mux,
    }
    // 註冊平滑關閉,退出時會呼叫 srv.Shutdown(ctx)
    quit.GetQuitEvent().RegisterQuitCloser(srv)
    if err := srv.ListenAndServe(); err != nil {
        log.Fatal("startHttpServer ListenAndServe error: " + err.Error())
    }
}

把平滑關閉註冊到http.Server的關閉函數中

// 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 平滑關閉

原來的寫法

// 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()
    // ...
}

帶平滑關閉的寫法 

// 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 協程平滑關閉

單獨的協程啟停,可以通過計數的方式註冊到退出事件處理器中。

1.啟動協程 增加計數

quit.GetQuitEvent().AddGoroutine()

2.停止協程 減計數 

quit.GetQuitEvent().DoneGoroutine()

3.常駐後臺執行的協程退出的條件改成退出事件是否結束的條件 

!quit.GetQuitEvent().HasFired()

4.常駐後臺執行的協程若通過 select 處理 chan,同時增加退出事件的chan

case <-quit.GetQuitEvent().Done()

// myWorker my worker
type myWorker struct {
}
 
// RunWorkerWithChan run Goroutine worker
func (m *myWorker) RunWorkerWithChan() {
    // 啟動一個Goroutine時,增加Goroutine數
    quit.GetQuitEvent().AddGoroutine()
    defer func() {
        // 一個Goroutine退出時,減少Goroutine數
        quit.GetQuitEvent().DoneGoroutine()
    }()
    // 退出時,此次退出
    for !quit.GetQuitEvent().HasFired() {
        select {
        // 退出時,收到退出訊號
        case <-quit.GetQuitEvent().Done():
            break
            //case msg := <- m.YouChan:
            // handle msg
        }
    }
}
 
// RunWorker run Goroutine worker
func (m *myWorker) RunWorker() {
    // 啟動一個Goroutine時,增加Goroutine數
    quit.GetQuitEvent().AddGoroutine()
    defer func() {
        // 一個Goroutine退出時,減少Goroutine數
        quit.GetQuitEvent().DoneGoroutine()
    }()
 
    // 退出時,此次退出
    for !quit.GetQuitEvent().HasFired() {
        // ...
    }
}

2.4 實現 io.Closer 介面的自定義服務平滑關閉

實現 io.Closer 介面的結構體,增加到退出事件處理器中 

// startMyService start my service
func startMyService() {
    srv := NewMyService()
    // 註冊平滑關閉,退出時會呼叫 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 整合其他框架怎麼做

退出訊號處理由某一框架接管,尋找框架如何註冊退出函數,優秀的框架一般都會實現安全實現退出的機制。

如下將退出事件註冊到某一框架的平滑關閉函數中

func startMyServer() {
    // ...
    // xxx框架退出函數註冊退出事件
    xxx.RegisterQuitter(func() {
        quit.GetQuitEvent().GracefulStop()
    })
}

以上就是Golang實現程式優雅退出的方法詳解的詳細內容,更多關於Golang程式退出的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com