ETCD Client 的生命周期影響系統(tǒng) TCP 連接資源

最近發(fā)現(xiàn)一個(gè) ETCD Client 端的實(shí)現(xiàn)問題——ETCD 所在機(jī)器宕機(jī)或者斷網(wǎng)的情況下,ETCD Client 無法快速重連到可用的 etcd 節(jié)點(diǎn),導(dǎo)致 client 端不可用(該問題的描述后續(xù)發(fā)表文章介紹)。
后來找到一個(gè)比較簡(jiǎn)單的優(yōu)化方式,即臨時(shí)新創(chuàng)建一個(gè)新的 ETCD 的 Client 來重試操作,可以立即操作成功。但是每次遇到斷網(wǎng)錯(cuò)誤或者斷網(wǎng)時(shí)間比較長(zhǎng),那么這段時(shí)間內(nèi)所有的請(qǐng)求都要重新創(chuàng)建一個(gè)新的 ETCD Client 來重試嗎?頻繁創(chuàng)建 ETCD Client 對(duì)系統(tǒng)有什么影響?此外,還聯(lián)想到在使用 ETCD 初期的時(shí)候,請(qǐng)教過一個(gè)專家同學(xué),關(guān)于 ETCD Client 的使用上,全局使用一個(gè) ETCD Client,還是在需要使用的模塊內(nèi)部使用獨(dú)立的 Client,這兩種方式哪個(gè)更為合理?
今天,就簡(jiǎn)單的為自己解答一下這幾個(gè)問題哈。本文主要是做一些簡(jiǎn)單的調(diào)研和基礎(chǔ)知識(shí)的分析哈,引出 ETCD Client 的生命周期管理比較合理的方式。
普及知識(shí)
先來普及一些基本的概念,便于我們更好的研究和分析哈。ETCD_API=3,即 v3 Client。
gRPC 相關(guān)的概念
etcd clientv3 端是基于 gPRC 實(shí)現(xiàn)的。所以,這里先簡(jiǎn)單的描述一下 gRPC 的相關(guān)的基本內(nèi)容哈。
首先,計(jì)算機(jī)網(wǎng)絡(luò)的 7 層協(xié)議: 物理層、數(shù)據(jù)鏈路層、網(wǎng)絡(luò)層、傳輸層、會(huì)話層、表示層和應(yīng)用層,大家肯定都非常熟悉了。從協(xié)議上來說:
TCP[1] 是傳輸層協(xié)議,主要解決數(shù)據(jù)如何在網(wǎng)絡(luò)中傳輸,它解決了第四層傳輸層所指定的功能。 HTTP[2] 是應(yīng)用層協(xié)議,主要解決如何包裝數(shù)據(jù),是建立在 TCP 協(xié)議之上的應(yīng)用協(xié)議。因?yàn)?TCP 協(xié)議對(duì)上層應(yīng)用的不友好,所以面向應(yīng)用層的開發(fā)產(chǎn)生了 HTTP 協(xié)議。
RPC 是遠(yuǎn)程過程調(diào)用,它是一種設(shè)計(jì)、實(shí)現(xiàn)框架,通信協(xié)議只是其中一部分,所以他和 HTTP 并不是對(duì)立的,也沒有包含關(guān)系,本質(zhì)上是提供了一種輕量無感知的跨進(jìn)程通信的方式,通信協(xié)議可以使用 HTTP,也可以使用其他協(xié)議。關(guān)于為何有 HTTP 協(xié)議,為何還要在系統(tǒng)之后通信上使用 RPC 調(diào)用的原因,相信網(wǎng)上有很多論述,這里就不詳細(xì)描述了哈。gRPC 是谷歌開源的一個(gè) RPC 框架,面向移動(dòng)和 HTTP2 設(shè)計(jì)的。和很多 RPC 系統(tǒng)一樣,服務(wù)端負(fù)責(zé)實(shí)現(xiàn)定義好的接口并處理客戶端的請(qǐng)求,客戶端根據(jù)接口描述直接調(diào)用需要的服務(wù)??蛻舳撕头?wù)端可以分別使用 gPRC 支持的不同語(yǔ)言實(shí)現(xiàn)。HTTP2[3] 相對(duì)于 HTTP1.x 具有很多新特性,比如多路復(fù)用,即多個(gè) request 共用一個(gè) TCP 連接,其他特性這里不詳細(xì)敘述了。
TCP 短連接使用的問題
TCP 連接是網(wǎng)絡(luò)編程中最基礎(chǔ)的概念,這里就不詳細(xì)介紹 TCP 連接過程了。短連接最大的問題在占用大量的系統(tǒng)資源,例如,socket,而導(dǎo)致這個(gè)問題的原因其實(shí)很簡(jiǎn)單:tcp 連接的使用,都需要經(jīng)過相同的流程:連接建立 -> 數(shù)據(jù)傳輸 -> 連接關(guān)閉。
對(duì)于系統(tǒng)請(qǐng)求負(fù)載較高的情況下,系統(tǒng)出現(xiàn)的最多和最直觀的錯(cuò)誤應(yīng)該就是 "too many time wait"。這里簡(jiǎn)單說一下 socket 句柄被耗盡的原因,主要因?yàn)?TIME_WAIT 這種狀態(tài)的 TCP 連接的存在。
由于 socket 是全雙工的工作模式,一個(gè) socket 的關(guān)閉,是需要四次握手來完成的,如下圖所示:

主動(dòng)關(guān)閉連接的一方(成為主動(dòng)方),調(diào)用 close,然后發(fā)送 FIN 包給被動(dòng)方,表明自己已經(jīng)準(zhǔn)備關(guān)閉連接; 被動(dòng)方收到 FIN 包后,回復(fù) ACK ,然后進(jìn)入到 CLOSE_WAIT ; 主動(dòng)方等待對(duì)方關(guān)閉,則進(jìn)入 FIN_WAIT_2 狀態(tài);此時(shí),主動(dòng)方等待被動(dòng)方的調(diào)用 close() 操作; 被動(dòng)方在完成所有數(shù)據(jù)發(fā)送后,調(diào)用 close()操作;此時(shí),被動(dòng)方發(fā)送 FIN 包給主動(dòng)方,等待對(duì)方的 ACK,被動(dòng)方進(jìn)入 LAST_ACK 狀態(tài); 主動(dòng)方收到 FIN 包,協(xié)議層回復(fù) ACK ;此時(shí),主動(dòng)方進(jìn)入 TIME_WAIT 狀態(tài);而被動(dòng)方,進(jìn)入 CLOSED 狀態(tài) 等待 2MSL 時(shí)間,主動(dòng)方結(jié)束 TIME_WAIT ,進(jìn)入 CLOSED 狀態(tài)
通過上面的一次 socket 關(guān)閉操作,可以得出以下幾點(diǎn):
主動(dòng)方最終會(huì)進(jìn)入 TIME_WAIT 狀態(tài); 被動(dòng)方,有一個(gè)中間狀態(tài),即 CLOSE_WAIT,因?yàn)閰f(xié)議層在等待上層的應(yīng)用程序,主動(dòng)調(diào)用 close 操作后才主動(dòng)關(guān)閉這條連接; TIME_WAIT 會(huì)默認(rèn)等待 2MSL 時(shí)間后,才最終進(jìn)入 CLOSED 狀態(tài); 在一個(gè)連接沒有進(jìn)入 CLOSED 狀態(tài)之前,這個(gè)連接是不能被重用的!
所以,由上面的原理可以看出,TCP 連接的頻繁創(chuàng)建和關(guān)閉,會(huì)導(dǎo)致系統(tǒng)處于 TIME_WAIT 或者 CLOSE_WAIT 狀態(tài)的 TCP 連接變多,占用系統(tǒng)資源,影響正常的功能。
那么,下面我們看看,gRPC 的 Client 如果不合理的使用,會(huì)造成什么樣的問題呢?
gRPC Client 生命周期控制問題
寫個(gè)簡(jiǎn)單的 ETCD Client V3 的小程序,來看看頻繁的創(chuàng)建和關(guān)閉 ETCD Client 會(huì)有什么樣的影響,程序代碼如下:
// 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()
}
}
然后,我們用如下命令看看系統(tǒng)有什么變化,如下所示,不到一分鐘時(shí)間 TIME_WAIT 暴漲到了 16325 多個(gè)。
$ netstat -n| awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'


結(jié)論和建議
前面原理上已經(jīng)解釋過, ETCD Client v3 基于 gRPC 實(shí)現(xiàn),而 gRPC 采用的 HTTP2 協(xié)議,在傳輸層的協(xié)議依然是 tcp。如果對(duì) gRPC 的 Client 的生命周期設(shè)置的非常短,那么相當(dāng)于對(duì)這個(gè) TCP 連接資源轉(zhuǎn)化成了短連接,沒有發(fā)揮其核心功能。
所以,對(duì)于 ETCD Client 的使用,應(yīng)該充分利用其多路復(fù)用的原則,全局定義一個(gè) Client 變量,生命同期等同于進(jìn)程,以降低對(duì) TCP 資源的管理成本。
參考文章
你所不知道的 TIME_WAIT 和 CLODE_WAIT[4]
引用鏈接
TCP: https://tools.ietf.org/html/rfc793
[2]HTTP: https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol
[3]HTTP2: https://zh.wikipedia.org/wiki/HTTP/2
[4]你所不知道的 TIME_WAIT 和 CLODE_WAIT: https://blog.oldboyedu.com/tcp-wait/
原文鏈接:https://developer.aliyun.com/article/704034


你可能還喜歡
點(diǎn)擊下方圖片即可閱讀

云原生是一種信仰 ??
關(guān)注公眾號(hào)
后臺(tái)回復(fù)?k8s?獲取史上最方便快捷的 Kubernetes 高可用部署工具,只需一條命令,連 ssh 都不需要!


點(diǎn)擊 "閱讀原文" 獲取更好的閱讀體驗(yàn)!
發(fā)現(xiàn)朋友圈變“安靜”了嗎?


