springboot實戰電商項目mall4j (https://gitee.com/gz-yami/mall4j)java
java開源商城系統git
隨着微服務的流行,熔斷做爲其中一項很重要的技術也廣爲人知。當微服務的運行質量低於某個臨界值時,啓動熔斷機制,暫停微服務調用一段時間,以保障後端的微服務不會由於持續過負荷而宕機。本文介紹了Hystrix、新一代熔斷器Resilience4j以及阿里開源的Sentinel如何使用。若有錯誤歡迎指出。github
斷路器模式源於Martin Fowler的Circuit Breaker 一文。「斷路器」自己是一種開關裝置,用於在電路上保護線路過載,當線路中有電器發生短路時,「斷路器」可以及時切斷故障電路,防止發生過載、發熱甚至起火等嚴重後果。spring
在分佈式架構中,斷路器模式的做用也是相似的,當某個服務單元發生故障(相似用電器發生短路)以後,經過斷路器的故障監控(相似熔斷保險絲),向調用方返回一個錯誤響應,而不是長時間的等待。這樣就不會使得線程因調用故障服務被長時間佔用不釋放,避免了故障在分佈式系統中的蔓延。編程
針對上述問題,斷路器是進行實現了斷路、線程隔離、流量控制等一系列服務保護功能的框架。系統、服務和第三方庫的節點,從而對延遲和故障提供更強大的容錯能力。後端
Hystrix是一款Netfix開源的框架,具備依賴隔離,系統容錯降級等功能,這也是其最重要的兩種用途,還有請求合併等功能。緩存
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
@EnableCircuitBreaker public class TestApplication extends SpringBootServletInitializer{ public static void main(String[] args) { SpringApplication.run(ApiApplication.class, args); } }
@RequestMapping("/get/{id}") @HystrixCommand(fallbackMethod="errorCallBack") //測試沒有這個數據時,服務降級 public Object get(@PathVariable("id") long id){ Product p= productService.findById(id); if( p==null){ throw new RuntimeException("查無此商品"); } return p; } //指定一個降級的方法 public Object errorCallBack( @PathVariable("id") long id ){ return id+"不存在,error"; }
簡單介紹了Hystrix工做原理以及簡單案例,不過Hystrix官方已經中止開發,就不深刻介紹了。springboot
在Hystrix官方已經中止開發後,Hystrix官方推薦使用新一代熔斷器爲Resilience4j。Resilience4j是一款輕量級,易於使用的容錯庫,其靈感來自於Netflix Hystrix,可是專爲Java 8和函數式編程而設計。由於庫只使用了Vavr(之前稱爲 Javaslang ),它沒有任何其餘外部依賴下。相比之下,Netflix Hystrix對Archaius具備編譯依賴性,Archaius具備更多的外部庫依賴性,例如Guava和Apache Commons Configuration,若是須要使用Resilience4j,也無需引入全部依賴,只需選擇你須要的功能模塊便可。服務器
Resilience4j提供了幾個核心模塊:架構
resilience4j-circuitbreaker:電路斷開 resilience4j-ratelimiter:速率限制 resilience4j-bulkhead:隔板 resilience4j-retry:自動重試(同步和異步) resilience4j-timelimiter:超時處理 resilience4j-cache:結果緩存
引入依賴
<dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-circuitbreaker</artifactId> <version>0.13.2</version> </dependency>
請注意,使用此功能,咱們須要引入上文的resilience4j-circuitbreaker依賴。
該熔斷器模式下能夠幫助咱們在遠程服務出故障時防止故障級聯。
在屢次請求失敗後,咱們就認爲服務不可用/超載,而且對以後的全部請求進行短路處理,這樣咱們就能節約系統資源。讓咱們看看如何經過Resilience4j實現這一目標。
首先,咱們須要定義要使用的設置。最簡單的方法是使用默認設置:
CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults();
一樣也可使用自定義參數:
CircuitBreakerConfig config = CircuitBreakerConfig.custom() .failureRateThreshold(20) .ringBufferSizeInClosedState(5) .build();
在這裏,咱們將ratethreshold設置爲20%,而且最少重試5次。
而後,咱們建立一個 CircuitBreaker對象,並經過它調用遠程服務:
interface RemoteService { int process(int i); } CircuitBreakerRegistry registry = CircuitBreakerRegistry.of(config); CircuitBreaker circuitBreaker = registry.circuitBreaker("my"); Function<Integer, Integer> decorated = CircuitBreaker .decorateFunction(circuitBreaker, service::process);
最後,讓咱們看看它如何經過JUnit測試。
咱們調用服務10次。能夠驗證服務至少調用5次,若是有20%的失敗的狀況下,會中止調用。
when(service.process(any(Integer.class))).thenThrow(new RuntimeException()); for (int i = 0; i < 10; i++) { try { decorated.apply(i); } catch (Exception ignore) {} } verify(service, times(5)).process(any(Integer.class));
斷路器的三種狀態:
咱們能夠配置如下設置:
此功能須要使用resilience4j-ratelimiter依賴性。
簡單示例:
RateLimiterConfig config = RateLimiterConfig.custom().limitForPeriod(2).build(); RateLimiterRegistry registry = RateLimiterRegistry.of(config); RateLimiter rateLimiter = registry.rateLimiter("my"); Function<Integer, Integer> decorated = RateLimiter.decorateFunction(rateLimiter, service::process);
如今全部對decorateFunction的調用都符合rate limiter。
咱們能夠配置以下參數:
這裏須要引入resilience4j-bulkhead依賴,能夠限制對特定服務的併發調用數。
讓咱們看一個使用Bulkhead API配置併發調用的示例:
BulkheadConfig config = BulkheadConfig.custom().maxConcurrentCalls(1).build(); BulkheadRegistry registry = BulkheadRegistry.of(config); Bulkhead bulkhead = registry.bulkhead("my"); Function<Integer, Integer> decorated = Bulkhead.decorateFunction(bulkhead, service::process);
爲了測試,咱們能夠調用一個mock服務的方法。這種狀況下,咱們就確保Bulkhead不容許其餘任何調用:
CountDownLatch latch = new CountDownLatch(1); when(service.process(anyInt())).thenAnswer(invocation -> { latch.countDown(); Thread.currentThread().join(); return null; }); ForkJoinTask<?> task = ForkJoinPool.commonPool().submit(() -> { try { decorated.apply(1); } finally { bulkhead.onComplete(); } }); latch.await(); assertThat(bulkhead.isCallPermitted()).isFalse();
咱們能夠配置如下設置:
須要引入resilience4j-retry庫。可使用Retry調用失敗後自動重試:
RetryConfig config = RetryConfig.custom().maxAttempts(2).build(); RetryRegistry registry = RetryRegistry.of(config); Retry retry = registry.retry("my"); Function<Integer, Void> decorated = Retry.decorateFunction(retry, (Integer s) -> { service.process(s); return null; });
如今,讓咱們模擬在遠程服務調用期間引起異常的狀況,並確保庫自動重試失敗的調用:
when(service.process(anyInt())).thenThrow(new RuntimeException()); try { decorated.apply(1); fail("Expected an exception to be thrown if all retries failed"); } catch (Exception e) { verify(service, times(2)).process(any(Integer.class)); }
咱們還能夠配置:
cache模塊須要引入resilience4j-cache依賴。初始化代碼以下:
javax.cache.Cache cache = ...; // Use appropriate cache here Cache<Integer, Integer> cacheContext = Cache.of(cache); Function<Integer, Integer> decorated = Cache.decorateSupplier(cacheContext, () -> service.process(1));
這裏的緩存是經過 JSR-107 Cache實現完成的,Resilience4j提供了操做緩存的方法。
請注意,沒有用於裝飾方法的API(例如Cache.decorateFunction(Function)),該API僅支持 Supplier和Callable類型。
對於此模塊,咱們須要引入resilience4j-timelimiter依賴,能夠限制使用TimeLimiter調用遠程服務所花費的時間。
咱們設置一個TimeLimiter,配置的超時時間爲1毫秒以方便測試:
long ttl = 1; TimeLimiterConfig config = TimeLimiterConfig.custom().timeoutDuration(Duration.ofMillis(ttl)).build(); TimeLimiter timeLimiter = TimeLimiter.of(config);
接下來,讓咱們調用Future.get()驗證Resilience4j是否如預期超時:
Future futureMock = mock(Future.class); Callable restrictedCall = TimeLimiter.decorateFutureSupplier(timeLimiter, () -> futureMock); restrictedCall.call(); verify(futureMock).get(ttl, TimeUnit.MILLISECONDS);
咱們也能夠將其與斷路器(CircuitBreaker)結合使用:
Callable chainedCallable = CircuitBreaker.decorateCallable(circuitBreaker, restrictedCall);
Resilience4j還提供了許多附加的功能模塊,可簡化其與流行框架和庫的集成。
一些比較常見的集成是:
經過上文咱們瞭解了Resilience4j庫的各個方面的簡單使用,以及如何使用它來解決服務器間通訊中的各類容錯問題。Resilience4j的源碼能夠在GitHub上找到。
Sentinel 是面向分佈式服務架構的輕量級流量控制組件,由阿里開源,主要以流量爲切入點,從限流、流量整形、熔斷降級、系統負載保護等多個維度來保障微服務的穩定性。
Sentinel 是面向分佈式服務架構的高可用流量防禦組件,做爲阿里的熔斷中間件,Sentinel 承接了阿里巴巴近 10 年的雙十一大促流量的核心場景,對於流量防禦的高可用、穩定性方面是很突出的。
三種主流熔斷中間件的性能對比,如表所示: