天天看點

ETCD Client 的生命周期影響系統TCP連接配接資源參考文章

最近發現一個 ETCD Client 端的實作問題——ETCD 所在機器當機或者斷網的情況下,ETCD Client 無法快速重連到可用的 etcd 節點,導緻 client 端不可用(該問題的描述後續發表文章介紹)。後來找到一個比較簡單的優化方式,即臨時新建立一個新的 ETCD 的 Client 來重試操作,可以立即操作成功。但是每次遇到斷網錯誤或者斷網時間比較長,那麼這段時間内所有的請求都要重新建立一個新的 ETCD Client 來重試嗎?頻繁建立 ETCD Client 對系統有什麼影響?此外,還聯想到在使用 ETCD 初期的時候,請教過一個專家同學,關于 ETCD Client 的使用上,全局使用一個 ETCD Client,還是在需要使用的子產品内部使用獨立的 Client,這兩種方式哪個更為合理?

今天,就簡單的為自己解答一下這幾個問題哈。本文主要是做一些簡單的調研和基礎知識的分析哈,引出 ETCD Client 的生命周期管理比較合理的方式。

普及知識

先來普及一些基本的概念,便于我們更好的研究和分析哈。ETCD_API=3,即 v3 Client。

gRPC 相關的概念

etcd clientv3 端是基于 gPRC 實作的。是以,這裡先簡單的描述一下 gRPC 的相關的基本内容哈。

首先,計算機網絡的 7 層協定: 實體層、資料鍊路層、網絡層、傳輸層、會話層、表示層和應用層,大家肯定都非常熟悉了。從協定上來說:

  • TCP 是傳輸層協定,主要解決資料如何在網絡中傳輸,它解決了第四層傳輸層所指定的功能。
  • HTTP 是應用層協定,主要解決如何包裝資料,是建立在 TCP 協定之上的應用協定。因為 TCP 協定對上層應用的不友好,是以面向應用層的開發産生了 HTTP 協定。

RPC 是遠端過程調用,它是一種設計、實作架構,通信協定隻是其中一部分,是以他和 HTTP 并不是對立的,也沒有包含關系,本質上是提供了一種輕量無感覺的跨程序通信的方式,通信協定可以使用 HTTP,也可以使用其他協定。關于為何有 HTTP 協定,為何還要在系統之後通信上使用 RPC 調用的原因,相信網上有很多論述,這裡就不較長的描述了哈。

gRPC 是谷歌開源的一個 RPC 架構,面向移動和 HTTP2 設計的。和很多 RPC 系統一樣,服務端負責實作定義好的接口并處理用戶端的請求,用戶端根據接口描述直接調用需要的服務。用戶端和服務端可以分别使用 gPRC 支援的不同語言實作。

HTTP2

相對于 HTTP1.x 具有很多新特性,比如多路複用,即多個 request 共用一個 TCP 連接配接,其他特性這裡不詳細叙述了。

TCP 短連接配接使用的問題

TCP 連接配接是網絡程式設計中最基礎的概念,這裡就不詳細介紹 TCP 連接配接過程了。短連接配接最大的問題在占用大量的系統資源,例如,socket,而導緻這個問題的原因其實很簡單:tcp 連接配接的使用,都需要經過相同的流程: 連接配接建立 -> 資料傳輸 -> 連接配接關閉。

對于系統請求負載較高的情況下,系統出現的最多和最直覺的錯誤應該就是 "too many time wait"。這裡簡單說一下 socket 句柄被耗盡的原因,主要因為 TIME_WAIT 這種狀态的 TCP 連接配接的存在。

由于 socket 是全雙工的工作模式,一個socket的關閉,是需要四次握手來完成的,如下圖所示:

ETCD Client 的生命周期影響系統TCP連接配接資源參考文章
  • 主動關閉連接配接的一方(成為主動方),調用 close,然後發送 FIN 包給被動方,表明自己已經準備關閉連接配接;
  • 被動方收到 FIN 包後,回複 ACK ,然後進入到 CLOSE_WAIT ;
  • 主動方等待對方關閉,則進入 FIN_WAIT_2 狀态;此時,主動方等待被動方的調用 close() 操作;
  • 被動方在完成所有資料發送後,調用close()操作;此時,被動方發送 FIN 包給主動方,等待對方的ACK,被動方進入 LAST_ACK 狀态;
  • 主動方收到 FIN 包,協定層回複 ACK ;此時,主動方進入 TIME_WAIT 狀态;而被動方,進入 CLOSED 狀态
  • 等待 2MSL 時間,主動方結束 TIME_WAIT ,進入 CLOSED 狀态

通過上面的一次 socket 關閉操作,可以得出以下幾點:

  • 主動方最終會進入 TIME_WAIT 狀态;
  • 被動方,有一個中間狀态,即 CLOSE_WAIT,因為協定層在等待上層的應用程式,主動調用 close 操作後才主動關閉這條連接配接;
  • TIME_WAIT 會預設等待 2MSL 時間後,才最終進入 CLOSED 狀态;
  • 在一個連接配接沒有進入 CLOSED 狀态之前,這個連接配接是不能被重用的!

是以,由上面的原理可以看出,TCP 連接配接的頻繁建立和關閉,會導緻系統處于 TIME_WAIT 或者 CLOSE_WAIT 狀态的 TCP 連接配接變多,占用系統資源,影響正常的功能。

那麼,下面我們看看,gRPC 的 Client 如果不合理的使用,會造成什麼樣的問題呢?

gRPC Client 生命周期控制問題

寫個簡單的 ETCD Client V3 的小程式,來看看頻繁的建立和關閉 ETCD Client 會有什麼樣的影響,程式代碼如下:

// golang

func TestNewETCDClient() {
    for {
        etcdClient, err := clientv3.New(clientv3.Config{
            Endpoints:   []string{"10.0.0.2:2379"},
            DialTimeout: 3 * time.Second,
        })

        if err != nil {
            logger.Errorf("new client failed due to %v", err)
            return
        }
        etcdClient.Close()
    }
}           

然後,我們用如下指令看看系統有什麼變化,如下所示,不到一分鐘時間 TIME_WAIT 暴漲到了 16325 多個。

netstat -n| awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'           
ETCD Client 的生命周期影響系統TCP連接配接資源參考文章
ETCD Client 的生命周期影響系統TCP連接配接資源參考文章

結論和建議

前面原理上已經解釋過, ETCD Client v3 基于 gRPC 實作,而 gRPC 采用的 HTTP2 協定,在傳輸層的協定依然是 tcp。如果對 gRPC 的 Client 的生命周期設定的非常短,那麼相當于對這個 TCP 連接配接資源轉化成了短連接配接,沒有發揮其核心功能。

是以,對于 ETCD Client 的使用,應該充分利用其多路複用的原則,全局定義一個 Client 變量,生命同期等同于程序,以降低對 TCP 資源的管理成本。

參考文章

繼續閱讀