Circuit Breaker pattern là một pattern được sử dụng trong trường hợp các bạn muốn tạm thời ứng dụng không gọi đến các service đang bị lỗi hoặc việc gọi đến các service này đang bị chậm, ngắt kết nối tới chúng và thử kết nối lại ngay khi service đã được hồi phục. Bằng cách định nghĩa 5 trạng thái của việc gọi đến các service là: OPEN, CLOSED, HALF_OPEN, DISABLED và FORCED_OPEN, Circuit Breaker pattern sẽ giúp chúng ta dễ dàng handle trong trường hợp ứng dụng xảy ra lỗi khi gọi tới các service dependency của nó. Các bạn có thể hiện thực Circuit Breaker pattern sử dụng thư viện Resilience4j CircuitBreaker. Cụ thể như thế nào? Chúng ta hãy cùng nhau tìm hiểu về Circuit Breaker pattern và cách hiện thực pattern này sử dụng thư viện Resilience4j CircuitBreaker này các bạn nhé!
Đầu tiên, mình sẽ tạo mới một Maven project để làm ví dụ:
Các bạn khai báo Resilience4j CircuitBreaker dependency như sau:
1 2 3 4 5 |
<dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-circuitbreaker</artifactId> <version>2.2.0</version> </dependency> |
Nói về 5 trạng thái được định nghĩa trong CircuitBreaker pattern thì mình có thể nói nôm na như sau:
- OPEN: Khi hiện thực CircuitBreaker pattern, chúng ta sẽ cấu hình một ngưỡng để khi việc gọi tới các service dependency bị lỗi, nếu vượt quá ngưỡng thì CircuitBreaker sẽ được mở, ngăn chặn việc gọi tới các service bị lỗi này. Ngưỡng này có thể theo số lần call tới service bị failed hoặc theo khoảng thời gian mà ứng dụng call tới service bị failed.
- HALF_OPEN: Sau khi CircuitBreaker được mở, sau một khoảng thời gian nào đó, CircuitBreader sẽ cho phép một số request tới các service được thực hiện để kiểm tra xem service đã hoạt động lại chưa. Nếu vẫn còn lỗi thì trạng thái OPEN vẫn tiếp tục được giữ, còn nếu việc gọi tới service đã hết lỗi, CircuitBreaker sẽ tự động chuyển sang trạng thái CLOSED.
- CLOSED: như mình đã nói ở trên, một khi việc gọi tới các service dependency đã bình thường trở lại, CircuitBreaker sẽ CLOSED và cho phép tất cả các request tới các service sẽ được hoạt động bình thường.
- DISABLED: đây là trạng thái tắt CircuitBreaker, các request tới các service dependency sẽ luôn được thực hiện.
- FORCE_OPEN: CircuitBreaker luôn luôn mở và do đó, việc gọi tới các service dependency sẽ luôn bị chặn.
Bây giờ mình sẽ sử dụng thư viện Resilience4j CircuitBreaker để hiện thực CircuitBreaker pattern cho một ứng dụng đơn giản xem sao các bạn nhé.
Giả sử mình có một ứng dụng đơn giản với class Hello có phương thức hello() trả về chuỗi “Hello from Huong Dan Java” như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package com.huongdanjava.resilience4j; public class Hello { private int failedTimes; private int maximumFailedTimes; public Hello(int maximumFailedTimes) { this.maximumFailedTimes = maximumFailedTimes; } public String hello() { System.out.println("hello() called ..."); if (failedTimes < maximumFailedTimes) { failedTimes++; throw new RuntimeException("Something went wrong"); } return "Hello from Huong Dan Java"; } } |
Để giả lập việc service dependency đôi lúc sẽ không kết nối được trong một khoảng thời gian nào đó, mình đã định nghĩa class Hello với constructor có 1 tham số là maximumFailedTimes, mang ý nghĩa, khi ứng dụng gọi tới phương thức hello() của class Hello, sẽ có maximum lần bị failed trước khi mọi thứ diễn ra bình thường.
Thư viện Resilience4j CircuitBreaker sử dụng class CircuitBreaker để handle các trạng thái khi ứng dụng gọi đến các service dependency. Class CircuitBreaker sẽ được định nghĩa các cấu hình liên quan đến ngưỡng, khi nào CircuitBreaker sẽ có trạng thái là OPEN, khi nào là CLOSED, … Tương tự như thư viện Resilience4j TimeLimiter, thư viện Resilience4j CircuitBreaker cũng có class CircuitBreakerRegistry để quản lý các đối tượng CircuitBreaker này.
Chúng ta có thể khởi tạo đối tượng CircuitBreakerRegistry với các cấu hình mặc định như sau:
1 |
CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults(); |
Đối tượng của class CircuitBreaker được lấy từ đối tượng circuitBreakerRegistry trên sẽ có các cấu hình mặc định như sau:
- failureRateThreshold: ngưỡng tỷ lệ lỗi theo phần trăm. Giá trị mặc định của cấu hình này là 50%.
- slowCallDurationThreshold: ngưỡng khoảng thời gian mà các call được xem là chậm. Giá trị mặc định cho cấu hình này là 60s.
- slowCallRateThreshold: ngưỡng tỉ lệ của slow call theo phần trăm. CircuitBreaker sẽ xem một call là chậm nếu khoảng thời gian của call lớn hơn giá trị của cấu hình slowCallDurationThreshold. Giá trị của cấu hình này là 100% và khi tỉ lệ slow call vượt qua ngưỡng này, CircuitBreaker sẽ chuyển sang trạng thái open các bạn nhé!
- permittedNumberOfCallsInHalfOpenState: số lượng call cho phép qua CircuitBreaker ở thời điểm trạng thái của nó là HALF_OPEN. Giá trị mặc định của cấu hình là 10 call.
- maxWaitDurationInHalfOpenState: khoảng thời gian đợi tối đa của CircuitBreaker ở trạng thái HALF_OPEN trước khi chuyển sang trạng thái OPEN. Giá trị mặc định của cấu hình này là 0ms. Điều này có nghĩa, CircuitBreaker sẽ đợi cho đến khi các request được permit đi qua được hoàn thành.
- slidingWindowType: cấu hình này liên quan đến việc cách chúng ta count số lượng request có vấn đề trước khi CircuitBreaker chuyển sang trạng thái OPEN để ngăn chặn các vấn đề này. Có 2 cách count số lượng request có lỗi là COUNT_BASED và TIME_BASED. COUNT_BASED sẽ count số lượng request bị lỗi còn TIME_BASED sẽ căn cứ vào khoảng thời gian các request bị lỗi. Mặc định của cấu hình này là COUNT_BASED nha các bạn!
- slidingWindowSize: số lượng request bị lỗi hoặc khoảng thời gian mà các request bị lỗi tính theo giây, tuỳ theo giá trị mà các bạn đang cấu hình cho slidingWindowType. Giá trị mặc định của cấu hình này là 100.
- minimumNumberOfCalls: số lượng call tối thiểu trước khi CircuitBreaker count số lượng call bị lỗi hoặc chậm. Giá trị mặc định là 100.
- waitDurationInOpenState: khoảng thời gian chờ trước khi CircuitBreaker chuyển từ trạng thái OPEN qua HALF_OPEN. Giá trị mặc định là 60s.
- automaticTransitionFromOpenToHalfOpenEnabled: sử dụng cấu hình này nếu các bạn muốn việc chuyển trạng thái từ OPEN qua HALF_OPEN được diễn ra tự động. Giá trị mặc định của cấu hình này là false.
- recordExceptions: cấu hình các Exception sẽ được tính là lỗi.
- ignoreExceptions: cấu hình các Exception sẽ không được tính là lỗi.
- recordFailurePredicate: cho phép chúng ta sử dụng Predicate để xác định trong trường hợp nào thì Exception sẽ được tính là lỗi. Mặc định thì tất cả các Exception đều sẽ được tính là lỗi.
- ignoreExceptionPredicate: cho phép chúng ta sử dụng Predicate để xác định trong trường hợp nào thì Exception sẽ được ignore, không tính là lỗi. Mặc định thì không có Exception nào được ignore.
Các bạn có thể sử dụng class CircuitBreakerConfig để thay đổi các cấu hình mặc định này, ví dụ như sau:
1 2 3 4 5 6 |
CircuitBreakerConfig config = CircuitBreakerConfig.custom() .failureRateThreshold(30) .slidingWindowSize(5) .build(); CircuitBreakerRegistry registry = CircuitBreakerRegistry.of(config); |
Ở đây, mình đang cấu hình để CircuitBreaker sẽ chuyển sang trạng thái OPEN nếu 30% của 5 lần call tới phương thức hello() của đối tượng Hello bị failed.
Bây giờ, chúng ta sẽ lấy đối tượng CircuitBreaker từ đối tượng của class CircuitBreakerRegistry như sau:
1 |
CircuitBreaker circuitBreaker = registry.circuitBreaker("hello"); |
Chúng ta sẽ truyền thông tin name mà chúng ta muốn đặt cho CircuitBreaker. Ở đây, như các bạn thấy, mình gán name cho đối tượng CircuitBreaker của mình là “hello”.
Bây giờ thì chúng ta sẽ sử dụng các phương thức static decorate…() của class CircuitBreaker, pass thông tin về đối tượng CircuitBreaker ở trên để execute phương thức gọi đến service dependency thông qua CircuitBreaker. Ví dụ, mình sử dụng phương thức static decorateSupplier() như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
CircuitBreaker circuitBreaker = registry.circuitBreaker("hello"); Hello hello = new Hello(2); Supplier<String> decorated = CircuitBreaker.decorateSupplier(circuitBreaker, hello::hello); for (int i = 0; i < 20; i++) { System.out.println("CircuitBreaker state is " + circuitBreaker.getState()); try { String s = decorated.get(); System.out.println(s); } catch (Exception e) { System.out.println("Error: " + e.getMessage()); } } |
Khi CircuitBreaker gọi đến phương thức hello() của đối tượng của class Hello, sẽ có 2 lần call bị failed, tỉ lệ lỗi là 40%. So với ngưỡng mà mình cấu hình ở trên là 30% thì CircuitBreaker sẽ OPEN. Do đó, chạy ứng dụng ví dụ này, các bạn sẽ thấy kết quả như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
CircuitBreaker state is CLOSED hello() called ... Error: Something went wrong CircuitBreaker state is CLOSED hello() called ... Error: Something went wrong CircuitBreaker state is CLOSED hello() called ... Hello from Huong Dan Java CircuitBreaker state is CLOSED hello() called ... Hello from Huong Dan Java CircuitBreaker state is CLOSED hello() called ... Hello from Huong Dan Java CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls |
Sau 5 lần call, CircuitBreaker sẽ chuyển sang trạng thái OPEN và không có call nào sau đó tiếp tục được gọi tới phương thức hello().
Vì mình đang cấu hình chỉ có 2 call lần đầu tới phương thức hello() sẽ bị lỗi và các request tiếp đó sẽ luôn thành công mà giá trị mặc định của waitDurationInOpenState tới 60s nên nếu bây giờ, mình giảm thời gian của waitDurationInOpenState xuống còn 2ms:
1 2 3 4 5 |
CircuitBreakerConfig config = CircuitBreakerConfig.custom() .failureRateThreshold(30) .slidingWindowSize(5) .waitDurationInOpenState(Duration.ofMillis(2)) .build(); |
Chạy lại ví dụ, các bạn sẽ thấy, sau 2ms, CircuitBreaker sẽ chuyển sang trạng thái HALF_OPEN và sau đó là CLOSED như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
CircuitBreaker state is CLOSED hello() called ... Error: Something went wrong CircuitBreaker state is CLOSED hello() called ... Error: Something went wrong CircuitBreaker state is CLOSED hello() called ... Hello from Huong Dan Java CircuitBreaker state is CLOSED hello() called ... Hello from Huong Dan Java CircuitBreaker state is CLOSED hello() called ... Hello from Huong Dan Java CircuitBreaker state is OPEN Error: CircuitBreaker 'hello' is OPEN and does not permit further calls CircuitBreaker state is OPEN hello() called ... Hello from Huong Dan Java CircuitBreaker state is HALF_OPEN hello() called ... Hello from Huong Dan Java CircuitBreaker state is HALF_OPEN hello() called ... Hello from Huong Dan Java CircuitBreaker state is HALF_OPEN hello() called ... Hello from Huong Dan Java CircuitBreaker state is HALF_OPEN hello() called ... Hello from Huong Dan Java CircuitBreaker state is HALF_OPEN hello() called ... Hello from Huong Dan Java CircuitBreaker state is HALF_OPEN hello() called ... Hello from Huong Dan Java CircuitBreaker state is HALF_OPEN hello() called ... Hello from Huong Dan Java CircuitBreaker state is HALF_OPEN hello() called ... Hello from Huong Dan Java CircuitBreaker state is HALF_OPEN hello() called ... Hello from Huong Dan Java CircuitBreaker state is CLOSED hello() called ... Hello from Huong Dan Java CircuitBreaker state is CLOSED hello() called ... Hello from Huong Dan Java CircuitBreaker state is CLOSED hello() called ... Hello from Huong Dan Java CircuitBreaker state is CLOSED hello() called ... Hello from Huong Dan Java |
Như vậy là chúng ta đã thành công trong việc hiện thực CircuitBreaker pattern sử dụng thư viện Resilience4j CircuitBreaker rồi đó các bạn!