This example demonstrates the operation of a circuit breaker as it transitions between the open
, half-open
, and closed
states in response to healthy and unhealthy service response times.
Example program structure
This example consists of a Spring Boot application utilizing resilience4j annotations. The application consists of a REST API which responds to GET /
requests after a configurable delay and a client which issues such requests on a fixed schedule. The client wraps its HTTP calls with a circuit-breaker, which may prevent calls to the REST API from being made on any attempt:
/**
* Makes a blocking call to the ping endpoint and returns the body as a
* String.
*/
@CircuitBreaker(name = "mycircuitbreaker")
public String ping() {
return webClient.get()
.exchange()
.block()
.bodyToMono(String.class)
.block();
}
The breaker is configured to trip into the OPEN
state and deny requests after a single slow or erroneous response from the protected HTTP call. It will remain OPEN
for a while and deny subsequent requests, then transition to HALF-OPEN
and allow a single probe request through. If the probe is still unhealthy, the breaker returns to the OPEN
state and continues to deny requests. Otherwise it will CLOSE
and allow all requests through until another slow response trips the breaker. The configuration is located in the application.yaml:
# Configuration parameters are documented here:
# https://resilience4j.readme.io/docs/circuitbreaker#create-and-configure-a-circuitbreaker
resilience4j.circuitbreaker:
instances:
mycircuitbreaker:
slowCallDurationThreshold: '500ms'
permittedNumberOfCallsInHalfOpenState: 1
waitDurationInOpenState: '5s'
# Sliding Window
slidingWindowType: 'COUNT_BASED'
slidingWindowSize: 1
minimumNumberOfCalls: 1
The GET /
endpoint responds to requests after a delay; this delay may be configured by sending a request to POST localhost:8080/
, like so:
curl localhost:8080/ -H 'Content-Type: application/json' -d '{"delay_millis": 2000}'
By reconfiguring the delay, we may create conditions under which the breaker will trip. For simplicity’s sake our server and client run in the same application, but were that not so we could also kill or otherwise crash the server to trip the client’s breaker.
Example in action
From the root of this repository, invoke the following:
./gradlew -p circuit-breaker/example/general bootRun
When the application starts, the Requester’s circuit breaker is initially in the `CLOSED
state and will therefore allow GET localhost:8080/
through. The controller responds after a 200 ms delay by default, which the circuit-breaker considers to be a healthy response time. As such, the following healthy logs are initially printed:
... 21:19:50.218 INFO [scheduling-1] com.github.tomboyo.exercise.Requester: Call OK in 222 ms 21:19:51.214 INFO [scheduling-1] com.github.tomboyo.exercise.Requester: Call OK in 217 ms 21:19:52.213 INFO [scheduling-1] com.github.tomboyo.exercise.Requester: Call OK in 217 ms
Note
|
Sometimes due to slow application startup, the first response is slow and trips the breaker. Wait a few moments and the logging should resemble the above. |
If we make the following POST request, the REST API will not respond to requests until after 700 ms, which is slower than the Client considers to be healthy:
curl localhost:8080/ -H 'Content-Type: application/json' -d '{"delay_millis": 700}'
As a result, the Client’s circuit breaker will trip into the open
state and deny subsequent requests to the protected HTTP GET method. At this point, REST API is no longer receiving traffic.
... 21:20:24.719 INFO [scheduling-1] com.github.tomboyo.exercise.Requester: Breaker mycircuitbreaker transition: CLOSED -> OPEN 21:20:24.720 INFO [scheduling-1] com.github.tomboyo.exercise.Requester: Call OK in 724 ms 21:20:25.000 ERROR [scheduling-1] com.github.tomboyo.exercise.Requester: Call failed: CircuitBreaker 'mycircuitbreaker' is OPEN and does not permit further calls 21:20:25.997 ERROR [scheduling-1] com.github.tomboyo.exercise.Requester: Call failed: CircuitBreaker 'mycircuitbreaker' is OPEN and does not permit further calls
Note
|
The Requester logs round-trip time (Call OK in … ) after the request to the breaker is evaluated, so transition events (transition: CLOSED → OPEN ) are logged before the elapsed time.
|
After an interval, however, the breaker will transition to the HALF-OPEN
state. It will allow one request through to the protected HTTP method in order to assess the health of the underlying service. If the REST API is left as we have configured it, the probe request will take a little over 700 ms and again trip the breaker into the OPEN
state.
... 21:20:28.997 ERROR [scheduling-1] com.github.tomboyo.exercise.Requester: Call failed: CircuitBreaker 'mycircuitbreaker' is OPEN and does not permit further calls 21:20:29.998 INFO [scheduling-1] com.github.tomboyo.exercise.Requester: Breaker mycircuitbreaker transition: OPEN -> HALF_OPEN 21:20:30.709 INFO [scheduling-1] com.github.tomboyo.exercise.Requester: Breaker mycircuitbreaker transition: HALF_OPEN -> OPEN 21:20:30.710 INFO [scheduling-1] com.github.tomboyo.exercise.Requester: Call OK in 714 ms 21:20:30.997 ERROR [scheduling-1] com.github.tomboyo.exercise.Requester: Call failed: CircuitBreaker 'mycircuitbreaker' is OPEN and does not permit further calls 21:20:31.997 ERROR [scheduling-1] com.github.tomboyo.exercise.Requester: Call failed: CircuitBreaker 'mycircuitbreaker' is OPEN and does not permit further calls
However, if we reconfigure the REST API to respond after a short 100 ms delay,
curl localhost:8080/ -H 'Content-Type: application/json' -d '{"delay_millis": 100}'
then when the breaker next enters the HALF-OPEN
state, the probe request will resolve quickly and the breaker will transition into the CLOSED
state. In this state, all requests to the breaker’s protected HTTP method are made. The REST API now receives 100% of attempted traffic.
... 21:23:28.996 ERROR [scheduling-1] com.github.tomboyo.exercise.Requester: Call failed: CircuitBreaker 'mycircuitbreaker' is OPEN and does not permit further calls 21:23:29.996 INFO [scheduling-1] com.github.tomboyo.exercise.Requester: Breaker mycircuitbreaker transition: OPEN -> HALF_OPEN 21:23:30.108 INFO [scheduling-1] com.github.tomboyo.exercise.Requester: Breaker mycircuitbreaker transition: HALF_OPEN -> CLOSED 21:23:30.109 INFO [scheduling-1] com.github.tomboyo.exercise.Requester: Call OK in 113 ms 21:23:31.106 INFO [scheduling-1] com.github.tomboyo.exercise.Requester: Call OK in 110 ms 21:23:32.106 INFO [scheduling-1] com.github.tomboyo.exercise.Requester: Call OK in 110 ms