摘要:隨著微服務的遍地開花,越來越多的公司開始采用用于公司內部的微服務框架。
隨著微服務的遍地開花,越來越多的公司開始采用SpringCloud用于公司內部的微服務框架。
按照微服務的理念,每個單體應用的功能都應該按照功能正交,也就是功能相互獨立的原則,劃分成一個個功能獨立的微服務(模塊),再通過接口聚合的方式統一對外提供服務!
然而隨著微服務模塊的不斷增多,通過接口聚合對外提供服務的中層服務需要聚合的接口也越來越多!慢慢地,接口聚合就成分布式微服務架構里一個非常棘手的性能瓶頸!
舉個例子,有個聚合服務,它需要聚合Service、Route和Plugin三個服務的數據才能對外提供服務:
@Headers({ "Accept: application/json" }) public interface ServiceClient { @RequestLine("GET /") Listlist(); }
@Headers({ "Accept: application/json" }) public interface RouteClient { @RequestLine("GET /") Listlist(); }
@Headers({ "Accept: application/json" }) public interface PluginClient { @RequestLine("GET /") Listlist(); }
使用聲明式的OpenFeign代替HTTP Client進行網絡請求
編寫單元測試
public class SyncFeignClientTest { public static final String SERVER = "http://devops2:8001"; private ServiceClient serviceClient; private RouteClient routeClient; private PluginClient pluginClient; @Before public void setup(){ BasicConfigurator.configure(); Logger.getRootLogger().setLevel(Level.INFO); String service = SERVER + "/services"; serviceClient = Feign.builder() .target(ServiceClient.class, service); String route = SERVER + "/routes"; routeClient = Feign.builder() .target(RouteClient.class, route); String plugin = SERVER + "/plugins"; pluginClient = Feign.builder() .target(PluginClient.class, plugin); } @Test public void aggressionTest() { long current = System.currentTimeMillis(); System.out.println("開始調用聚合查詢"); serviceTest(); routeTest(); pluginTest(); System.out.println("調用聚合查詢結束!耗時:" + (System.currentTimeMillis() - current) + "毫秒"); } @Test public void serviceTest(){ long current = System.currentTimeMillis(); System.out.println("開始獲取Service"); String service = serviceClient.list(); System.out.println(service); System.out.println("獲取Service結束!耗時:" + (System.currentTimeMillis() - current) + "毫秒"); } @Test public void routeTest(){ long current = System.currentTimeMillis(); System.out.println("開始獲取Route"); String route = routeClient.list(); System.out.println(route); System.out.println("獲取Route結束!耗時:" + (System.currentTimeMillis() - current) + "毫秒"); } @Test public void pluginTest(){ long current = System.currentTimeMillis(); System.out.println("開始獲取Plugin"); String plugin = pluginClient.list(); System.out.println(plugin); System.out.println("獲取Plugin結束!耗時:" + (System.currentTimeMillis() - current) + "毫秒"); } }測試結果:
開始調用聚合查詢 開始獲取Service {"next":null,"data":[]} 獲取Service結束!耗時:134毫秒 開始獲取Route {"next":null,"data":[]} 獲取Route結束!耗時:44毫秒 開始獲取Plugin {"next":null,"data":[]} 獲取Plugin結束!耗時:45毫秒 調用聚合查詢結束!耗時:223毫秒 Process finished with exit code 0
可以明顯看出:聚合查詢查詢所用的時間223毫秒 = 134毫秒 + 44毫秒 + 45毫秒
也就是聚合服務的請求時間與接口數量成正比關系,這種做法顯然不能接受!
而解決這種問題的最常見做法就是預先創建線程池,通過多線程并發請求接口進行接口聚合!
這種方案在網上隨便百度一下就能找到好多,今天我就不再把它的代碼貼出來!而是說一下這個方法的缺點:
原本JavaWeb的主流Servlet容器采用的方案是一個HTTP請求就使用一個線程和一個Servlet進行處理!這種做法在并發量不高的情況沒有太大問題,但是由于摩爾定律失效了,單臺機器的線程數量仍舊停留在一萬左右,在網站動輒上千萬點擊量的今天,單機的線程數量根本無法應付上千萬級的并發量!
而為了解決接口聚合的耗時過長問題,采用線程池多線程并發網絡請求的做法,更是火上澆油!原本只需一個線程就搞定的請求,通過多線程并發進行接口聚合,就把處理每個請求所需要的線程數量給放大了,急速降低系統可用線程的數量,自然也降低系統的并發數量!
這時,人們想起從Java5開始就支持的NIO以及它的開源框架Netty!基于Netty以及Reactor模式,Java生態圈出現了SpringWebFlux等異步非阻塞的JavaWeb框架!Spring5也是基于SpringWebFlux進行開發的!有了異步非阻塞服務器,自然也有異步非阻塞網絡請求客戶端WebClient!
今天我就使用WebClient和ReactiveFeign做一個異步非阻塞的接口聚合教程:
首先,引入依賴
com.playtika.reactivefeign feign-reactor-core 1.0.30 test com.playtika.reactivefeign feign-reactor-webclient 1.0.30 test
然而基于Reactor Core重寫Feign客戶端,就是把原本接口返回值:List<實體>改成FLux<實體>,實體改成Mono<實體>
@Headers({ "Accept: application/json" }) public interface ServiceClient { @RequestLine("GET /") Fluxlist(); }
@Headers({ "Accept: application/json" }) public interface RouteClient { @RequestLine("GET /") Fluxlist(); }
@Headers({ "Accept: application/json" }) public interface PluginClient { @RequestLine("GET /") Flux然后編寫單元測試list(); }
public class AsyncFeignClientTest { public static final String SERVER = "http://devops2:8001"; private CountDownLatch latch; private ServiceClient serviceClient; private RouteClient routeClient; private PluginClient pluginClient; @Before public void setup(){ BasicConfigurator.configure(); Logger.getRootLogger().setLevel(Level.INFO); latch= new CountDownLatch(3); String service= SERVER + "/services"; serviceClient= WebReactiveFeign .builder() .target(ServiceClient.class, service); String route= SERVER + "/routes"; routeClient= WebReactiveFeign . builder() .target(RouteClient.class, route); String plugin= SERVER + "/plugins"; pluginClient= WebReactiveFeign . builder() .target(PluginClient.class, plugin); } @Test public void aggressionTest() throws InterruptedException { long current= System.currentTimeMillis(); System.out.println("開始調用聚合查詢"); serviceTest(); routeTest(); pluginTest(); latch.await(); System.out.println("調用聚合查詢結束!耗時:" + (System.currentTimeMillis() - current) + "毫秒"); } @Test public void serviceTest(){ long current= System.currentTimeMillis(); System.out.println("開始獲取Service"); serviceClient.list() .subscribe(result ->{ System.out.println(result); latch.countDown(); System.out.println("獲取Service結束!耗時:" + (System.currentTimeMillis() - current) + "毫秒"); }); } @Test public void routeTest(){ long current= System.currentTimeMillis(); System.out.println("開始獲取Route"); routeClient.list() .subscribe(result ->{ System.out.println(result); latch.countDown(); System.out.println("獲取Route結束!耗時:" + (System.currentTimeMillis() - current) + "毫秒"); }); } @Test public void pluginTest(){ long current= System.currentTimeMillis(); System.out.println("開始獲取Plugin"); pluginClient.list() .subscribe(result ->{ System.out.println(result); latch.countDown(); System.out.println("獲取Plugin結束!耗時:" + (System.currentTimeMillis() - current) + "毫秒"); }); } }
這里的關鍵點就在于原本同步阻塞的請求,現在改成異步非阻塞了,所以需要使用CountDownLatch來同步,在獲取到接口后調用CountDownLatch.coutdown(),在調用所有接口請求后調用CountDownLatch.await()等待所有的接口返回結果再進行下一步操作!
測試結果:
開始調用聚合查詢 開始獲取Service 開始獲取Route 開始獲取Plugin {"next":null,"data":[]} {"next":null,"data":[]} 獲取Plugin結束!耗時:215毫秒 {"next":null,"data":[]} 獲取Route結束!耗時:216毫秒 獲取Service結束!耗時:1000毫秒 調用聚合查詢結束!耗時:1000毫秒 Process finished with exit code 0
顯然,聚合查詢所消耗的時間不再等于所有接口請求的時間之和,而是接口請求時間中的最大值!
下面開始性能測試:普通Feign接口聚合測試調用1000次:
開始調用聚合查詢 開始獲取Service {"next":null,"data":[]} 獲取Service結束!耗時:169毫秒 開始獲取Route {"next":null,"data":[]} 獲取Route結束!耗時:81毫秒 開始獲取Plugin {"next":null,"data":[]} 獲取Plugin結束!耗時:93毫秒 調用聚合查詢結束!耗時:343毫秒 summary: 238515, average: 238
使用WebClient進行接口聚合查詢1000次:
開始調用聚合查詢 開始獲取Service 開始獲取Route 開始獲取Plugin {"next":null,"data":[]} {"next":null,"data":[]} 獲取Route結束!耗時:122毫秒 {"next":null,"data":[]} 獲取Service結束!耗時:122毫秒 獲取Plugin結束!耗時:121毫秒 調用聚合查詢結束!耗時:123毫秒 summary: 89081, average: 89
測試結果中,WebClient的測試結果恰好相當于普通FeignClient的三分之一!正好在意料之中!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/75367.html
摘要:那么為什么可以帶給我們這樣的完美編碼體驗呢實際上,這完全歸功于的封裝,由于在服務注冊與發現客戶端負載均衡等方面都做了很好的抽象,而上層應用方面依賴的都是這些抽象接口,而非針對某個具體中間件的實現。 通過《Spring Cloud Alibaba基礎教程:使用Nacos實現服務注冊與發現》一文的學習,我們已經學會如何使用Nacos來實現服務的注冊與發現,同時也介紹如何通過LoadBala...
摘要:對于異步的請求,使用的是異步客戶端即。要實現的配置設計以及使用舉例要實現的配置設計以及使用舉例首先,我們要實現的,其包含三個重試重試的要在負載均衡之前,因為重試的時候,我們會從負載均衡器獲取另一個實例進行重試,而不是在同一個實例上重試多次。 本系列代碼地址:https://github.com/JoJoTec/spring-cloud-parent 為何需要封裝異步 HT...
摘要:通用的抽象服務發現負載均衡和斷路器等模式適用于所有客戶端都可以使用的通用抽象層,獨立于實現例如,使用或發現。重試失敗的請求可以將負載均衡的配置為重試失敗的請求,默認情況下,禁用此邏輯,你可以通過將添加到應用程序的類路徑來啟用它。 Spring Cloud Commons:通用的抽象 服務發現、負載均衡和斷路器等模式適用于所有Spring Cloud客戶端都可以使用的通用抽象層,獨立于實...
摘要:授權框架使第三方應用程序來獲取對服務的有限訪問機會。無論是通過編排資源所有者和服務之間的交互批準的資源所有者,或通過允許第三方應用程序來獲取自己的訪問權限。 SpringCloud打造微服務平臺--概覽 簡述 SpringCloud是什么 Spring Boot和SpringCloud是什么關系 Spring Boot是Spring的一套快速WEB開發的腳手架,可建立獨立的Sprin...
摘要:繼承支持通過單繼承接口支持樣板,這允許將通用操作分組為方便的基本接口。,記錄基本信息以及請求和響應。例如,類定義參數和以下客戶端使用注解使用類 聲明式REST客戶端:Feign Feign是一個聲明式的Web服務客戶端,它使編寫Web服務客戶端變得更容易,要使用Feign,請創建一個接口并對其進行注解,它具有可插拔的注解支持,包括Feign注解和JAX-RS注解,Feign還支持可插拔...
閱讀 1290·2021-11-24 09:39
閱讀 2632·2021-09-30 09:47
閱讀 1325·2021-09-22 15:15
閱讀 2410·2021-09-10 10:51
閱讀 1954·2019-08-30 15:55
閱讀 2977·2019-08-30 11:06
閱讀 896·2019-08-30 10:53
閱讀 830·2019-08-29 17:26