問題:RPC 和多個(gè)服務(wù)端遠(yuǎn)程調(diào)用,每次都進(jìn)行 TCP 連接蚕泽,會(huì)對(duì) RPC 性能影響大晌梨。實(shí)際中桥嗤,要對(duì)連接進(jìn)行管理和保持。
解決辦法:用心跳包仔蝌、斷線重連實(shí)現(xiàn)泛领,結(jié)合系統(tǒng) tcp-keepalive 機(jī)制。
一敛惊、 連接管理(1. 長(zhǎng)連接和短連接? ?2. TCP 層 keep-alive? ?3. 應(yīng)用層 keep-alive? 4. 應(yīng)用層心跳還是 Keep-Alive)
二渊鞋、SOFARPC 如何實(shí)現(xiàn)
1.基于系統(tǒng) tcp-keepalive 機(jī)制實(shí)現(xiàn)
2.基于 Netty IdleStateHandler 心跳實(shí)現(xiàn)
3. SOFARPC 連接管理斷開重連實(shí)現(xiàn)
一、 連接管理
1. 長(zhǎng)連接和短連接
短連接:客戶端向服務(wù)端發(fā)起連接請(qǐng)求瞧挤。連接建立后锡宋,發(fā)送數(shù)據(jù),接收返回特恬,觸發(fā)連接斷開执俩,下次重復(fù)。
長(zhǎng)連接:則是在建立連接后癌刽,發(fā)送數(shù)據(jù)役首,接收數(shù)據(jù),不主動(dòng)斷開显拜,主動(dòng)通過心跳等機(jī)制來維持連接可用衡奥,下次無需重連。
? ? ? ? ? ? ??場(chǎng)景:長(zhǎng)發(fā)送頻繁远荠,點(diǎn)對(duì)點(diǎn)通訊矮固。TCP握手需要時(shí)間,跨城矮台,或長(zhǎng)距離時(shí)乏屯,處理速度會(huì)降很多。
PS:網(wǎng)絡(luò)設(shè)備防火墻瘦赫,可能導(dǎo)致連接斷開。需要管理長(zhǎng)連接蛤迎。
2. TCP 層 keep-alive
2.1 TCP 的 keep-alive 是什么
讓 TCP 連接“活著”确虱,或讓對(duì)方無響應(yīng)TCP 連接斷開,解決痛點(diǎn)場(chǎng)景是:
(1)兩個(gè)機(jī)器之間有防火墻替裆,防火墻自動(dòng)斷開長(zhǎng)期無活動(dòng) TCP 連接校辩。
(2)客戶端。斷電重啟辆童,卡死等等宜咒,導(dǎo)致 TCP 連接無法釋放。
導(dǎo)致:如熱數(shù)據(jù)需傳遞把鉴,連接已斷開故黑,應(yīng)用程序沒感知儿咱,在無效數(shù)據(jù)鏈路層面發(fā)送業(yè)務(wù)數(shù)據(jù),發(fā)送失敗场晶。
解決辦法:tcp-keepalive 連接無活動(dòng)一段時(shí)間混埠,發(fā)送空 ack,不會(huì)被防火墻關(guān)閉诗轻。
2.2 TCP 的 keep-alive 的默認(rèn)值
tcp-keepalive钳宪,需要自行開啟,三個(gè)參數(shù)生效扳炬,決定其行為吏颖。
net.ipv4.tcp_keepalive_time = 7200? ? ?TCP 保活打開情況恨樟,發(fā)送心跳的周期侦高,默認(rèn)值為7200s(2h)
net.ipv4.tcp_keepalive_probes = 9? ? ?沒有接收到對(duì)方確認(rèn),繼續(xù)發(fā)送毖岫牛活探測(cè)包次數(shù)奉呛,默認(rèn)值為 9(次)
net.ipv4.tcp_keepalive_intvl = 75? ??沒有接收到對(duì)方確認(rèn),繼續(xù)發(fā)送焙痪。活探測(cè)包的發(fā)送頻率瞧壮,默認(rèn)值為75s。
2.3 如何使用
?Java 的 Netty 為例匙握,服務(wù)端和客戶端設(shè)置即可咆槽。
ChannelOption.SO_KEEPALIVE, true 即打開,bolt默認(rèn)打開圈纺。
.childOption( ChannelOption.SO_KEEPALIVE, Boolean.parseBoolean( System.getProperty (Configs.TCP_SO_KEEPALIVE,"true") ) );
Java 只能設(shè)置 SO_KEEPALIVE 秦忿;TCP_KEEPCNT,TCP_KEEPIDLE蛾娶,TCP_KEEPINTVL 等參數(shù)配置灯谣,依賴于 sysctl 配置,系統(tǒng)進(jìn)行讀取蛔琅。
2.4 檢查
用 `netstat -no|grep keepalive` 命令來查看當(dāng)前哪些 tcp 連接開啟了 tcp keepalive.
3. 應(yīng)用層 keep-alive
應(yīng)用層 keep-alive 方案胎许,叫心跳包,跟 tcp-keepalive 類似罗售,間隔一定時(shí)間發(fā)送心跳數(shù)據(jù)辜窑,檢測(cè)對(duì)方是否連接,屬于應(yīng)用程序協(xié)議一部分寨躁。
3.1 心跳是什么
實(shí)現(xiàn)和 tcp keep-alive一樣穆碎,為什么要有應(yīng)用層心跳?
tcp keep-alive默認(rèn)2h,系統(tǒng)級(jí)別一旦更改职恳,影響所有服務(wù)器上開啟 keep alive應(yīng)用所禀。socks proxy 讓 tcp keep-alive 失效方面, socks 協(xié)議只管轉(zhuǎn)發(fā) TCP 層具體數(shù)據(jù)包,不轉(zhuǎn)發(fā)TCP 協(xié)議內(nèi)實(shí)現(xiàn)細(xì)節(jié)包(也做不到)北秽。
用 socks 代理葡幸,tcp keep-alive 失效,所以要自己心跳包贺氓。 socks proxy 只是一個(gè)例子蔚叨,有各種原因讓 tcp keep-alive 失效。
3.2 如何使用
基于 netty 開發(fā)的簡(jiǎn)單辙培。分析 rpc 中連接管理時(shí)候介紹蔑水。
4. 應(yīng)用層心跳還是 Keep-Alive
默認(rèn) keepalive 周期2h
4.1 系統(tǒng) keep-alive 優(yōu)勢(shì):
上層應(yīng)用只需要處理數(shù)據(jù)收發(fā)、連接異常通知扬蕊;
TCP 協(xié)議層面辈蟊穑活探測(cè)機(jī)制,系統(tǒng)內(nèi)核自動(dòng)替上層應(yīng)用做好尾抑;
內(nèi)核層面計(jì)時(shí)器相比上層應(yīng)用歇父,更為高效 ;
數(shù)據(jù)包更緊湊再愈;
4.2 應(yīng)用 keep-alive 優(yōu)勢(shì):
(1)關(guān)閉 TCP? keepalive榜苫,完全使用業(yè)務(wù)層面心跳保活機(jī)制翎冲;
(2)應(yīng)用的心跳包更靈活垂睬,可控制檢測(cè)間隔,方式等抗悍;
(3)適用于 TCP 和 UDP 驹饺,切換 TCP 和 UDP 時(shí),上層的心跳包功能都適用缴渊;
心跳包附帶:定時(shí)在服務(wù)端和客戶端之間同步(如幀數(shù)同步)赏壹;
所以大多數(shù)情況下,業(yè)務(wù)心跳 + TCP keepalive 互相補(bǔ)充疟暖。
二卡儒、SOFARPC 如何實(shí)現(xiàn)
1. SOFABOLT 基于系統(tǒng) tcp-keepalive 機(jī)制實(shí)現(xiàn)
直接打開 KeepAlive 選項(xiàng)即可
客戶端
RpcConnectionFactory 創(chuàng)建 RPC 連接,生成用戶觸發(fā)事件俐巴,init() 方法初始化 Bootstrap通過option()方法給每條連接設(shè)置 TCP屬性,ChannelOption.SO_KEEPALIVE 表示是否開啟 TCP 底層心跳機(jī)制硬爆,默認(rèn)打開 SO_KEEPALIVE 選項(xiàng)欣舵。
服務(wù)端
RpcServer 服務(wù)端啟動(dòng)類 ServerBootstrap 初始化通過 option() 方法給每條連接設(shè)置 TCP底層相關(guān)的屬性,默認(rèn)設(shè)置 ChannelOption.SO_KEEPALIVE 選項(xiàng)為 true缀磕,表示 RPC 連接開啟 TCP 底層心跳機(jī)制劣光。
2. SOFABOLT 基于 Netty IdleStateHandler 心跳實(shí)現(xiàn)
向 Netty 中注冊(cè)一個(gè)處理 Idle 事件的監(jiān)聽器。注冊(cè)時(shí)傳入 idle 產(chǎn)生事件糟把,讀 還是寫 IDLE绢涡,還是都有,多久沒有讀寫則認(rèn)為是 IDLE 等遣疯。
客戶端
(1)SOFABOLT 心跳檢測(cè)客戶端默認(rèn)基于 IdleStateHandler(15000ms, 150000 ms, 0) 即 15 秒沒有讀或者寫操作雄可,注冊(cè)給 Netty
(2)調(diào)用HeartbeatHandler 的?userEventTriggered()方法觸發(fā) RpcHeartbeatTrigger 發(fā)送心跳消息。
(3)RpcHeartbeatTrigger 心跳檢測(cè)判斷成功標(biāo)準(zhǔn)為是否接收到服務(wù)端回復(fù)成功響應(yīng)缠犀,如果心跳失敗次數(shù)超過最大心跳次數(shù)(默認(rèn)為 3 )則關(guān)閉連接
服務(wù)端
(1)SOFABOLT 心跳檢測(cè)服務(wù)端默認(rèn)基于?IdleStateHandler(0,0, 90000 ms) 即 90 秒沒有讀或者寫操作為空閑数苫,調(diào)用ServerIdleHandler的userEventTriggered() 方法觸發(fā)關(guān)閉連接。
(2)SOFABOLT 心跳檢測(cè)由客戶端在沒有對(duì) TCP 有讀或者寫操作后觸發(fā)定時(shí)發(fā)送心跳消息辨液,服務(wù)端接收到響應(yīng)虐急。客戶端 15 秒/服務(wù)端 90 秒心跳檢測(cè)滔迈,服務(wù)端不會(huì)運(yùn)行到 90 秒仍舊沒有任何讀寫操作的止吁,只有客戶端下線或拋異常時(shí)等待 90 秒過后服務(wù)端主動(dòng)關(guān)閉與客戶端連接。如果是 tcp-keepalive 需要等到 90秒之后燎悍,在此期間則為讀寫異常敬惦。
服務(wù)端一旦產(chǎn)生 IDLE,客戶端不可用间涵,直接斷連仁热。
3. SOFARPC 連接管理斷開重連實(shí)現(xiàn)
每次 RPC 調(diào)用過程都校驗(yàn)是否有可用連接,不需要斷鏈與重連勾哩,沒有則新建抗蠢。但一些場(chǎng)景是需要斷鏈和保持長(zhǎng)連接:
自動(dòng)斷連:通過 LVS VIP 或者 F5 建立多個(gè)連接,網(wǎng)絡(luò)設(shè)備負(fù)載均衡機(jī)制思劳,可能某些連接固定映射到了某幾臺(tái)后端的 RS 上迅矛,需要自動(dòng)斷連然后重連,靠隨機(jī)性實(shí)現(xiàn)終負(fù)載均衡潜叛。需配合重連使用秽褒。
重連:客戶端發(fā)起建連后由服務(wù)端通過雙工通信發(fā)起請(qǐng)求到客戶端,沒有重連無法實(shí)現(xiàn)威兜。
連接管理是客戶端邏輯销斟,啟動(dòng)好,連接管理開啟異步線程椒舵。
(1)SOFARPC 連接管理 ConnectionHolder 客戶端列表 aliveConnections(維護(hù)存活) 和 retryConnections(失敗待重試)蚂踊,RPC 啟動(dòng)守護(hù)線程以默認(rèn) 10 秒的間隔檢查存活和失敗待重試的客戶端列表的可用連接:
存活列表不可用則需要放到待重試列表 retryConnections ;遍歷retryConnections?連接命中重連周期則重連笔宿;重連成功放到存活列表?犁钟,多次失敗丟棄棱诱。
核心代碼在連接管理器的方法中:com.alipay.sofa.rpc.client.AllConnectConnectionHolder#doReconnect