GRPC連接池的設(shè)計與實現(xiàn)

本文介紹了Grpc連接池的設(shè)計,介紹了應(yīng)該如何配置連接池調(diào)優(yōu)。
前言
在分布式高并發(fā)服務(wù)器中,client到server以及server中的多個節(jié)點之間的連接往往使用連接池來管理。簡單來說就是將提前創(chuàng)建好的連接保存在池中,當(dāng)有請求到來時,直接使用連接池中的連接對server端訪問,省去了創(chuàng)建連接和銷毀連接的開銷(TCP建立連接時的三次握手和釋放連接時的四次揮手),從而提高了性能。
目錄
設(shè)計原則 基本原理 GRPC特性 GRPC調(diào)優(yōu) 實現(xiàn)細(xì)則 延伸閱讀
設(shè)計原則
連接池的擴縮容 空閑連接的超時與保活 池滿的處理機制
連接池的擴縮容
通常連接池屬性包含最大空閑連接數(shù)和最大活躍連接數(shù)。
最大空閑連接數(shù):連接池一直保持的連接數(shù),無論這些連接被使用與否都會被保持。如果客戶端對連接池的使用量不大,便會造成服務(wù)端連接資源的浪費。
最大活躍連接數(shù):連接池最多保持的連接數(shù),如果客戶端請求超過次數(shù),便要根據(jù)池滿的處理機制來處理沒有得到連接的請求。
擴容:當(dāng)請求到來時,如果連接池中沒有空閑的連接,同時連接數(shù)也沒有達(dá)到最大活躍連接數(shù),便會按照特定的增長策略創(chuàng)建新的連接服務(wù)該請求,同時用完之后歸還到池中,而不是關(guān)閉連接。
縮容:當(dāng)連接池一段時間沒有被使用,同時池中的連接數(shù)超過了最大空閑連接數(shù),那么便會關(guān)閉一部分連接,使池中的連接數(shù)始終維持在最大空閑連接數(shù)。
空閑連接的超時與保活
超時?如果連接沒有被客戶端使用的話,便會成為空閑連接,在一段時間后,服務(wù)端可能會根據(jù)自己的超時策略關(guān)閉空閑連接,此時空閑連接已經(jīng)失效,如果客戶端再使用失效的連接,便會通信失敗。為了避免這種情況發(fā)生,通常連接池中的連接設(shè)有最大空閑超時時間(最好略小于服務(wù)器的空閑連接超時時間),在從池中獲取連接時,判斷是否空閑超時,如果超時則關(guān)閉,沒有超時則可以繼續(xù)使用。
保活?如果服務(wù)器發(fā)生重啟,那么連接池中的連接便會全部失效,如果此時再從池中獲取連接,不論獲取到哪一個,都將通信失敗。因此,連接池必須考慮連接的保活問題,有兩種解決方法:
1、連接池設(shè)置一個Ping函數(shù),專門用來做連接的保活。在從池中獲取連接的時候,Ping一下服務(wù)器,如果得到響應(yīng),則連接依然有效,便可繼續(xù)使用,如果超時無響應(yīng),則關(guān)閉該連接,生成新的連接,由于每次都要Ping一下,必然會增加延遲。也可以后臺用一個線程或者協(xié)程定期的執(zhí)行Ping函數(shù),進行連接的保活,缺點是感知連接的失效會有一定的延遲,從池中仍然有可能獲取到失效的連接。
2、客戶端加入相應(yīng)的重試機制。比如重試3次,前兩次從池中獲取連接執(zhí)行,如果報的錯是失效的連接等有關(guān)連接問題的錯誤,那么第3次從池中獲取的時候帶上參數(shù),指定獲取新建的連接,同時連接池移除前兩次獲取的失效的連接。
池滿的處理機制
連接池不可能無限的容納連接,當(dāng)池滿時,有兩種處理機制:
1、池新建連接,并返回給客戶端,當(dāng)客戶端用完時,如果池滿則關(guān)閉連接,否則放入池中。
2、設(shè)置一定的超時時間來等待空閑連接。需要客戶端加入重試機制,避免因超時之后獲取不到空閑連接產(chǎn)生的錯誤。
基本原理
服務(wù)啟動時建立連接池。 初始化連接池,建立最大空閑連接數(shù)個連接。 請求到來時,從池中獲取一個連接。如果沒有空閑連接且連接數(shù)沒有達(dá)到最大活躍連接數(shù),則新建連接;如果達(dá)到最大活躍連接數(shù),設(shè)置一定的超時時間,等待獲取空閑連接。 獲取到連接后進行通信服務(wù)。 釋放連接,此時是將連接放回連接池,如果池滿則關(guān)閉連接。 釋放連接池,關(guān)閉所有連接。
GRPC特性
關(guān)于GRPC的介紹,不在這里闡述,可閱讀深入了解GRPC協(xié)議,也可自行Google。這里主要簡要說明GRPC的兩個特性:多路復(fù)用、超時重連。
多路復(fù)用GRPC使用HTTP/2作為應(yīng)用層的傳輸協(xié)議,HTTP/2會復(fù)用底層的TCP連接。每一次RPC調(diào)用會產(chǎn)生一個新的Stream,每個Stream包含多個Frame,F(xiàn)rame是HTTP/2里面最小的數(shù)據(jù)傳輸單位。同時每個Stream有唯一的ID標(biāo)識,如果是客戶端創(chuàng)建的則ID是奇數(shù),服務(wù)端創(chuàng)建的ID則是偶數(shù)。如果一條連接上的ID使用完了,Client會新建一條連接,Server也會給Client發(fā)送一個GOAWAY Frame強制讓Client新建一條連接。一條GRPC連接允許并發(fā)的發(fā)送和接收多個Stream,而控制的參數(shù)便是MaxConcurrentStreams,Golang的服務(wù)端默認(rèn)是100。
超時重連我們在通過調(diào)用Dial或者DialContext函數(shù)創(chuàng)建連接時,默認(rèn)只是返回ClientConn結(jié)構(gòu)體指針,同時會啟動一個Goroutine異步的去建立連接。如果想要等連接建立完再返回,可以指定grpc.WithBlock()傳入Options來實現(xiàn)。超時機制很簡單,在調(diào)用的時候傳入一個timeout的context就可以了。重連機制通過啟動一個Goroutine異步的去建立連接實現(xiàn)的,可以避免服務(wù)器因為連接空閑時間過長關(guān)閉連接、服務(wù)器重啟等造成的客戶端連接失效問題。也就是說通過GRPC的重連機制可以完美的解決連接池設(shè)計原則中的空閑連接的超時與保活問題。
以Golang的GRPC客戶端為例:

GRPC調(diào)優(yōu)
GRPC默認(rèn)的參數(shù)對于傳輸大數(shù)據(jù)塊來說不夠友好,我們需要進行特定參數(shù)的調(diào)優(yōu)。
MaxSendMsgSizeGRPC最大允許發(fā)送的字節(jié)數(shù),默認(rèn)4MiB,如果超過了GRPC會報錯。Client和Server我們都調(diào)到4GiB。
MaxRecvMsgSizeGRPC最大允許接收的字節(jié)數(shù),默認(rèn)4MiB,如果超過了GRPC會報錯。Client和Server我們都調(diào)到4GiB。
InitialWindowSize基于Stream的滑動窗口,類似于TCP的滑動窗口,用來做流控,默認(rèn)64KiB,吞吐量上不去,Client和Server我們調(diào)到1GiB。
InitialConnWindowSize基于Connection的滑動窗口,默認(rèn)16 * 64KiB,吞吐量上不去,Client和Server我們也都調(diào)到1GiB。
KeepAliveTime每隔KeepAliveTime時間,發(fā)送PING幀測量最小往返時間,確定空閑連接是否仍然有效,我們設(shè)置為10S。
KeepAliveTimeout超過KeepAliveTimeout,關(guān)閉連接,我們設(shè)置為3S。
PermitWithoutStream如果為true,當(dāng)連接空閑時仍然發(fā)送PING幀監(jiān)測,如果為false,則不發(fā)送忽略。我們設(shè)置為true。
實現(xiàn)細(xì)則
代碼:https://github.com/shimingyah/pool
基于GRPC的多路復(fù)用、超時重連特性,我們很容易實現(xiàn)GRPC連接池。
接口設(shè)計
提供簡潔的Pool和Conn的接口設(shè)計。


連接復(fù)用
GRPC是支持多路復(fù)用的,所以在設(shè)計GRPC池的時候和其他連接池區(qū)別之一是支持連接復(fù)用,通過MaxConcurrentStreams控制,默認(rèn)64。我們稱單個的GRPC為物理連接,復(fù)用的連接為邏輯連接。池的實際有效連接邏輯連接=物理連接?*?MaxConcurrentStreams。
擴縮容
擴容初始化池的有效連接數(shù)(邏輯連接)為:最大空閑連接數(shù)?*?MaxConcurrentStreams,每一次請求都會對池的引用計數(shù)原子++,同時hash求取選取連接,當(dāng)引用計數(shù)超過邏輯連接數(shù)時,就需要進行擴容了,如果最大空閑連接沒有達(dá)到最大活躍連接數(shù),則按照double的方式擴容,如果達(dá)到了最大活躍連接數(shù),我們會根據(jù)Reuse參數(shù)的值來做進一步操作:如果為true,則繼續(xù)使用池中的連接,即使用的是物理連接的邏輯連接,關(guān)閉連接時,對引用計數(shù)原子--即可,如果為false,則新建連接,關(guān)閉連接時還需要對連接進行真正的Close。
縮容如果池的引用計數(shù)為0時,便會觸發(fā)縮容操作,是連接維持到最大空閑連接數(shù)。
超時保活
基于GRPC的Keepalived特性,我們不需要自己實現(xiàn)保活機制,也無需關(guān)注連接池中的連接是否有效,因為就算失效,GRPC會自動重連的,此時只不過耗時會略微增加,即認(rèn)為除了服務(wù)器一直處于down狀態(tài)等原因,連接池中的連接是始終有效的。
Tips
由于使用hash求余,每個GRPC上并發(fā)的Stream可能會超過MaxConcurrentStreams。 不同場景對應(yīng)的連接池配置也不一樣,需要根據(jù)自己的場景壓測得出連接池的最佳參數(shù)配置。 該連接池適用于客戶端對消息順序沒有要求的場景;如果客戶端對消息順序要求嚴(yán)格,即同一個客戶端發(fā)送消息的順序必須和服務(wù)端接收消息的順序一致,那么便需要做消息的保序處理了,因為單個GRPC的消息是有序的,但是多個GRPC的消息就不一定有序了。可能需要引入一個會話層Session,可以對應(yīng)一條鏈接,也可以對應(yīng)一個連接池的多條鏈接,并且在該層實現(xiàn)消息的有序處理。
轉(zhuǎn)自:zhuanlan.zhihu.com/p/104887882
文章轉(zhuǎn)載:Go開發(fā)大全
(版權(quán)歸原作者所有,侵刪)

點擊下方“閱讀原文”查看更多
