瘋狂創客圈 Java 高併發【 億級流量聊天室實戰】實戰系列 【博客園總入口 】html
瘋狂創客圈 正在進行分佈式和高併發基礎原理的研習,進行已經發布一些基礎性的文章:java
1、版本1 :springcloud + zookeeper 秒殺面試
以及有關Springcloud 幾篇核心、重要的文章:spring
1、Springcloud 配置, 史上最全 一文全懂apache
2、Feign Ribbon Hystrix 三者關係 , 史上最全 深度解析api
3、SpringCloud gateway 詳解 , 史上最全性能優化
4、常識糾錯:Feign 默認不用 短鏈接服務器
若是不瞭解 SpringCloud 中 Feign 核心原理,不會真正的瞭解 SpringCloud 的性能優化和配置優化,也就不可能作到真正掌握 SpringCloud。網絡
本章從Feign 遠程調用的重要組件開始,圖文並茂的介紹 Feigh 遠程調用的執行流程、Feign 本地 JDK Proxy 實例的建立流程,完全的爲你們解讀 SpringCloud 的核心知識。使得廣大的工程師不光作到知其然,更能知其因此然。併發
Feign遠程調用,核心就是經過一系列的封裝和處理,將以JAVA註解的方式定義的遠程調用API接口,最終轉換成HTTP的請求形式,而後將HTTP的請求的響應結果,解碼成JAVA Bean,放回給調用者。Feign遠程調用的基本流程,大體以下圖所示。
從上圖能夠看到,Feign經過處理註解,將請求模板化,當實際調用的時候,傳入參數,根據參數再應用到請求上,進而轉化成真正的 Request 請求。經過Feign以及JAVA的動態代理機制,使得Java 開發人員,能夠不用經過HTTP框架去封裝HTTP請求報文的方式,完成遠程服務的HTTP調用。
在微服務啓動時,Feign會進行包掃描,對加@FeignClient註解的接口,按照註解的規則,建立遠程接口的本地JDK Proxy代理實例。而後,將這些本地Proxy代理實例,注入到Spring IOC容器中。當遠程接口的方法被調用,由Proxy代理實例去完成真正的遠程訪問,而且返回結果。
爲了清晰的介紹SpringCloud中Feign運行機制和原理,在這裏,首先爲你們梳理一下Feign中幾個重要組件。
遠程接口的本地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 接口代碼以下:
package com.crazymaker.springcloud.demo.contract.client; //…省略import @FeignClient( value = "seckill-provider", path = "/api/demo/", fallback = DemoDefaultFallback.class) public interface DemoClient { /** * 測試遠程調用 * * @return hello */ @GetMapping("/hello/v1") Result<JSONObject> hello(); /** * 很是簡單的一個 回顯 接口,主要用於遠程調用 * * @return echo 回顯消息 */ @RequestMapping(value = "/echo/{word}/v1", method = RequestMethod.GET) Result<JSONObject> echo( @PathVariable(value = "word") String word); }
注意,上面的代碼中,在DemoClient 接口上,加有@FeignClient 註解。也便是說,Feign在啓動時,會爲其建立一個本地JDK Proxy代理實例,並註冊到Spring IOC容器。
如何使用呢?能夠經過@Resource註解,按照類型匹配(這裏的類型爲DemoClient接口類型),從Spring IOC容器找到這個代理實例,而且裝配給須要的成員變量。
DemoClient的 本地JDK Proxy 代理實例的使用的代碼以下:
package com.crazymaker.springcloud.user.info.controller; //…省略import @Api(value = "用戶信息、基礎學習DEMO", tags = {"用戶信息、基礎學習DEMO"}) @RestController @RequestMapping("/api/user") public class UserController { @Resource DemoClient demoClient; //裝配 DemoClient 的本地代理實例 @GetMapping("/say/hello/v1") @ApiOperation(value = "測試遠程調用速度") public Result<JSONObject> hello() { Result<JSONObject> result = demoClient.hello(); JSONObject data = new JSONObject(); data.put("others", result); return Result.success(data).setMsg("操做成功"); } //… }
DemoClient的本地JDK Proxy代理實例的建立過程,比較複雜,稍後做爲重點介紹。先來看另外兩個重要的邏輯組件。
你們知道,經過 JDK Proxy 生成動態代理類,核心步驟就是須要定製一個調用處理器,具體來講,就是實現JDK中位於java.lang.reflect 包中的 InvocationHandler 調用處理器接口,而且實現該接口的 invoke(…) 抽象方法。
爲了建立Feign的遠程接口的代理實現類,Feign提供了本身的一個默認的調用處理器,叫作 FeignInvocationHandler 類,該類處於 feign-core 核心jar包中。固然,調用處理器能夠進行替換,若是Feign與Hystrix結合使用,則會替換成 HystrixInvocationHandler 調用處理器類,類處於 feign-hystrix 的jar包中。
圖3 Feign中實現的 InvocationHandler 調用處理器
默認的調用處理器 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的關鍵源碼,節選以下:
package feign; //...省略import public class ReflectiveFeign extends Feign { //... //內部類:默認的Feign調用處理器 FeignInvocationHandler static class FeignInvocationHandler implements InvocationHandler { private final Target target; //方法實例對象和方法處理器的映射 private final Map<Method, MethodHandler> dispatch; //構造函數 FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) { this.target = checkNotNull(target, "target"); this.dispatch = checkNotNull(dispatch, "dispatch for %s", target); } //默認Feign調用的處理 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //... //首先,根據方法實例,從方法實例對象和方法處理器的映射中, //取得 方法處理器,而後,調用 方法處理器 的 invoke(...) 方法 return dispatch.get(method).invoke(args); } //... }
源碼很簡單,重點在於invoke(…)方法,雖然核心代碼只有一行,可是其功能是複雜的:
(1)根據Java反射的方法實例,在dispatch 映射對象中,找到對應的MethodHandler 方法處理器;
(2)調用MethodHandler方法處理器的 invoke(...) 方法,完成實際的HTTP請求和結果的處理。
補充說明一下:MethodHandler 方法處理器,和JDK 動態代理機制中位於 java.lang.reflect 包的 InvocationHandler 調用處理器接口,沒有任何的繼承和實現關係。MethodHandler 僅僅是Feign自定義的,一個很是簡單接口。
Feign的方法處理器 MethodHandler 是一個獨立的接口,定義在 InvocationHandlerFactory 接口中,僅僅擁有一個invoke(…)方法,源碼以下:
//定義在InvocationHandlerFactory接口中 public interface InvocationHandlerFactory { //… //方法處理器接口,僅僅擁有一個invoke(…)方法 interface MethodHandler { //完成遠程URL請求 Object invoke(Object[] argv) throws Throwable; } //... }
MethodHandler 的invoke(…)方法,主要職責是完成實際遠程URL請求,而後返回解碼後的遠程URL的響應結果。Feign提供了默認的 SynchronousMethodHandler 實現類,提供了基本的遠程URL的同步請求處理。有關 SynchronousMethodHandler類以及其與MethodHandler的關係,大體如圖4所示。
圖5 Feign的MethodHandler方法處理器
爲了完全瞭解方法處理器,來讀一下 SynchronousMethodHandler 方法處理器的源碼,大體以下:
package feign; //…..省略import final class SynchronousMethodHandler implements MethodHandler { //… // 執行Handler 的處理 public Object invoke(Object[] argv) throws Throwable { RequestTemplate requestTemplate = this.buildTemplateFromArgs.create(argv); Retryer retryer = this.retryer.clone(); while(true) { try { return this.executeAndDecode(requestTemplate); } catch (RetryableException var5) { //…省略不相干代碼 } } } //執行請求,而後解碼結果 Object executeAndDecode(RequestTemplate template) throws Throwable { Request request = this.targetRequest(template); long start = System.nanoTime(); Response response; try { response = this.client.execute(request, this.options); response.toBuilder().request(request).build(); } } }
SynchronousMethodHandler的invoke(…)方法,調用了本身的executeAndDecode(…) 請求執行和結果解碼方法。該方法的工做步驟:
(1)首先通 RequestTemplate 請求模板實例,生成遠程URL請求實例 request;
(2)而後用本身的 feign 客戶端client成員,excecute(…) 執行請求,而且獲取 response 響應;
(3)對response 響應進行結果解碼。
客戶端組件是Feign中一個很是重要的組件,負責端到端的執行URL請求。其核心的邏輯:發送request請求到服務器,並接收response響應後進行解碼。
feign.Client 類,是表明客戶端的頂層接口,只有一個抽象方法,源碼以下:
package feign; /**客戶端接口 * Submits HTTP {@link Request requests}. Implementations are expected to be thread-safe. */ public interface Client { //提交HTTP請求,而且接收response響應後進行解碼 Response execute(Request request, Options options) throws IOException; }
因爲不一樣的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 接口的實現類,在Client.Default內部使用JDK自帶的HttpURLConnnection類實現URL網絡請求。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-MpvoQNy0-1575176264529)(C:\Users\wuqinglin\AppData\Roaming\Typora\typora-user-images\1575175924885.png)]
圖7 默認的Client 接口的客戶端實現類
在JKD1.8中,雖然在HttpURLConnnection 底層,使用了很是簡單的HTTP鏈接池技術,可是,其HTTP鏈接的複用能力,實際是很是弱的,性能固然也很低。具體的緣由,參見後面的「SpringCloud與長鏈接的深刻剖析」專題內容。
ApacheHttpClient 客戶端類的內部,使用 Apache HttpClient開源組件完成URL請求的處理。
從代碼開發的角度而言,Apache HttpClient相比傳統JDK自帶的URLConnection,增長了易用性和靈活性,它不只使客戶端發送Http請求變得容易,並且也方便開發人員測試接口。既提升了開發的效率,也方便提升代碼的健壯性。
從性能的角度而言,Apache HttpClient帶有鏈接池的功能,具有優秀的HTTP鏈接的複用能力。關於帶有鏈接池Apache HttpClient的性能提高倍數,具體能夠參見後面的對比試驗。
ApacheHttpClient 類處於 feign-httpclient 的專門jar包中,若是使用,還須要經過Maven依賴或者其餘的方式,倒入配套版本的專門jar包。
OkHttpClient 客戶端類的內部,使用OkHttp3 開源組件完成URL請求處理。OkHttp3 開源組件由Square公司開發,用於替代HttpUrlConnection和Apache HttpClient。因爲OkHttp3較好的支持 SPDY協議(SPDY是Google開發的基於TCP的傳輸層協議,用以最小化網絡延遲,提高網絡速度,優化用戶的網絡使用體驗。),從Android4.4開始,google已經開始將Android源碼中的 HttpURLConnection 請求類使用OkHttp進行了替換。也就是說,對於Android 移動端APP開發來講,OkHttp3 組件,是基礎的開發組件之一。
LoadBalancerFeignClient 內部使用了 Ribben 客戶端負載均衡技術完成URL請求處理。在原理上,簡單的使用了delegate包裝代理模式:Ribben負載均衡組件計算出合適的服務端server以後,由內部包裝 delegate 代理客戶端完成到服務端server的HTTP請求;所封裝的 delegate 客戶端代理實例的類型,能夠是 Client.Default 默認客戶端,也能夠是 ApacheHttpClient 客戶端類或OkHttpClient 高性能客戶端類,還能夠其餘的定製的feign.Client 客戶端實現類型。
LoadBalancerFeignClient 負載均衡客戶端實現類,具體以下圖所示。
圖8 LoadBalancerFeignClient 負載均衡客戶端實現類
因爲Feign遠程調用接口的JDK Proxy實例的InvokeHandler調用處理器有多種,致使Feign遠程調用的執行流程,也稍微有所區別,可是遠程調用執行流程的主要步驟,是一致的。這裏主要介紹兩類JDK Proxy實例的InvokeHandler調用處理器相關的遠程調用執行流程:
(1)與 默認的調用處理器 FeignInvocationHandler 相關的遠程調用執行流程;
(2)與 Hystrix調用處理器 HystrixInvocationHandler 相關的遠程調用執行流程。
介紹過程當中,仍是之前面的DemoClient的JDK Proxy遠程動態代理實例的執行過程爲例,演示分析Feigh遠程調用的執行流程。
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 調用處理器。
具體,請關注 Java 高併發研習社羣 【博客園 總入口 】
最後,介紹一下瘋狂創客圈:瘋狂創客圈,一個Java 高併發研習社羣 【博客園 總入口 】
瘋狂創客圈,傾力推出:面試必備 + 面試必備 + 面試必備 的基礎原理+實戰 書籍 《Netty Zookeeper Redis 高併發實戰》
Java (Netty) 聊天程序【 億級流量】實戰 開源項目實戰
瘋狂創客圈 【 博客園 總入口 】
m/crazymakercircle/p/9904544.html) 】
瘋狂創客圈,傾力推出:面試必備 + 面試必備 + 面試必備 的基礎原理+實戰 書籍 《Netty Zookeeper Redis 高併發實戰》
[外鏈圖片轉存中...(img-QTCNgdff-1575176264531)]
Java (Netty) 聊天程序【 億級流量】實戰 開源項目實戰
瘋狂創客圈 【 博客園 總入口 】