Cloud Bits: Circuit Breakers – The First Line of Defense in Cloud-Native Resilience

gray power switch box

What are we solving?

  • Open: Request goes through normally
  • Closed: Triggered when failure threshold is reached. You end up getting a 503 usually
  • Half-Open: A sampling phase to check if the faulty service has recovered. If we get successful response then the state is changed to closed else it goes back to open
@Configuration
public class CircuitBreakerConfiguration {

    @Bean
    public Customizer<Resilience4JCircuitBreakerFactory> defaultCustomizer() {
        return factory -> factory.configureDefault(
            id -> new Resilience4JConfigBuilder(id)
                .timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(4)).build())
                .circuitBreakerConfig(CircuitBreakerConfig.custom()
                    .failureRateThreshold(50)
                    .waitDurationInOpenState(Duration.ofSeconds(2))
                    .slidingWindowSize(10)
                    .build())
                .build());
    }

    @Bean
    public RetryRegistry retryRegistry() {
        RetryConfig retryConfig = RetryConfig.custom()
            .maxAttempts(3)
            .waitDuration(Duration.ofMillis(100))
            .retryOnException(throwable -> true)
            .ignoreExceptions(InsuranceVerificationException.class)
            .build();
        return RetryRegistry.of(retryConfig);
    }
}
public RegisterResponse register(
    String firstName, String lastName, int age, String controlNumber, String policyNumber) {
    VerificationRequest verificationRequest = new VerificationRequest(controlNumber, policyNumber, LocalDate.now());
    CircuitBreaker circuitBreaker = circuitBreakerFactory.create("insuranceService");
    Retry retry = retryRegistry.retry("insuranceServiceRetry");

    return circuitBreaker.run(
        () -> retry.executeSupplier(() ->
            callVerificationService(verificationRequest, firstName, lastName, age, controlNumber, policyNumber)
        ),
        throwable -> getFallbackResponse(firstName, lastName, age, controlNumber, policyNumber, throwable)
    );
}
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: health-record-gateway
  namespace: circuit-breaker
spec:
  selector:
    istio: ingressgateway
  servers:
    - port:
        number: 80
        name: http
        protocol: HTTP
      hosts:
        - "*"
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: health-record-vs
  namespace: circuit-breaker
spec:
  hosts:
    - "*"
  gateways:
    - health-record-gateway
  http:
    - route:
        - destination:
            host: health-record-service
            port:
              number: 80
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: insurance-service-destination
  namespace: circuit-breaker
spec:
  host: insurance-service
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 10
      http:
        http1MaxPendingRequests: 10
        maxRequestsPerConnection: 2
    outlierDetection:
      consecutiveErrors: 1
      interval: 10s
      baseEjectionTime: 10s
      splitExternalLocalOriginErrors: true
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: insurance-service-vs
  namespace: circuit-breaker
spec:
  hosts:
    - insurance-service
  http:
    - route:
        - destination:
            host: insurance-service
      retries:
        attempts: 2
        perTryTimeout: 2s
        retryOn: 5xx
        retryRemoteLocalities: true
      timeout: 10s