使用Spring Boot + Resilience 4j實現斷路器

banq發表於2019-02-08

Resilience 4j提供以下功能。
  • 斷路器
  • RateLimiter
  • 艙壁
  • 重試
  • 快取記憶體
  • TimeLimiter

如果您打算在Spring Boot中使用它,可以使用Starter。請注意,Spring Boot 1.x和2.x系列之間的artifactId似乎有所不同。另外,上面只包含CircuitBreaker和RateLimiter,在使用其他功能時需要單獨新增依賴項。(由於未準備好AutoConfigure,您還需要自己定義bean。)
這次我將總結如何在Spring Boot 2.x系列中使用CircuitBreaker和RateLimiter。

環境
  • JDK 8
  • Spring Boot 2.1.2.RELEASE
  • Resilience 4j 0.13.2


斷路器
當某些具有微服務的服務發生故障時,可以臨時阻止對故障服務的訪問並防止故障傳播。
CircuitBreaker有三種狀態:Closed,Open,HalfOpen。如果是正常的,則它是關閉的,如果處理失敗超過一定數量,它將變為開啟並且訪問被阻止。當在開啟狀態下經過一段時間後,進入HalfOpen狀態。如果處理在HalfOpen狀態下失敗超過一定量,則返回到關閉狀態。

使用Spring Boot + Resilience 4j實現斷路器
在Resilience 4j中,處理的成功和失敗由環形緩衝器Ring Bit Buffer管理,並且當緩衝器中的故障數超過設定的速率時,狀態轉變。
斷路器使用狀態中的Ring Bit Buffer CLOSED來儲存呼叫的成功或失敗狀態。成功的呼叫儲存為0位,失敗的呼叫儲存為1位。Ring Bit Buffer具有(可配置的)固定大小。環位緩衝區在內部使用類似資料結構的BitSet來儲存與布林陣列相比節省記憶體的位。BitSet使用long []陣列來儲存這些位。這意味著BitSet只需要一個包含16個長(64位)值的陣列來儲存1024個呼叫的狀態。

使用Spring Boot + Resilience 4j實現斷路器
例如,如果環形緩衝區的大小為10,則必須至少評估10個呼叫,然後才能計算故障率。如果僅評估了9個呼叫,即使所有9個呼叫都失敗,斷路器也不會開啟。
用於Closed - > Open和HalfOpen - > Closed判斷的環形緩衝區是不同的,可以定義大小,但使用相同的判斷條件(錯誤率)。
在持續關閉時間結束後,斷路器狀態從OPEN更改為HALF_OPEN並允許呼叫以檢視後端是否仍然不可用或已再次可用。
斷路器使用另一個(可配置的)環位緩衝區來評估HALF_OPEN狀態中的故障率。如果故障率高於配置的閾值,則狀態將更改回OPEN。如果故障率低於或等於閾值,則狀態變回CLOSED。

此外,處理的成功和失敗由異常判斷。預設情況下,如果任何異常丟擲異常,則會將其視為處理失敗,但您也可以指定要將其視為失敗的條件。

設定
application.yml你可以設定定義多個斷路器。

resilience4j:
    circuitbreaker:
        backends:
            circuitA: # #斷路器名
                truering-buffer-size-in-closed-state: 5 #環形緩衝區是在封閉狀態下使用的大小
                ring-buffer-size-in-half-open-state: 3 # HalfOpen 狀態下的大小
                wait-duration-in-open-state : 5000 # Open持續時間
                failure-rate-threshold: 50 # 到開啟狀態的閾值
                record-failure-predicate: com.example.resilience.RecordFailurePredicate
                ignore-exceptions: 沒有失敗#異常類和計數
                    - com.example.resilience.exception.BusinessException
                record-exceptions: #異常類失敗和計數
                    - com.example.resilience.exception.SystemException
            circuitB:
                ・・・

如果你想只考慮一個特定的異常和故障使用RecordExceptions,當你不想忽視特定的異常時使用ignoreExceptions。

有兩種方法可以使用Spring AOP並在函式中實現它。無論哪種實現,如果Circuit處於Open狀態,它將生成CircuitBreakerOpenException。
在以下實現示例中,為簡單起見,它不是微服務。最初RestTemplate,我認為這將是Service Class 呼叫其他服務API等使用等的過程。

Spring AOP實現
透過@CircuitBreaker(name = "hogehoge")註釋到類或方法上則可以啟用斷路器。如果在類指定這個註釋,則為所有公共方法啟用斷路器。

@Service
@CircuitBreaker(name = "circuitB")
public class CircuitBreakerService {
    public String aop(String str) {
        if (str == null) {
            throw new RuntimeException();
        }
        return "success!!";
    }
}

呼叫者不用考慮任何事情,只需執行該方法即可。

@RestController
@RequestMapping("/circuit")
public class CircuitBreakerController {
    private final CircuitBreakerService service;

    public CircuitBreakerController(CircuitBreakerService service) {
        this.service = service;
    }

    @GetMapping("/aop")
    public String aop(@RequestParam(required = false) String str) {
        return service.aop(str);
    }
}


如何寫業務函式?

@Service
public class CircuitBreakerService {
    public String func(String str) {
        if (str == null) {
            throw new RuntimeException();
        }
        return "success!!";
    }
}


呼叫端使用斷路器的decorate~方法修飾要呼叫的方法。

@RestController
@RequestMapping("/circuit")
public class CircuitBreakerController {
    private final CircuitBreaker circuitBreaker;
    private final CircuitBreakerService service;

    public CircuitBreakerController(CircuitBreakerRegistry registry, CircuitBreakerService service) {
        this.circuitBreaker = registry.circuitBreaker("circuitA");
        this.service = service;
    }

    @GetMapping("/func")
    public String func(@RequestParam(required = false) String str) {
        return CircuitBreaker.decorateSupplier(circuitBreaker, () -> service.func(str)).get();
    }
}


後備處理
接下來,如果發生故障,執行回退過程怎麼辦?在Hystrix 的情況下,透過指定@HystrixCommand("hogeMethod"),由於Resilience4j沒有設定的這樣的功能,必須自己實現。

@RestController
@RequestMapping("/circuit")
public class CircuitBreakerController {
    private final CircuitBreaker circuitBreaker;
    private final CircuitBreakerService service;

    public CircuitBreakerController(CircuitBreakerRegistry registry, CircuitBreakerService service) {
        this.circuitBreaker = registry.circuitBreaker("circuitA");
        this.service = service;
    }

    @GetMapping("/func")
    public String func(@RequestParam(required = false) String str) {
        return Try.ofSupplier(CircuitBreaker.decorateSupplier(circuitBreaker, () -> service.func(str)))
                .recover(CircuitBreakerOpenException.class, "Circuit is Open!!")
                .recover(RuntimeException.class, "fallback!!").get();
    }
}


完整的原始碼位於下方。https://github.com/d-yosh/spring-boot-resilience4j-example


RateLimiter
您可以限制每單位時間的執行次數。
單位時間是一個週期,並且可以在一個週期中執行的數量是有限的。如果它超過了可以在一個迴圈中執行的上限,則讓它等待,如果等待時間超過超時時間,則發生RequestNotPermitted。

在application.yml可以定義多個RateLimiter。


resilience4j:
    ratelimiter:
        limiters:
            limiterA: # #Rate​​Limiter名稱
                limit-for-period: 1 # 每時間單位#可執行處理數
                limit-refresh-period-in-millis: 10000 # #單位時間(毫秒)
                timeout-in-millis: 10000 #timeout time(milliseconds)
            limiterB:
            ・・・

它與斷路器實現方式相同,有兩種方法可以使用Spring AOP並在業務函式中編寫它。實現方法也類似於斷路器。

@Service
@RateLimiter(name = "limiterB")
public class RateLimiterService {
    public String func() {
        return LocalDateTime.now().toString();
    }
}

呼叫者不用考慮任何事情,只需執行該方法即可。

@RestController
@RequestMapping("/ratelimiter")
public class RateLimiterController {
    private final RateLimiterService service;

    public RateLimiterController(RateLimiterService service) {
        this.service = service;
    }

    @GetMapping("aop")
    public String aop() {
        return service.aop();
    }
}


函式方法:

@Service
public class RateLimiterService {
    public String func() {
        return LocalDateTime.now().toString();
    }
}

@RestController
@RequestMapping("/ratelimiter")
public class RateLimiterController {
    private final RateLimiter rateLimiter;
    private final RateLimiterService service;

    public RateLimiterController(RateLimiterRegistry registry, RateLimiterService service) {
        this.rateLimiter = registry.rateLimiter("limiterA");
        this.service = service;
    }

    @GetMapping("func")
    public String func() {
        return Try.ofSupplier(RateLimiter.decorateSupplier(rateLimiter, service::func))
                .recover(RequestNotPermitted.class, "Request Not Permitted!!").get();
    }
}


後備處理,與斷路器一樣,沒有自動執行回退處理的機制,因此您需要自己實現它。

完整的原始碼位於下方。https://github.com/d-yosh/spring-boot-resilience4j-example
單位時間為5秒,超時時間為1秒,每單位時間的執行次數為1。如果同時傳送多個請求,則會發出失敗請求。(如果您同時請求三個,則至少一個將始終失敗。)


$ curl http://localhost:8080/ratelimiter/func
2019-01-22T23:09:35.612
$ curl http://localhost:8080/ratelimiter/func
Request Not Permitted!!



 

相關文章