reactor.netty.http.client.PrematureCloseException: Connection prematurely closed BEFORE response解決方案


一、背景

可能大家在使用Spring Cloud Gateway構建微服務網關的時候,過五關斬六將,Reactor沒能難倒我們,鏈路追蹤沒能難倒我們,最后在上線之后發現許多奇妙的問題,這些奇妙的問題還無從下手,比如這個堆棧,深入使用過SCG的人一定不會陌生:

reactor.netty.http.client.PrematureCloseException: Connection prematurely closed BEFORE response
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
1
2
類似的還有:
Connection prematurely closed DURING response 。。。等等

百度了一圈,鮮有人提供解決方案,有條件的Google了一把,跟着官方調整幾個參數,有用沒用也不得而知,最后反正就不了了之。

二、如何找答案
去SCG官方Issue中查找一番,還不少,(這里插一句,遇到問題可以先找前人的Issue,盡量不要提一些重復的問題,美德永存!)

然后發現了一句:

看來問題的根因不是在SCG,按照以往的經驗來看,Spencer Gibb老鐵是個實誠人,知之為知之,不知為不知,深得儒家精髓。召喚了Reactor-Netty的@violetagg,由此,我們知道了這個問題要去 https://github.com/reactor/reactor-netty/issues找答案了。其實題主也一樣,在Reactor-Netty下面也沒怎么搞明白這個錯誤的產生原理,正好全局異常處理器可以捕獲到這個異常,給調用方返回一個請求第三方出錯的統一結果,也不痛不癢。But,我是不會讓這種不明不白的問題程序上線的!其實仔細閱讀Reactor-Netty項目的Issue會總結出一些關鍵點,除了@violetagg指導大家如何如何切debug模式,如何如何找channel id之外,有一些總結性的話,我貼在下面,大家細品:

第一段:
I would recommend to configure maxIdleTime on the client side having in mind the keepAliveTimeout on Tomcat. Without such configuration Reactor Netty can receive the close event at any time between acquiring the connection from the pool and before actual sending of the request. Also you might want to switch to LIFO leasing strategy so that you will use always the most recently used connection.

第二段:
The connection is closed by Spring Framework WebClient’s new change for disposing the connection when a cancellation happens

第三段:
the connection was closed while still sending the request body

其實問題最本質的原因就是一個正常的請求在一些情況下被突然關閉了,題主也和大家一樣腦袋中也出現了更多的問號:“一些情況”是嘛情況?為什么會被關閉?這樣的問題出現頻率不高,如何有效復現?why???

在眾多的Issue中,你一定也會注意到,這個異常和Reactor-Netty內部的HttpClient有莫大的關系。

三、原因剖析
SCG官方文檔有說,設置請求第三方服務的連接超時和讀取超時實際上是設置的org.springframework.cloud.gateway.config.HttpClientProperties類屬性,接着挖下去,HttpClientProperties其實就是提高配置能力,為初始化reactor.netty.http.client.HttpClient做門面,其實這個配置類和你知道的HttpClient沒啥直接關系,它只是模擬出了類似HttpClient該有的一些機制,譬如連接池(使用過HttpClient的老鐵在線上出幺蛾子的時候一定也把玩過它的連接/線程池參數)機制,HttpClientProperties里面的pool屬性就是設置連接池相關的屬性的。

看到這里,你只需要知道,SCG的底層Reactor-Netty會為請求實例創建連接池,以便后面發起請求不用重新創建請求,直接從中獲取即可。其實這也就是問題的根因,看下面的時序圖你就明白了:

這里使用一個Spring Boot內置Tomcat作為服務提供方,用戶通過SCG訪問,SCG代理請求。
默認情況下,SCG內部創建的連接是不會被回收的,一直存在於內存中,而Spring Boot內置的Tomcat不一樣,默認在20s之后沒有數據交互,便會回收掉這個連接,在回收的時候恰巧碰到又來了請求,剛好又在SCG拿到這個連接來嘗試請求Tomcat,就會出現這個異常。
所以,不要指望在Reactor-Netty或是SCG中解決這個問題,這需要網關和后端服務配合解決,最大限度不出現這個異常。

四、解決方式
從上文的第一段原話就有解決方案:

第1步、加入JVM參數:
-Dreactor.netty.pool.leasingStrategy=lifo

第2步、SCG新增配置:
spring:
cloud:
gateway:
httpclient:
pool:
maxIdleTime: 10000(根據需要調整)
1
2
3
4
5
6
7
8
9
10
第1步將獲取連接策略由默認的FIFO變更為LIFO,因為LIFO能夠確保獲取的連接最大概率是最近剛被用過的,也就是熱點連接始終是熱點連接,而始終用不到的連接就可以被回收掉,LRU的思想。

第2步是設置空閑請求在空閑多久后會被回收,這樣也就可以避免拿到舊連接剛好在請求途中被強行close了,這個時間的設置只要確保比你后端服務的connectTimeout小就行了,這樣能夠確保SCG回收請求在后端服務回收請求之前,就可以避免掉這個問題。

這樣設置后還會偶發這個異常,請排查你的所有后端服務是否connectTimeout都比maxIdleTime大,或者嘗試調整maxIdleTime。另外,本身這是個概率性偶發問題,如果你的架構是題主舉的這個例子類似,題主這樣設置后,幾乎看不到這個異常出現了,徹底根除這個頑疾,請看懂時序圖再提問題。另外,如果你的架構不太一樣,你需要找到你的請求為什么在請求途中被突然關閉的原因,這可能不是Reactor-Netty的問題,而是你的服務的問題。

版本說明:
題主之前使用的SCG版本是Greenwich.SR2版本,對應的Spring Boot版本是2.1.6.RELEASE,這個版本對應的Reactor-Netty版本是v0.8.9.RELEASE,這個版本的Reactor-Netty是沒有提供設置maxIdleTime這個選項的。

Reactor-Netty是在v0.9.5.RELEASE版本開始提供設置

所以以上的配置請下面的版本當中使用:
Spring Cloud:Hoxton.SR1及以上(SCG 2.2.1.RELEASE及以上)
Reactor-Netty:v0.9.5.RELEASE及以上
Spring Boot:2.2.2.RELEASE及以上

注意:v0.9.6.RELEASE版本的maxIdleTime有個bug,可能不生效,需要升級到v0.9.7.RELEASE版本以上

單純使用Reactor-Netty的同學也可以在reactor.netty.resources.ConnectionProvider找到配置方式。

另外,v0.9.10.RELEASE版本做了連接提前關閉的重試機制,讓出現這個異常的幾率變得微乎其微
————————————————
版權聲明:本文為CSDN博主「Lovnx」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/rickiyeat/article/details/107900585


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM