摘要:本系列代碼地址上一節(jié)我們通過(guò)單元測(cè)試驗(yàn)證了線程隔離的正確性,這一節(jié)我們來(lái)驗(yàn)證我們斷路器的正確性,主要包括驗(yàn)證配置正確加載即我們?cè)谂渲美缰械募尤氲牡呐渲帽徽_加載應(yīng)用了。
上一節(jié)我們通過(guò)單元測(cè)試驗(yàn)證了線程隔離的正確性,這一節(jié)我們來(lái)驗(yàn)證我們斷路器的正確性,主要包括:
application.yml
)中的加入的 Resilience4j 的配置被正確加載應(yīng)用了。與之前驗(yàn)證重試類(lèi)似,我們可以定義不同的 FeignClient,之后檢查 resilience4j 加載的斷路器配置來(lái)驗(yàn)證線程隔離配置的正確加載。
并且,與重試配置不同的是,通過(guò)系列前面的源碼分析,我們知道 spring-cloud-openfeign 的 FeignClient 其實(shí)是懶加載的。所以我們實(shí)現(xiàn)的斷路器也是懶加載的,需要先調(diào)用,之后才會(huì)初始化斷路器。所以這里我們需要先進(jìn)行調(diào)用之后,再驗(yàn)證斷路器配置。
首先定義兩個(gè) FeignClient,微服務(wù)分別是 testService1 和 testService2,contextId 分別是 testService1Client 和 testService2Client
@FeignClient(name = "testService1", contextId = "testService1Client")public interface TestService1Client { @GetMapping("/anything") HttpBinAnythingResponse anything();}@FeignClient(name = "testService2", contextId = "testService2Client") public interface TestService2Client { @GetMapping("/anything") HttpBinAnythingResponse anything();}
然后,我們?cè)黾?Spring 配置,并且給兩個(gè)微服務(wù)都添加一個(gè)實(shí)例,使用 SpringExtension 編寫(xiě)單元測(cè)試類(lèi):
//SpringExtension也包含了 Mockito 相關(guān)的 Extension,所以 @Mock 等注解也生效了@ExtendWith(SpringExtension.class)@SpringBootTest(properties = { //默認(rèn)請(qǐng)求重試次數(shù)為 3 "resilience4j.retry.configs.default.maxAttempts=3", // testService2Client 里面的所有方法請(qǐng)求重試次數(shù)為 2 "resilience4j.retry.configs.testService2Client.maxAttempts=2", //默認(rèn)斷路器配置 "resilience4j.circuitbreaker.configs.default.slidingWindowSize=5", "resilience4j.circuitbreaker.configs.default.minimumNumberOfCalls=2", //testService2Client 的 斷路器配置 "resilience4j.circuitbreaker.configs.testService2Client.failureRateThreshold=30", "resilience4j.circuitbreaker.configs.testService2Client.minimumNumberOfCalls=10", })@Log4j2public class OpenFeignClientTest { @SpringBootApplication @Configuration public static class App { @Bean public DiscoveryClient discoveryClient() { //模擬兩個(gè)服務(wù)實(shí)例 ServiceInstance service1Instance1 = Mockito.spy(ServiceInstance.class); ServiceInstance service2Instance2 = Mockito.spy(ServiceInstance.class); Map zone1 = Map.ofEntries( Map.entry("zone", "zone1") ); when(service1Instance1.getMetadata()).thenReturn(zone1); when(service1Instance1.getInstanceId()).thenReturn("service1Instance1"); when(service1Instance1.getHost()).thenReturn("www.httpbin.org"); when(service1Instance1.getPort()).thenReturn(80); when(service2Instance2.getInstanceId()).thenReturn("service1Instance2"); when(service2Instance2.getHost()).thenReturn("httpbin.org"); when(service2Instance2.getPort()).thenReturn(80); DiscoveryClient spy = Mockito.spy(DiscoveryClient.class); Mockito.when(spy.getInstances("testService1")) .thenReturn(List.of(service1Instance1)); Mockito.when(spy.getInstances("testService2")) .thenReturn(List.of(service2Instance2)); return spy; } }}
編寫(xiě)測(cè)試代碼,驗(yàn)證配置正確:
@Test public void testConfigureCircuitBreaker() { //防止斷路器影響 circuitBreakerRegistry.getAllCircuitBreakers().asJava().forEach(CircuitBreaker::reset); //調(diào)用下這兩個(gè) FeignClient 確保對(duì)應(yīng)的 NamedContext 被初始化 testService1Client.anything(); testService2Client.anything(); //驗(yàn)證斷路器的實(shí)際配置,符合我們的填入的配置 List circuitBreakers = circuitBreakerRegistry.getAllCircuitBreakers().asJava(); Set collect = circuitBreakers.stream().map(CircuitBreaker::getName) .filter(name -> { try { return name.contains(TestService1Client.class.getMethod("anything").toGenericString()) || name.contains(TestService2Client.class.getMethod("anything").toGenericString()); } catch (NoSuchMethodException e) { return false; } }).collect(Collectors.toSet()); Assertions.assertEquals(collect.size(), 2); circuitBreakers.forEach(circuitBreaker -> { if (circuitBreaker.getName().contains(TestService1Client.class.getName())) { Assertions.assertEquals((int) circuitBreaker.getCircuitBreakerConfig().getFailureRateThreshold(), (int) DEFAULT_FAILURE_RATE_THRESHOLD); Assertions.assertEquals(circuitBreaker.getCircuitBreakerConfig().getMinimumNumberOfCalls(), DEFAULT_MINIMUM_NUMBER_OF_CALLS); } else if (circuitBreaker.getName().contains(TestService2Client.class.getName())) { Assertions.assertEquals((int) circuitBreaker.getCircuitBreakerConfig().getFailureRateThreshold(), (int) TEST_SERVICE_2_FAILURE_RATE_THRESHOLD); Assertions.assertEquals(circuitBreaker.getCircuitBreakerConfig().getMinimumNumberOfCalls(), TEST_SERVICE_2_MINIMUM_NUMBER_OF_CALLS); } }); }
我們給 TestService1Client 添加一個(gè)方法:
@GetMapping("/status/500")String testCircuitBreakerStatus500();
這個(gè)方法一定會(huì)調(diào)用失敗,從而導(dǎo)致斷路器打開(kāi)。經(jīng)過(guò) 2 次失敗以上后(因?yàn)榕渲米钌儆|發(fā)斷路器打開(kāi)的請(qǐng)求個(gè)數(shù)為 2),驗(yàn)證斷路器狀態(tài):
@Testpublic void testCircuitBreakerOpenBasedOnServiceAndMethod() { //防止斷路器影響 circuitBreakerRegistry.getAllCircuitBreakers().asJava().forEach(CircuitBreaker::reset); AtomicBoolean passed = new AtomicBoolean(false); for (int i = 0; i < 10; i++) { //多次調(diào)用會(huì)導(dǎo)致斷路器打開(kāi) try { System.out.println(testService1Client.testCircuitBreakerStatus500()); } catch(Exception e) {} List circuitBreakers = circuitBreakerRegistry.getAllCircuitBreakers().asJava(); circuitBreakers.stream().filter(circuitBreaker -> { return circuitBreaker.getName().contains("testCircuitBreakerStatus500") && circuitBreaker.getName().contains("TestService1Client"); }).findFirst().ifPresent(circuitBreaker -> { //驗(yàn)證對(duì)應(yīng)微服務(wù)和方法的斷路器被打開(kāi) if (circuitBreaker.getState().equals(CircuitBreaker.State.OPEN)) { passed.set(true); //斷路器打開(kāi)后,調(diào)用其他方法,不會(huì)拋出斷路器打開(kāi)異常 testService1Client.testAnything(); } }); } Assertions.assertTrue(passed.get());}
這樣,我們就成功驗(yàn)證了,驗(yàn)證斷路器是基于服務(wù)和方法打開(kāi)的。
微信搜索“我的編程喵”關(guān)注公眾號(hào),每日一刷,輕松提升技術(shù),斬獲各種offer:
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/123768.html
摘要:在上面打開(kāi)一個(gè)微服務(wù)某個(gè)實(shí)例的一個(gè)路徑的斷路器之后,我們調(diào)用其他的路徑,無(wú)論多少次,都成功并且調(diào)用負(fù)載均衡器獲取服務(wù)實(shí)例的次數(shù)等于調(diào)用次數(shù),代表沒(méi)有重試,也就是沒(méi)有斷路器異常。 本系列代碼地址:??https://github.com/JoJoTec/spring-cloud-parent??我們來(lái)測(cè)試下前面封裝好的 We...
摘要:本系列代碼地址我們繼續(xù)上一節(jié),繼續(xù)使用測(cè)試我們自己封裝的測(cè)試針對(duì)重試測(cè)試針對(duì)重試針對(duì)響應(yīng)超時(shí),我們需要驗(yàn)證重試僅針對(duì)可以重試的方法包括方法以及配置的可重試方法,針對(duì)不可重試的方法沒(méi)有重試。本系列代碼地址:https://github.com/JoJoTec/spring-cloud-parent我們繼續(xù)上一節(jié),繼續(xù)使用 spock 測(cè)試我們自己封裝的 WebClient測(cè)試針對(duì) readTi...
摘要:對(duì)于異步的請(qǐng)求,使用的是異步客戶端即。要實(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...
摘要:將請(qǐng)求封裝成將請(qǐng)求封裝成的接口定義是但是最外層傳進(jìn)來(lái)的參數(shù)是和,需要將他們封裝成,這個(gè)工作就是在中做的。其實(shí)主要任務(wù)就是將各種參數(shù)封裝成除了和本次請(qǐng)求相關(guān)的和,還有會(huì)話管理器,編碼解碼器配置,國(guó)際化配置還有用于擴(kuò)展。本系列代碼地址:https://github.com/JoJoTec/spring-cloud-parent接下來(lái),將進(jìn)入我們升級(jí)之路的又一大模塊,即網(wǎng)關(guān)模塊。網(wǎng)關(guān)模塊我們廢棄了...
摘要:在這里,會(huì)將上下文中載入的拼接成,然后調(diào)用其方法的,它是的處理請(qǐng)求業(yè)務(wù)的起點(diǎn)。添加相關(guān)依賴(lài)之后,會(huì)有這個(gè)。路由權(quán)重相關(guān)配置功能相關(guān)實(shí)現(xiàn)類(lèi),這個(gè)我們這里不關(guān)心。本系列代碼地址:https://github.com/JoJoTec/spring-cloud-parent我們繼續(xù)分析上一節(jié)提到的 WebHandler,經(jīng)過(guò)將請(qǐng)求封裝成 ServerWebExchange 的 HttpWebHand...
閱讀 3492·2021-11-18 10:07
閱讀 1589·2021-11-04 16:08
閱讀 1515·2021-11-02 14:43
閱讀 1093·2021-10-09 09:59
閱讀 845·2021-09-08 10:43
閱讀 1083·2021-09-07 09:59
閱讀 967·2019-12-27 11:56
閱讀 1015·2019-08-30 15:56