Go 編程:tcpserver & graceful shutdown

轉載請使用原文鏈接: https://www.gitdig.com/go-tcpserver-graceful-shutdown/

工作需要快速寫了個tcpserver的框架,有效代碼差不多 100 行左右,寫篇文章分享下實現思路, 順便解釋一下如何實現類似網絡服務的Graceful Shutdown功能。

首先,什麼是框架?簡單總結一下就是將變化交給用戶,將不變交給自己,即面向接口編程。所以要實現一個簡單的框架,第一步就是明確接口,對所有可能的變化進行接口級別的抽象。畫一張簡圖說明下:

圖片描述

tcpserver 框架的實現

tcpserver框架程序而言,變化的部分非常有限:

  • 監聽類型
  • 監聽地址
  • 客戶端連接處理

那麼,就可以將這三部分作爲tcpserver的可選參數傳遞進去。所以,在尚未實現之前, README 文檔中的 Quick Start 部分可以寫起來了。

import "github.com/x-mod/tcpserver"

srv := tcpserver.NewServer(
    tcpserver.Network("tcp"),
    tcpserver.Address(":8080"),
    tcpserver.Handler(MyHandler),
)

從這個簡單的創建函數可知,tcpserver.Handler選項傳入的是一個函數對象。既然是一個函數對象,就需要對其類型進行定義。通常而言,在函數類型定義時必須要注意的是參數問題,即參數不能過於複雜,同時必要的參數一個不能少。我這樣定義接口函數:

type ConnectionHandler func(context.Context, net.Conn) error

函數簽名中最重要的參數是 net.Conn 參數,不論使用方如何實現,這個參數是必須的。所以作爲獨立的參數,直接定義。另外一個參數,上下文參數context.Context。上下文的特點與用途,無需多說了。通過它,可以傳遞各種變量信息與上層的中斷信號。這樣也就解決了參數定義不足的問題。

現在,再看看 tcpserver.NewServer 還缺少什麼?顯然就是啓動函數與關閉函數了。參考 http.Server 的處理方式。在使用上,可以這樣定義操作函數:


func (srv *Server) Serve(ctx context.Context) error {
    //TODO
    return nil
}

func (srv *Server) Close() {
    //TODO
}

具體實現,可以直接參考我的項目代碼tcpserver

接下來簡單說下,類似服務端程序如何實現Graceful Shutdown功能。

Graceful Shutdown 功能的實現

Graceful Shutdown的概念早就有了,只是在Go語言早期的版本中沒有受到重視。好像是context包被移入系統包開始,Graceful Shutdown就開始被重視起來。爲什麼是從context包被移入系統包開始受到重視,還真是有一定聯繫。

什麼是Graceful Shutdown,不妨看看官方在net/http包中的說明:

Shutdown gracefully shuts down the server without interrupting any active connections. Shutdown works by first closing all open listeners, then closing all idle connections, and then waiting indefinitely for connections to return to idle and then shut down. If the provided context expires before the shutdown is complete, Shutdown returns the context's error, otherwise it returns any error returned from closing the Server's underlying Listener(s).

Graceful Shutdown的具體操作步驟如下:

  • 首先關閉所有監聽接口
  • 再關閉所有閒置連接
  • 等待所有運行連接處理完成
  • 上下文超時信號的處理

這樣關閉服務程序的好處很明顯,當然也有弊端:萬一個別連接無法完成,程序可能就會一直等待。所以客戶端處理邏輯上需要考慮這種異常情況,通過超時機制進行規避。

對於tcpserver而言,同樣需要提供Graceful Shutdown功能。看看怎麼來實現:

通過上節中 Server.Close 函數來觸發 Graceful Shutdown。收到觸發信號後,首先關閉監聽,再等待所有客戶端連接完成。具體程序實現如下:

//Close tcpserver waiting all connections finished
func (srv *Server) Close() {
    //觸發關閉信號
    close(srv.closed)
    //等待客戶端連完成
    srv.wgroup.Wait()
}

//Serve tcpserver serving
func (srv *Server) Serve(ctx context.Context) error {
    ln, err := net.Listen(srv.network, srv.address)//開啓監聽
    ...
    for {
        select {
        case <-ctx.Done():
            return ctx.Err()
        case <-srv.closed: //收到關閉信號
            log.Println("tcpserver is closing ...")
            return ln.Close()//關閉監聽
        ...
    }
    ...
}

貼一小段代碼方便大家閱讀,具體代碼請直接參考實現: server.go。爲什麼context很重要,因爲除了自己定義chan作爲信號觸發機制以外,還可以通過context的超時或者取消機制進行信號的傳遞,具體實現不再贅述了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章