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