Add resilience4j-circuit-breaker: Resilience4j 2.4.0 vs Spring Framework 7 core, on Boot 4.1
Companion module for the rewritten post 'Resilience4j Circuit Breaker in Spring Boot 4.1: What It's Still For', reworked around Framework 7 now shipping @Retryable/@ConcurrencyLimit in core. Covers what's still Resilience4j's job (circuit breaker, rate limiter, bulkhead's bounded wait, fallback methods, Actuator/Micrometer metrics), the off-by-one between maxAttempts and maxRetries, and two Boot-4.1 build breaks: spring-boot-starter-aop no longer exists (renamed to spring-boot-starter-aspectj, proven with Maven Central metadata and the renamed starter's own POM -- see resilience/docs/08-starter-aop-renamed-to-starter-aspectj.md in this same repo), and the resulting fix uses that renamed starter directly rather than assembling spring-aop + aspectjweaver by hand. Kept as its own module rather than a new top-level repository, alongside the existing resilience/ module for the sibling Framework-7 post.
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
# 2. The circuit breaker, verified
|
||||
|
||||
[Previous: 01-two-resilience-stacks.md](01-two-resilience-stacks.md) | [README](../README.md) | Next: [03-spring-retryable.md](03-spring-retryable.md)
|
||||
|
||||
Source: [`R4jPaymentService.java`](../src/main/java/com/ankurm/resilience/r4j/R4jPaymentService.java).
|
||||
Config: [`application.yml`](../src/main/resources/application.yml).
|
||||
Test: [`CircuitBreakerTripAndRecoverTest.java`](../src/test/java/com/ankurm/resilience/r4j/CircuitBreakerTripAndRecoverTest.java).
|
||||
Transcript: [`docs/output/01-circuitbreaker-trip.txt`](output/01-circuitbreaker-trip.txt).
|
||||
|
||||
## The state machine
|
||||
|
||||
```
|
||||
CLOSED --(failure rate >= threshold, over >= minimumNumberOfCalls)--> OPEN
|
||||
OPEN --(waitDurationInOpenState elapses)--> HALF_OPEN
|
||||
HALF_OPEN --(permittedNumberOfCallsInHalfOpenState calls all succeed)--> CLOSED
|
||||
HALF_OPEN --(a probe call fails)--> OPEN
|
||||
```
|
||||
|
||||
This repo's config: `slidingWindowSize=10`, `minimumNumberOfCalls=5`, `failureRateThreshold=50`,
|
||||
`waitDurationInOpenState=2s`, `permittedNumberOfCallsInHalfOpenState=2`.
|
||||
|
||||
## What the transcript actually shows
|
||||
|
||||
Calls 1-5 against a dead downstream: each one really calls the downstream, gets a
|
||||
`DownstreamUnavailableException`, and the fallback method runs. By call 5 the breaker has seen
|
||||
5 calls (the configured minimum) at a 100% failure rate and flips to `OPEN`. Call 6 is the
|
||||
first call that does **not** touch the downstream at all — it gets `CallNotPermittedException`
|
||||
instead, straight from the breaker, before `R4jPaymentService.pay()`'s body ever runs.
|
||||
|
||||
Calls 7-9 (phase 2 in the transcript) confirm the downstream call counter is frozen at 5 for
|
||||
the rest of the OPEN period — three more calls, zero more downstream traffic. This is the
|
||||
property `@Retryable` cannot offer on its own (see [chapter 3](03-spring-retryable.md)): a
|
||||
circuit breaker remembers that the dependency is down and stops asking.
|
||||
|
||||
After `waitDurationInOpenState` (2s; the test waits 2.2s to be safely past it) the breaker
|
||||
allows exactly `permittedNumberOfCallsInHalfOpenState` probe calls through. The downstream was
|
||||
reconfigured to succeed by then, both probes pass, and the breaker closes.
|
||||
|
||||
## `minimumNumberOfCalls` and why it exists
|
||||
|
||||
A fresh breaker with one failed call and no `minimumNumberOfCalls` floor would trip on pure
|
||||
noise — one deployment-time connection refused, one cold JVM. `minimumNumberOfCalls=5` here
|
||||
means the first four failures cannot trip anything by themselves; the breaker needs a real
|
||||
sample before it judges the dependency.
|
||||
|
||||
## Actuator health vs the circuitbreakers endpoint
|
||||
|
||||
With `management.endpoint.health.show-details: always`, `GET /actuator/health` reports only an
|
||||
aggregate `"circuitBreakers":{"status":"UNKNOWN"}` in this version — no per-instance detail.
|
||||
The full breakdown (`state`, `failureRate`, `bufferedCalls`, `notPermittedCalls`, ...) is at
|
||||
`GET /actuator/circuitbreakers` instead. Both captured live, breaker actually OPEN, via
|
||||
[`ActuatorHealthTest.java`](../src/test/java/com/ankurm/resilience/r4j/ActuatorHealthTest.java):
|
||||
[`05-actuator-health-tripped.txt`](output/05-actuator-health-tripped.txt).
|
||||
|
||||
- Resilience4j reference: [CircuitBreaker](https://resilience4j.readme.io/docs/circuitbreaker) (`rel=nofollow`)
|
||||
- The actual states and transition rules, from the CircuitBreaker interface itself, are worth
|
||||
reading directly rather than from a diagram: `io.github.resilience4j.circuitbreaker.CircuitBreaker.State`.
|
||||
|
||||
Next: [03-spring-retryable.md](03-spring-retryable.md).
|
||||
Reference in New Issue
Block a user