淺談springcloud gateway 連接保活問題
項目中使用了springcloud gateway作為網(wǎng)關(guān),上游與負載均衡服務(wù)器連接。
近期通過監(jiān)控系統(tǒng)觀察,發(fā)現(xiàn)網(wǎng)關(guān)與上游負載均衡服務(wù)器保持的TCP連接有300+,初步懷疑是調(diào)用方未釋放連接
用如下方法進行分析:
1)周期性采集當前建立的連接及端口數(shù)據(jù)
首先是每隔10分鐘連續(xù)采集2兩個小時,發(fā)現(xiàn)在兩個小時之內(nèi)新出現(xiàn)的端口不到12個,再逐步縮短采樣周期,到最后每秒采集一次,分析發(fā)現(xiàn)每秒種建立一個連接,同時關(guān)閉一個連接,當仍存在300+連接,這些連接對應(yīng)的端口稱為不活躍端口,記錄下這300+不活躍端口。
2)為了進一步分析,用whireshark抓包
發(fā)現(xiàn)絕大部分情況下都是正常的連接和關(guān)閉,但這300+個不活躍端口對應(yīng)的連接上沒有任何數(shù)據(jù),這300+個不活躍對應(yīng)的連接稱為不活躍連接。同步趕緊上馬接口調(diào)用實時監(jiān)控功能,發(fā)現(xiàn)實際的調(diào)用數(shù)量卻非常少(每分鐘不足10個)。
3)與上游的負載均衡工程師一起檢查
從負載均衡服務(wù)器看到的活躍連接也是個位數(shù),并且并未找到在網(wǎng)關(guān)上的不活躍端口。也就是說在負載均衡服務(wù)器已經(jīng)已經(jīng)拆除了與網(wǎng)關(guān)上的不活躍連接對應(yīng)的連接。咨詢負載均衡工程師,負載均衡設(shè)備對于1超過1個小時的不活躍連接會主動拆除。
經(jīng)過以上分析,確定是外部系統(tǒng)經(jīng)過負載均衡設(shè)備與網(wǎng)關(guān)建立連接后,并未進行任何操作,但網(wǎng)關(guān)會一直維護這個連接,導(dǎo)致網(wǎng)關(guān)的連接數(shù)持續(xù)上升。
為解決這個問題,需要首先回顧一下傳統(tǒng)的TCP長連接維護機制
針對長連接的維護,傳統(tǒng)的TCP服務(wù)采用心跳來維持,比如服務(wù)端每分鐘發(fā)送一個心跳報文,并啟動計數(shù)器并設(shè)置為1,客戶端收到后回應(yīng)一個報文,服務(wù)端收到回復(fù)報文后重置計數(shù)器,如果為收到應(yīng)答,則一分鐘再發(fā)送一個心跳報文,同時計數(shù)器加1,連續(xù)發(fā)送三個心跳報文并且未收到映帶,則服務(wù)端則認為客戶端已經(jīng)失聯(lián),會主動拆除這個連接,以避免不必要的資源占用。
我們現(xiàn)在使用的springcloud gateway,顯然很難直接修改源碼增加以上的心跳機制,所以我又想到了操作系統(tǒng)協(xié)議棧的連接?;顧C制。
TCP協(xié)議棧的?;顧C制與應(yīng)用層的長連接維護機制類似(當然,應(yīng)用層的TCP長連接維護機制就是從協(xié)議棧的保護機制學(xué)習來的'&'),只不過是在協(xié)議棧層面完成,這樣避免了應(yīng)用層實現(xiàn)負載的長連接維護
?;顧C制如下:
1)服務(wù)器端判斷一個連接在指定的時間內(nèi)
(缺省為2小時)沒有任何數(shù)據(jù),則發(fā)送一個探測報文,并啟動定時器
2)如果客戶端在正常運行并且網(wǎng)絡(luò)可達
則客戶端則回復(fù)一個響應(yīng)報文,服務(wù)端認為客戶端正常,則重新開始計時。
如果客戶端主機崩潰或網(wǎng)絡(luò)不可達,服務(wù)端將收不到應(yīng)答,定時器超時后(一般為75秒),服務(wù)端將再次發(fā)送探測報文,如此連續(xù)發(fā)送若干次(一般為10次),如果均未收到應(yīng)答,則服務(wù)端將主動關(guān)閉連接。
當然,如果中間有任何一次服務(wù)端收到應(yīng)答,則認為連接正常,不再發(fā)送探測報文。
使用如下命令可以查看以上保活時間、發(fā)送探測報文的間隔和次數(shù):
#sysctl -a|grep keepalive net.ipv4.tcp_keepalive_time = 7200(單位為秒) net.ipv4.tcp_keepalive_probes = 9 net.ipv4.tcp_keepalive_intvl = 75 (單位為秒)
關(guān)于保活參數(shù)中兩個小時的時間設(shè)置存在爭議,通常人們希望這個值可以小很多,比如分鐘級,但?;铋g隔時間是系統(tǒng)級別的變量,如果改變該值會影響所有使用該功能的用戶。
所以,Host Requirements RFC提出一個實現(xiàn)方式,?;铋g隔是可配置的,但缺省不小于兩個小時,并且需要應(yīng)用程序設(shè)置才啟用。
如果使用協(xié)議棧的?;罟δ?,那么缺省的兩個小時的時間還是太長,如果縮短這個時間會有什么影響,并無把握。
所以還是先想其他辦法,從網(wǎng)上看到可以通過以下代碼修改網(wǎng)關(guān)對長連接的維護辦法,以下代碼是設(shè)置?;顣r間為3分鐘,如果3分鐘內(nèi)連接上沒有數(shù)據(jù),網(wǎng)關(guān)將主動關(guān)閉連接:
配置文件:
server: netty: idie-timeout: 300 @Configuration public class NettyConfig { @Bean publiWebServerFactoryCustomizer<NettyReactiveWebServerFactory> idleTimeoutCustomizer( @Value("${server.netty.idle-timeout}") Duration idleTimeout) { return factory -> factory.addServerCustomizers( server -> server.tcpConfiguration( tcp->tcp.bootstrap( bootstrap->bootstrap.childHandler(new ChannelInitializer<Channel>() { @Override protected void initChannel(Channel channel) { channel.pipeline().addLast( new IdleStateHandler(0, 0, idleTimeout.toNanos(), NANOSECONDS) { private final AtomicBoolean closed = new AtomicBoolean(); @Override protected void channelIdle( ChannelHandlerContext ctx, IdleStateEvent evt) { if (closed.compareAndSet(false, true)) { ctx.close(); } } } ); } })))); } }
系統(tǒng)上線后,通過監(jiān)控系統(tǒng)發(fā)現(xiàn)網(wǎng)關(guān)連接數(shù)并未持續(xù)增長,剛松一口氣,線上業(yè)務(wù)系統(tǒng)頻頻報錯,請求網(wǎng)關(guān)失敗,趕緊安排網(wǎng)絡(luò)抓包,然后馬上回退恢復(fù)業(yè)務(wù)。
然后對網(wǎng)絡(luò)抓包進行分析,截圖如下:
從抓包結(jié)果來看,客戶端和網(wǎng)關(guān)經(jīng)過3次握手后,建立了連接,但后面的建立SSL的過程中,網(wǎng)關(guān)返回了400 Bad Request,所以導(dǎo)致業(yè)務(wù)系統(tǒng)請求失?。I(yè)務(wù)系統(tǒng)使用https請求網(wǎng)關(guān)),懷疑是上面的代碼中的配置覆蓋了配置文件中SSL的相關(guān)配置,所以導(dǎo)致SSL連接未建立。
我們優(yōu)秀的工程師,本著鍥而不舍的精神對gateway進行源碼分析,經(jīng)過對代碼的分析,發(fā)現(xiàn)確實是這個配置覆蓋了原有的SSL配置,導(dǎo)致SSL配置未生效所致,所以對以上代碼進行改寫,具體如下:
@Configuration public class NettyConfig { @Bean publiWebServerFactoryCustomizer<NettyReactiveWebServerFactory> idleTimeoutCustomizer( @Value("${server.netty.idle-timeout}") Duration idleTimeout) { return factory -> factory.addServerCustomizers( server -> server.tcpConfiguration( tcp->tcp.bootstrap(bootstrap->{ //增加如下代碼,從而可保持原有配置并追加?;? BootstrapHandlers.updateConfiguration(bootstrap, "IdleStateHandler", (connectionObserver, channel) ->{ channel.pipeline().addLast(new IdleStateHandler(0, 0, idleTimeout.toNanos(), NANOSECONDS) { private final AtomicBoolean closed = new AtomicBoolean(); @Override protected void channelIdle(ChannelHandlerContext ctx, IdleStateEvent evt) { if (closed.compareAndSet(false, true)) { ctx.close(); } } }); }); return bootstrap; } ))); } }
進行測試驗證,一切OK!
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java實現(xiàn)多級表頭和復(fù)雜表頭的導(dǎo)出功能
這篇文章主要為大家詳細介紹了Java實現(xiàn)多級表頭和復(fù)雜表頭的導(dǎo)出功能的相關(guān)知識,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學(xué)習一下2024-03-03SpringBoot接收參數(shù)所有方式總結(jié)
這篇文章主要介紹了SpringBoot接收參數(shù)所有方式總結(jié),文中通過代碼示例和圖文結(jié)合的方式給大家介紹的非常詳細,對大家的學(xué)習或工作有一定的幫助,需要的朋友可以參考下2024-07-07JAVA構(gòu)造函數(shù)不能使用void關(guān)鍵字問題
這篇文章主要介紹了JAVA構(gòu)造函數(shù)不能使用void關(guān)鍵字問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03