Robust Service Interaction with a Circuit Breaker in Go
Circuit breakers are a crucial pattern for building resilient distributed systems. They prevent cascading failures by temporarily stopping requests to a failing service, allowing it time to recover. This challenge asks you to implement a basic circuit breaker in Go to protect a simulated external service.
Problem Description
You are tasked with implementing a circuit breaker pattern in Go. The circuit breaker should monitor calls to a simulated external service (ExternalService) and, based on the success/failure rate, transition between three states: Closed, Open, and Half-Open.
- Closed: Requests are allowed to pass through to the external service. The circuit breaker tracks the success and failure rates.
- Open: Requests are immediately rejected without calling the external service. This state is entered when the failure rate exceeds a defined threshold.
- Half-Open: After a timeout period in the
Openstate, the circuit breaker transitions toHalf-Open. In this state, a limited number of requests are allowed to pass through to the external service. If these requests succeed, the circuit breaker transitions back to theClosedstate. If they fail, it returns to theOpenstate.
The ExternalService is simulated by a function that randomly succeeds or fails. Your circuit breaker should wrap this service and provide a Call method that handles the circuit breaker logic.
Key Requirements:
- Implement the three circuit breaker states:
Closed,Open, andHalf-Open. - Track the success and failure rates of calls to the external service.
- Define thresholds for transitioning between states (failure rate threshold, timeout duration).
- Implement a timeout mechanism for the
Openstate. - Provide a
Callmethod that encapsulates the circuit breaker logic. - Handle concurrency safely.
Expected Behavior:
- When the circuit is
Closedand a call fails, the failure count should increment. If the failure rate exceeds the threshold, the circuit should transition toOpen. - When the circuit is
Open, all calls should be immediately rejected with an error. - After the timeout period in the
Openstate, the circuit should transition toHalf-Open. - When the circuit is
Half-Open, a limited number of calls should be allowed to pass through. If these calls succeed, the circuit should transition toClosed. If they fail, it should transition back toOpen. - The circuit breaker should be thread-safe.
Edge Cases to Consider:
- What happens if the external service never succeeds?
- What happens if the external service always succeeds?
- How does the circuit breaker handle concurrent calls?
- How does the circuit breaker handle errors from the external service?
Examples
Example 1:
Input: Repeated calls to ExternalService that initially succeed, then start failing frequently.
Output: Circuit transitions from Closed -> Open after exceeding failure rate threshold. Subsequent calls return error immediately. After timeout, transitions to Half-Open, then back to Closed if a few calls succeed.
Explanation: Demonstrates the core functionality of detecting failures and transitioning states.
Example 2:
Input: ExternalService always fails.
Output: Circuit remains in Open state indefinitely.
Explanation: Shows how the circuit breaker handles a consistently failing service.
Example 3:
Input: ExternalService always succeeds.
Output: Circuit remains in Closed state indefinitely.
Explanation: Demonstrates the circuit breaker's behavior when the service is consistently healthy.
Constraints
- Failure Rate Threshold: The failure rate threshold should be configurable (e.g., 50% - 5 out of 10 calls).
- Timeout Duration: The timeout duration for the
Openstate should be configurable (e.g., 5 seconds). - Half-Open Test Calls: The number of test calls allowed in the
Half-Openstate should be configurable (e.g., 3 calls). - Concurrency: The circuit breaker must be thread-safe and handle concurrent calls correctly.
- ExternalService: The
ExternalServicefunction is provided and cannot be modified. It simulates a service that randomly succeeds or fails.
Notes
- Consider using channels and goroutines to implement the timeout mechanism.
- Use mutexes to protect shared state (failure count, state) from race conditions.
- The
ExternalServicefunction is defined as follows:
import "math/rand"
func ExternalService() error {
if rand.Intn(10) < 5 { // 50% chance of failure
return nil // Success
}
return fmt.Errorf("service failed") // Failure
}
- Focus on the core circuit breaker logic. Error handling within the
ExternalServiceis already handled. - Think about how to make the circuit breaker configurable and reusable.
- Consider adding logging to track state transitions and errors.