糾錯:Feign 沒用 短連接


Feign 默認不是 短連接

瘋狂創客圈 Java 高並發【 億級流量聊天室實戰】實戰系列 【博客園總入口


瘋狂創客圈(筆者尼恩創建的高並發研習社群)Springcloud 高並發系列文章,將為大家介紹三個版本的 高並發秒殺:

一、版本1 :springcloud + zookeeper 秒殺

二、版本2 :springcloud + redis 分布式鎖秒殺

三、版本3 :springcloud + Nginx + Lua 高性能版本秒殺

以及有關Springcloud 幾篇核心、重要的文章

一、Springcloud 配置, 史上最全 一文全懂

二、Springcloud 中 SpringBoot 配置全集 , 收藏版

三、Feign Ribbon Hystrix 三者關系 , 史上最全 深度解析

四、SpringCloud gateway 詳解 , 史上最全

五、圖解:tomcat的maxConnections、maxThreads、acceptCount | 秒懂

前言

網上很多文章都說,Feign 默認采用短連接進行遠程調用,其實,這種結論是不對的。為什么呢? 下面細致的為大家來解讀。

Feign中默認情況下的長連接

Feign中,默認情況下,使用的是JDK1.8中的 HttpURLConnection 基礎連接類。該類的內部,使用了JDK1.8自帶的 HttpClient 請求客戶端類去負責完成底層的socket流操作。另外,JDK1.8還提供了一個簡單的長連接緩沖類 KeepAliveCache,實現HttpClient 請求客戶端類的緩存和復用。

三個類的所處位置為:HttpURLConnection 類處於 java.net 包中,而 HttpClient 類和KeepAliveCache類,則處於sun.net.www.http 包中。

HttpURLConnection、HttpClient、KeepAliveCache三個類的簡單關系為:

每個HTTP請求都是一個HttpURLConnection實例,每個請求都會有一個 HttpClient 客戶端實例,一個HttpClient 實例都持有一個TCP socket 長連接。如果 HttpClient 實例可以復用,則暫存在KeepAliveCache 緩存實例中。HttpURLConnection 會優先從緩存中取得合適的HttpClient 客戶端,如果緩存中沒有,HttpURLConnection 才會選擇去創建新的HttpClient 實例。

通過三者之間的關系,可以看出: HttpClient 實例的復用,就是底層 TCP socket 長連接的復用。

JDK1.8 的 HttpClient 實例的復用的流程

下面,通過單步跟蹤的方式,說一下HttpClient 實例的復用大致流程。

(1)首先,從默認的 feign.Client.Default 客戶端的 execute 方法開始。

execute 方法首先會調用 convertAndSend(connection, request) 方法,打開一個URL連接實例,也即是一個 HttpURLConnection 類型的實例。然后,在convertResponse 方法中,開始獲取響應碼。這個時候,請求的整個處理過程,才真正開始。

在這里插入圖片描述

(2) 通過單步跟蹤發現,connection.getResponseCode() 語句執行過程中, 會通過調用HttpClient.New(..) 獲取一個可用的 HttpClient 客戶端實例。

HttpClient.New(..)是一個靜態方法,大致的邏輯:它會調用 KeepAliveCache 類型的靜態成員 kac 的get方法,去首先獲取緩存中的HttpClient 客戶端實例。

在這里插入圖片描述

KeepAliveCache實例的 get方法,會主要以請求的 url 值作為 key,去緩存中查找是否有綁定的 HttpClient 實例,如果有的話直接拿過來用。

在這里插入圖片描述

(3) 如果KeepAliveCache 緩存實例中沒有,則調用HttpClient的構造器,新建一個HttpClient 對象,這個構造方法的最后一行,會調用了openServer() 方法,這個時候才會去真正的建立TCP連接。

在這里插入圖片描述

(4) 至此,HttpURLConnection 實例的getResponseCode() 方法,終於拿到了內部的 HttpClient 連接,這個時候可以向 SERVER 端寫請求數據了,這個時候會調用 writeRequests 方法。

HttpURLConnection 實例的writeRequests方法,首先會判斷 httpClient.isKeepAlive 的值,該值默認是true,所以在請求上加上了 Connection:keep-alive 請求頭 。

在這里插入圖片描述

(5)writeRequests 方法寫數據完成之后,會調用HttpClient.parseHTTP(..)方法,去解析服務端響應的數據,包括服務的響應頭。

在parseHTTP(..)方法中,如果響應頭中包含了Connection:keep-alive,並設置了Keep-Alive 頭,比如含有以下內容:“Keep-Alive:timeout=xx,max=xxx” ,其中 timeout 表示服務端的‘空閑’超時時間,max表示長連接最多處理多少個請求。則這兩個值,將覆蓋掉 httpClient對象的keepAliveTimeout 和 keepAliveConnections 屬性的值。

(6) parseHTTP(..)方法讀取完數據之后,最終會調用到httpClient.finished方法,將當前httpClient對象,加入到緩存中,這個地方是實現 TCP 連接復用的關鍵。
在這里插入圖片描述

(7)HttpClient的 putInKeepAliveCache方法,主要以請求的 url 值作為 key (因為這里的第二個參數總是寫死為 null ),以當前 HttpClient 實例為 value,放入到KeepAliveCache 類型的靜態成員 kac 緩存中,以便后面進行復用 。

在這里插入圖片描述

通過以上的七步,JDK1.8 實現了HttpURLConnection 的長連接。

這里,有一個問題:什么Feign調用會默認在請求頭中加上Connection:keep-alive?

原因是這樣的,在 sun.net.www.http.HttpClient 類的靜態初始化代碼部分,包括了 keepAliveProp 靜態屬性的初始化。keepAliveProp 靜態屬性的值,是決定HttpClient 客戶端實例是否復用的關鍵之一,如果keepAliveProp 靜態屬性值為false,則無論如何都不會進行連接復用。sun.net.www.http.HttpClient 類的靜態初始化代碼節選如下:

 static {
        String var0 = (String)AccessController.doPrivileged(new GetPropertyAction("http.keepAlive"));
        //…省略不相干代碼
        if (var0 != null) {
            keepAliveProp = Boolean.valueOf(var0);
        } else {
            keepAliveProp = true;  //默認值為true
        }
         //…省略不相干代碼
}

可以看到,首先取得一個系統配置項 http.keepAlive 的值,如果該配置項的值沒有做專門的設置或者修改,則sun.net.www.http.HttpClient 靜態屬性 keepAliveProp 的值,默認被賦值為true。

通過閱讀源碼可以知道,靜態屬性 keepAliveProp 的值,是決定HttpClient 的實例對象是否放入長連接緩沖池 KeepAliveCache 的一個重要關鍵屬性值。也就是說,這個屬性為true,則 HttpClient 實例對象具備復用的可能,否則,HttpClient 實例對象不能被復用。

至此,關於JDK1.8 實現如何實現長連接,也就介紹完了。不過,細心的讀者可能會發現,HttpURLConnection 內部的長連接復用,和URL有關:只有在URL字符串相同的情況下,才能進行復用。這就有一個問題,如果URL中帶有變量值,比如 /order/1/detail、/order/2/detail ,則不同的參數,不能進行HttpClient 實例對象的復用。

JDK默認的HttpClient 實例對象的復用的問題

和ApacheHttpClient 連接復用相比,JDK默認的HttpClient 實例對象的復用,有以下問題:

(1)JDK默認的 HttpClient 實例對象復用的粒度太小,只有URL相同的情況下,才能進行連接復用。而 ApacheHttpClient 連接復用的粒度則大很多,同路由的連接,就可以復用。

(2)在URL字符串變化比較大的場景下,JDK默認的 HttpClient 實例對象的內部連接,會保持一段時間才被釋放,會占用系統的連接資源,更加不利於高並發。

所以,從以上兩點出發,由於不能相同保證URL的請求數據巨大,所以不建議使用JDK默認的HttpClient 實例對象。建議在Feign中,使用ApacheHttpClient 連接池進行連接的復用

最后,介紹一下瘋狂創客圈:瘋狂創客圈,一個Java 高並發研習社群博客園 總入口

瘋狂創客圈,傾力推出:面試必備 + 面試必備 + 面試必備 的基礎原理+實戰 書籍 《Netty Zookeeper Redis 高並發實戰

img


瘋狂創客圈 Java 死磕系列

  • Java (Netty) 聊天程序【 億級流量】實戰 開源項目實戰

ty) 聊天程序【 億級流量】實戰 開源項目實戰**



免責聲明!

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



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