文章很長,建議收藏起來,慢慢讀! Java 高並發 發燒友社群:瘋狂創客圈 奉上以下珍貴的學習資源:
-
免費贈送 經典圖書:《Java高並發核心編程(卷1)》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
-
免費贈送 經典圖書:《Java高並發核心編程(卷2)》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
-
免費贈送 經典圖書:《Netty Zookeeper Redis 高並發實戰》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
-
免費贈送 經典圖書:《SpringCloud Nginx高並發核心編程》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
-
免費贈送 資源寶庫: Java 必備 百度網盤資源大合集 價值>10000元 加尼恩領取
推薦:入大廠 、做架構、大力提升Java 內功 的 精彩博文
入大廠 、做架構、大力提升Java 內功 必備的精彩博文 | 2021 秋招漲薪1W + 必備的精彩博文 |
---|---|
1:Redis 分布式鎖 (圖解-秒懂-史上最全) | 2:Zookeeper 分布式鎖 (圖解-秒懂-史上最全) |
3: Redis與MySQL雙寫一致性如何保證? (面試必備) | 4: 面試必備:秒殺超賣 解決方案 (史上最全) |
5:面試必備之:Reactor模式 | 6: 10分鍾看懂, Java NIO 底層原理 |
7:TCP/IP(圖解+秒懂+史上最全) | 8:Feign原理 (圖解) |
9:DNS圖解(秒懂 + 史上最全 + 高薪必備) | 10:CDN圖解(秒懂 + 史上最全 + 高薪必備) |
11: 分布式事務( 圖解 + 史上最全 + 吐血推薦 ) | 12:限流:計數器、漏桶、令牌桶 三大算法的原理與實戰(圖解+史上最全) |
13:架構必看:12306搶票系統億級流量架構 (圖解+秒懂+史上最全) |
14:seata AT模式實戰(圖解+秒懂+史上最全) |
15:seata 源碼解讀(圖解+秒懂+史上最全) | 16:seata TCC模式實戰(圖解+秒懂+史上最全) |
Java 面試題 30個專題 , 史上最全 , 面試必刷 | 阿里、京東、美團... 隨意挑、橫着走!!! |
---|---|
1: JVM面試題(史上最強、持續更新、吐血推薦) | 2:Java基礎面試題(史上最全、持續更新、吐血推薦 |
3:架構設計面試題 (史上最全、持續更新、吐血推薦) | 4:設計模式面試題 (史上最全、持續更新、吐血推薦) |
17、分布式事務面試題 (史上最全、持續更新、吐血推薦) | 一致性協議 (史上最全) |
29、多線程面試題(史上最全) | 30、HR面經,過五關斬六將后,小心陰溝翻船! |
9.網絡協議面試題(史上最全、持續更新、吐血推薦) | 更多專題, 請參見【 瘋狂創客圈 高並發 總目錄 】 |
SpringCloud 微服務 精彩博文 | |
---|---|
nacos 實戰(史上最全) | sentinel (史上最全+入門教程) |
SpringCloud gateway (史上最全) | 更多專題, 請參見【 瘋狂創客圈 高並發 總目錄 】 |
1 SpringCloud 中 Feign 核心原理
如果不了解 SpringCloud 中 Feign 核心原理,不會真正的了解 SpringCloud 的性能優化和配置優化,也就不可能做到真正掌握 SpringCloud。
本章從Feign 遠程調用的重要組件開始,圖文並茂的介紹 Feigh 遠程調用的執行流程、Feign 本地 JDK Proxy 實例的創建流程,徹底的為大家解讀 SpringCloud 的核心知識。使得廣大的工程師不光做到知其然,更能知其所以然。
1.1 簡介:Feign遠程調用的基本流程
Feign遠程調用,核心就是通過一系列的封裝和處理,將以JAVA注解的方式定義的遠程調用API接口,最終轉換成HTTP的請求形式,然后將HTTP的請求的響應結果,解碼成JAVA Bean,放回給調用者。Feign遠程調用的基本流程,大致如下圖所示。
從上圖可以看到,Feign通過處理注解,將請求模板化,當實際調用的時候,傳入參數,根據參數再應用到請求上,進而轉化成真正的 Request 請求。通過Feign以及JAVA的動態代理機制,使得Java 開發人員,可以不用通過HTTP框架去封裝HTTP請求報文的方式,完成遠程服務的HTTP調用。
1.2 Feign 遠程調用的重要組件
在微服務啟動時,Feign會進行包掃描,對加@FeignClient注解的接口,按照注解的規則,創建遠程接口的本地JDK Proxy代理實例。然后,將這些本地Proxy代理實例,注入到Spring IOC容器中。當遠程接口的方法被調用,由Proxy代理實例去完成真正的遠程訪問,並且返回結果。
為了清晰的介紹SpringCloud中Feign運行機制和原理,在這里,首先為大家梳理一下Feign中幾個重要組件。
1.2.1 遠程接口的本地JDK Proxy代理實例
遠程接口的本地JDK Proxy代理實例,有以下特點:
(1)Proxy代理實例,實現了一個加 @FeignClient 注解的遠程調用接口;
(2)Proxy代理實例,能在內部進行HTTP請求的封裝,以及發送HTTP 請求;
(3)Proxy代理實例,能處理遠程HTTP請求的響應,並且完成結果的解碼,然后返回給調用者。
下面以一個簡單的遠程服務的調用接口 DemoClient 為例,具體介紹一下遠程接口的本地JDK Proxy代理實例的創建過程。
DemoClient 接口,有兩個非常簡單的遠程調用抽象方法:一個為hello() 抽象方法,用於完成遠程URL “/api/demo/hello/v1”的HTTP請求;一個為 echo(…) 抽象方法,用於完成遠程URL “/api/demo/echo/{word}/v1”的HTTP請求。具體如下圖所示。
圖2 遠程接口的本地JDK Proxy代理實例示意圖
DemoClient 接口代碼如下:
注意,上面的代碼中,在DemoClient 接口上,加有@FeignClient 注解。也即是說,Feign在啟動時,會為其創建一個本地JDK Proxy代理實例,並注冊到Spring IOC容器。
如何使用呢?可以通過@Resource注解,按照類型匹配(這里的類型為DemoClient接口類型),從Spring IOC容器找到這個代理實例,並且裝配給需要的成員變量。
DemoClient的 本地JDK Proxy 代理實例的使用的代碼如下:
DemoClient的本地JDK Proxy代理實例的創建過程,比較復雜,稍后作為重點介紹。先來看另外兩個重要的邏輯組件。
1.2.2 調用處理器 InvocationHandler
大家知道,通過 JDK Proxy 生成動態代理類,核心步驟就是需要定制一個調用處理器,具體來說,就是實現JDK中位於java.lang.reflect 包中的 InvocationHandler 調用處理器接口,並且實現該接口的 invoke(…) 抽象方法。
為了創建Feign的遠程接口的代理實現類,Feign提供了自己的一個默認的調用處理器,叫做 FeignInvocationHandler 類,該類處於 feign-core 核心jar包中。當然,調用處理器可以進行替換,如果Feign與Hystrix結合使用,則會替換成 HystrixInvocationHandler 調用處理器類,類處於 feign-hystrix 的jar包中。
圖3 Feign中實現的 InvocationHandler 調用處理器
1.2.1 默認的調用處理器 FeignInvocationHandler
默認的調用處理器 FeignInvocationHandler 是一個相對簡單的類,有一個非常重要Map類型成員 dispatch 映射,保存着遠程接口方法到MethodHandler方法處理器的映射。
以前面示例中DemoClient 接口為例,其代理實現類的調用處理器 FeignInvocationHandler 的dispatch 成員的內存結構圖如圖3所示。
圖4 DemoClient代理實例的調用處理器 FeignInvocationHandler的dispatch 成員
為何在圖3中的Map類型成員 dispatch 映射對象中,有兩個Key-Value鍵值對呢?
原因是:默認的調用處理器 FeignInvocationHandle,在處理遠程方法調用的時候,會根據Java反射的方法實例,在dispatch 映射對象中,找到對應的MethodHandler 方法處理器,然后交給MethodHandler 完成實際的HTTP請求和結果的處理。前面示例中的 DemoClient 遠程調用接口,有兩個遠程調用方法,所以,其代理實現類的調用處理器 FeignInvocationHandler 的dispatch 成員,有兩個有兩個Key-Value鍵值對。
FeignInvocationHandler的關鍵源碼,節選如下:
源碼很簡單,重點在於invoke(…)方法,雖然核心代碼只有一行,但是其功能是復雜的:
(1)根據Java反射的方法實例,在dispatch 映射對象中,找到對應的MethodHandler 方法處理器;
(2)調用MethodHandler方法處理器的 invoke(...) 方法,完成實際的HTTP請求和結果的處理。
補充說明一下:MethodHandler 方法處理器,和JDK 動態代理機制中位於 java.lang.reflect 包的 InvocationHandler 調用處理器接口,沒有任何的繼承和實現關系。MethodHandler 僅僅是Feign自定義的,一個非常簡單接口。
1.2.2 方法處理器 MethodHandler
Feign的方法處理器 MethodHandler 是一個獨立的接口,定義在 InvocationHandlerFactory 接口中,僅僅擁有一個invoke(…)方法,源碼如下:
MethodHandler 的invoke(…)方法,主要職責是完成實際遠程URL請求,然后返回解碼后的遠程URL的響應結果。Feign提供了默認的 SynchronousMethodHandler 實現類,提供了基本的遠程URL的同步請求處理。有關 SynchronousMethodHandler類以及其與MethodHandler的關系,大致如圖4所示。
圖5 Feign的MethodHandler方法處理器
為了徹底了解方法處理器,來讀一下 SynchronousMethodHandler 方法處理器的源碼,大致如下:
SynchronousMethodHandler的invoke(…)方法,調用了自己的executeAndDecode(…) 請求執行和結果解碼方法。該方法的工作步驟:
(1)首先通 RequestTemplate 請求模板實例,生成遠程URL請求實例 request;
(2)然后用自己的 feign 客戶端client成員,excecute(…) 執行請求,並且獲取 response 響應;
(3)對response 響應進行結果解碼。
1.2.3 Feign 客戶端組件 feign.Client
客戶端組件是Feign中一個非常重要的組件,負責端到端的執行URL請求。其核心的邏輯:發送request請求到服務器,並接收response響應后進行解碼。
feign.Client 類,是代表客戶端的頂層接口,只有一個抽象方法,源碼如下:
由於不同的feign.Client 實現類,內部完成HTTP請求的組件和技術不同,故,feign.Client 有多個不同的實現。這里舉出幾個例子:
(1)Client.Default類:默認的feign.Client 客戶端實現類,內部使用HttpURLConnnection 完成URL請求處理;
(2)ApacheHttpClient 類:內部使用 Apache httpclient 開源組件完成URL請求處理的feign.Client 客戶端實現類;
(3)OkHttpClient類:內部使用 OkHttp3 開源組件完成URL請求處理的feign.Client 客戶端實現類。
(4)LoadBalancerFeignClient 類:內部使用 Ribben 負載均衡技術完成URL請求處理的feign.Client 客戶端實現類。
此外,還有一些特殊場景使用的feign.Client客戶端實現類,也可以定制自己的feign.Client實現類。下面對上面幾個常見的客戶端實現類,進行簡要介紹。
圖6 feign.Client客戶端實現類
一:Client.Default類:
作為默認的Client 接口的實現類,在Client.Default內部使用JDK自帶的HttpURLConnnection類實現URL網絡請求。
圖7 默認的Client 接口的客戶端實現類
在JKD1.8中,雖然在HttpURLConnnection 底層,使用了非常簡單的HTTP連接池技術,但是,其HTTP連接的復用能力,實際是非常弱的,性能當然也很低。具體的原因,參見后面的“SpringCloud與長連接的深入剖析”專題內容。
二:ApacheHttpClient類
ApacheHttpClient 客戶端類的內部,使用 Apache HttpClient開源組件完成URL請求的處理。
從代碼開發的角度而言,Apache HttpClient相比傳統JDK自帶的URLConnection,增加了易用性和靈活性,它不僅使客戶端發送Http請求變得容易,而且也方便開發人員測試接口。既提高了開發的效率,也方便提高代碼的健壯性。
從性能的角度而言,Apache HttpClient帶有連接池的功能,具備優秀的HTTP連接的復用能力。關於帶有連接池Apache HttpClient的性能提升倍數,具體可以參見后面的對比試驗。
ApacheHttpClient 類處於 feign-httpclient 的專門jar包中,如果使用,還需要通過Maven依賴或者其他的方式,倒入配套版本的專門jar包。
三:OkHttpClient類
OkHttpClient 客戶端類的內部,使用OkHttp3 開源組件完成URL請求處理。OkHttp3 開源組件由Square公司開發,用於替代HttpUrlConnection和Apache HttpClient。由於OkHttp3較好的支持 SPDY協議(SPDY是Google開發的基於TCP的傳輸層協議,用以最小化網絡延遲,提升網絡速度,優化用戶的網絡使用體驗。),從Android4.4開始,google已經開始將Android源碼中的 HttpURLConnection 請求類使用OkHttp進行了替換。也就是說,對於Android 移動端APP開發來說,OkHttp3 組件,是基礎的開發組件之一。
四:LoadBalancerFeignClient 類
LoadBalancerFeignClient 內部使用了 Ribben 客戶端負載均衡技術完成URL請求處理。在原理上,簡單的使用了delegate包裝代理模式:Ribben負載均衡組件計算出合適的服務端server之后,由內部包裝 delegate 代理客戶端完成到服務端server的HTTP請求;所封裝的 delegate 客戶端代理實例的類型,可以是 Client.Default 默認客戶端,也可以是 ApacheHttpClient 客戶端類或OkHttpClient 高性能客戶端類,還可以其他的定制的feign.Client 客戶端實現類型。
LoadBalancerFeignClient 負載均衡客戶端實現類,具體如下圖所示。
圖8 LoadBalancerFeignClient 負載均衡客戶端實現類
1.1 Feigh 遠程調用的執行流程
由於Feign遠程調用接口的JDK Proxy實例的InvokeHandler調用處理器有多種,導致Feign遠程調用的執行流程,也稍微有所區別,但是遠程調用執行流程的主要步驟,是一致的。這里主要介紹兩類JDK Proxy實例的InvokeHandler調用處理器相關的遠程調用執行流程:
(1)與 默認的調用處理器 FeignInvocationHandler 相關的遠程調用執行流程;
(2)與 Hystrix調用處理器 HystrixInvocationHandler 相關的遠程調用執行流程。
介紹過程中,還是以前面的DemoClient的JDK Proxy遠程動態代理實例的執行過程為例,演示分析Feigh遠程調用的執行流程。
1.1.1 與 FeignInvocationHandler 相關的遠程調用執行流程
FeignInvocationHandler是默認的調用處理器,如果不對Feign做特殊的配置,則Feign將使用此調用處理器。結合前面的DemoClient的JDK Proxy遠程動態代理實例的hello()遠程調用執行過程,在這里,詳細的介紹一下與 FeignInvocationHandler 相關的遠程調用執行流程,大致如下圖所示。
圖6 與 FeignInvocationHandler 相關的遠程調用執行流程
整體的遠程調用執行流程,大致分為4步,具體如下:
第1步:通過Spring IOC 容器實例,裝配代理實例,然后進行遠程調用。
前文講到,Feign在啟動時,會為加上了@FeignClient注解的所有遠程接口(包括 DemoClient 接口),創建一個本地JDK Proxy代理實例,並注冊到Spring IOC容器。在這里,暫且將這個Proxy代理實例,叫做 DemoClientProxy,稍后,會詳細介紹這個Proxy代理實例的具體創建過程。
然后,在本實例的UserController 調用代碼中,通過@Resource注解,按照類型或者名稱進行匹配(這里的類型為DemoClient接口類型),從Spring IOC容器找到這個代理實例,並且裝配給@Resource注解所在的成員變量,本實例的成員變量的名稱為 demoClient。
在需要代進行hello()遠程調用時,直接通過 demoClient 成員變量,調用JDK Proxy動態代理實例的hello()方法。
第2步:執行 InvokeHandler 調用處理器的invoke(…)方法
前面講到,JDK Proxy動態代理實例的真正的方法調用過程,具體是通過 InvokeHandler 調用處理器完成的。故,這里的DemoClientProxy代理實例,會調用到默認的FeignInvocationHandler 調用處理器實例的invoke(…)方法。
通過前面 FeignInvocationHandler 調用處理器的詳細介紹,大家已經知道,默認的調用處理器 FeignInvocationHandle,內部保持了一個遠程調用方法實例和方法處理器的一個Key-Value鍵值對Map映射。FeignInvocationHandle 在其invoke(…)方法中,會根據Java反射的方法實例,在dispatch 映射對象中,找到對應的 MethodHandler 方法處理器,然后由后者完成實際的HTTP請求和結果的處理。
所以在第2步中,FeignInvocationHandle 會從自己的 dispatch映射中,找到hello()方法所對應的MethodHandler 方法處理器,然后調用其 invoke(…)方法。
第3步:執行 MethodHandler 方法處理器的invoke(…)方法
通過前面關於 MethodHandler 方法處理器的非常詳細的組件介紹,大家都知道,feign默認的方法處理器為 SynchronousMethodHandler,其invoke(…)方法主要是通過內部成員feign客戶端成員 client,完成遠程 URL 請求執行和獲取遠程結果。
feign.Client 客戶端有多種類型,不同的類型,完成URL請求處理的具體方式不同。
第4步:通過 feign.Client 客戶端成員,完成遠程 URL 請求執行和獲取遠程結果
如果MethodHandler方法處理器實例中的client客戶端,是默認的 feign.Client.Default 實現類性,則使用JDK自帶的HttpURLConnnection類,完成遠程 URL 請求執行和獲取遠程結果。
如果MethodHandler方法處理器實例中的client客戶端,是 ApacheHttpClient 客戶端實現類性,則使用 Apache httpclient 開源組件,完成遠程 URL 請求執行和獲取遠程結果。
通過以上四步,應該可以清晰的了解到了 SpringCloud中的 feign 遠程調用執行流程和運行機制。
實際上,為了簡明扼要的介紹清楚默認的調用流程,上面的流程,實際上省略了一個步驟:第3步,實際可以分為兩小步。為啥呢? SynchronousMethodHandler 並不是直接完成遠程URL的請求,而是通過負載均衡機制,定位到合適的遠程server 服務器,然后再完成真正的遠程URL請求。換句話說,SynchronousMethodHandler實例的client成員,其實際不是feign.Client.Default類型,而是 LoadBalancerFeignClient 客戶端負載均衡類型。 因此,上面的第3步,如果進一步細分話,大致如下:(1)首先通過 SynchronousMethodHandler 內部的client實例,實質為負責客戶端負載均衡 LoadBalancerFeignClient 實例,首先查找到遠程的 server 服務端;(2) 然后再由LoadBalancerFeignClient 實例內部包裝的feign.Client.Default 內部類實例,去請求server端服務器,完成URL請求處理。
最后,說明下,默認的與 FeignInvocationHandler 相關的遠程調用執行流程,在運行機制以及調用性能上,滿足不了生產環境的要求,為啥呢? 大致原因有以下兩點:
(1) 沒有遠程調用過程中的熔斷監測和恢復機制;
(2) 也沒有用到高性能的HTTP連接池技術。
接下來,將為大家介紹一下用到熔斷監測和恢復機制 Hystrix 技術的遠程調用執行流程,該流程中,遠程接口的JDK Proxy動態代理實例所使用的調用處理器,叫做 HystrixInvocationHandler 調用處理器。
本文的內容,在《SpringCloud Nginx高並發核心編程》一書時,進行內容的完善和更新,並且進行的源碼的升級。 博客和書不一樣,書更加層層升入、層次分明,請大家以書的內容為准。
具體,請關注 Java 高並發研習社群 【博客園 總入口 】