PHP Circuit Breaker Pattern – Build Resilient, Fault-Tolerant Systems
Implement the Circuit Breaker pattern in PHP to prevent cascading failures, enable graceful degradation, and build resilient distributed systems.
Circuit Breaker Pattern in PHP: How to Build Resilient and Fault-Tolerant Systems
In distributed systems, failure is inevitable. The Circuit Breaker pattern is a proven resilience strategy that helps prevent cascading failures by stopping repeated calls to failing services. This article explains how to implement the Circuit Breaker pattern in PHP to create fault tolerant, adaptive applications.
What are Cascading Failures?
Cascading failures occur when one service failure triggers failures in dependent services, creating a domino effect that can bring down entire systems.
Summary
This comprehensive guide covers everything you need to implement the Circuit Breaker pattern in PHP:
- Core Concepts: Understanding the three states and their transitions
- PHP Implementation: Complete Circuit Breaker class with configurable parameters
- Configuration: How to tune thresholds and timeouts for different service types
- Distributed Systems: Managing shared state and locks across multiple instances
- Real-World Examples: API gateway and database service implementations with fallback strategies
What is the Circuit Breaker Pattern?
Like an electrical circuit breaker that protects a building from overload, the software Circuit Breaker protects your system by monitoring for failures and stopping calls to unhealthy services. Instead of letting failures cascade and cause downtime, it fails fast and recovers gracefully.
The Three States of a Circuit Breaker
The pattern operates through three states that control how requests are handled:
- Closed: All requests are passed through while monitoring failures. This is the normal operating state.
- Open: When failures exceed a threshold, the circuit opens, immediately failing requests to prevent further damage.
- Half-Open: After a timeout, limited requests are allowed to test if the service has recovered before fully closing the circuit.
State Transitions Explained
Implementing Circuit Breaker in PHP
<?php
class CircuitBreaker
{
private CircuitBreakerState $state = CircuitBreakerState::CLOSED;
private int $failureCount = 0;
private int $halfOpenCallCount = 0;
private int $halfOpenSuccessCount = 0;
private ?\DateTimeImmutable $lastFailureTime = null;
private ?\DateTimeImmutable $nextAttemptTime = null;
public function __construct(
private readonly int $failureThreshold = 5,
private readonly int $recoveryTimeoutSeconds = 60,
private readonly int $halfOpenMaxCalls = 3,
) {
}
public function call(callable $callback): mixed
{
if (true === $this->state->isOpen()) {
if (true === $this->shouldAttemptReset()) {
$this->transitionToHalfOpen();
} else {
throw new CircuitBreakerException($this->state);
}
}
if (true === $this->state->isHalfOpen()
&& $this->halfOpenCallCount >= $this->halfOpenMaxCalls) {
throw new CircuitBreakerException($this->state);
}
if (true === $this->state->isHalfOpen()) {
++$this->halfOpenCallCount;
}
try {
$result = $callback();
$this->recordSuccess();
return $result;
} catch (\Throwable $e) {
$this->recordFailure();
throw $e;
}
}
// ... Additional methods for state transitions and checks
}
Choosing Timing Parameters
- Failure Threshold: Controls sensitivity; too low causes premature opening, too high delays protection.
- Recovery Timeout: Duration to wait before testing service recovery.
- Half-Open Test Count: Number of test requests to verify recovery.
Tune these parameters based on the criticality and behavior of the protected service.
<?php
// config/circuit_breakers.php
return [
'critical_services' => [
'failure_threshold' => 3, // Fail fast for critical services
'recovery_timeout' => 30, // Quick recovery attempts
'half_open_calls' => 2, // Conservative testing
],
'external_apis' => [
'failure_threshold' => 5, // More tolerance for external deps
'recovery_timeout' => 120, // Longer recovery time
'half_open_calls' => 3, // Standard testing
],
'internal_services' => [
'failure_threshold' => 10, // High tolerance for internal services
'recovery_timeout' => 60, // Standard recovery time
'half_open_calls' => 5, // Thorough testing
],
];
Without Circuit Breaker — Risk of Cascading Failures
class UserService {
public function getUserProfile(int $userId): array {
$userData = $this->externalApi->getUser($userId);
$preferences = $this->externalApi->getPreferences($userId);
$settings = $this->externalApi->getSettings($userId);
return compact('userData', 'preferences', 'settings');
}
}
With Circuit Breaker — Controlled Failure and Graceful Degradation
What is Graceful Degradation?
Graceful degradation means providing a reduced but functional service when primary systems fail. Instead of complete failure, the application falls back to cached data, default responses, or simplified functionality to maintain user experience.
class UserService {
public function getUserProfile(int $userId): array {
try {
$userData = $this->circuitBreaker->call(
fn() => $this->externalApi->getUser($userId)
);
return $this->buildProfile($userData, $userId);
} catch (CircuitBreakerException $e) {
// Fallback to cached profile
return $this->getCachedProfile($userId);
}
}
}
Handling Distributed Systems: Using Locks and Shared Storage
What are Distributed Locks?
Distributed locks ensure that only one process can modify shared data at a time across multiple servers or instances.
In distributed environments, where multiple processes or instances run concurrently, managing a consistent Circuit Breaker state becomes challenging. Each process may independently detect failures and update the state, which can cause race conditions and inconsistent behavior.
Why Use Locks?
To ensure that state updates are atomic and consistent across all instances, a locking mechanism is essential. A distributed lock prevents multiple processes from modifying the Circuit Breaker state simultaneously, avoiding conflicts and corrupted state.
Storage Options for Shared State and Locks
-
Redis:
Redis is a popular choice due to its support for atomic operations, fast performance, and native support for distributed locks (e.g., using Redlock algorithm).
You can store the Circuit Breaker state (failure counts, timestamps, current state) as Redis keys with expiration times, and use Redis locks to coordinate updates. -
Relational Databases:
Using a database allows leveraging transactions and row-level locks to maintain state consistency. However, databases often have higher latency compared to in-memory stores like Redis. -
Distributed Caches (e.g., Memcached):
Generally not recommended for Circuit Breaker state due to lack of atomic operations and lock primitives.
Example: Redis-based Lock and State Storage
// Pseudo-code for acquiring Redis lock and updating circuit breaker state
$lockKey = "cb_lock_service_xyz";
$stateKey = "cb_state_service_xyz";
// Attempt to acquire lock with expiration (e.g., 5 seconds)
if ($redis->set($lockKey, "locked", ['NX', 'EX' => 5])) {
$state = json_decode($redis->get($stateKey), true) ?? defaultState();
// Update state based on logic
$state = updateCircuitBreakerState($state, $event);
// Save updated state
$redis->set($stateKey, json_encode($state));
// Release lock
$redis->del($lockKey);
} else {
// Could not acquire lock - decide to retry, skip, or use cached state
}
Real-World Usage Examples
API Gateway with Circuit Breaker
class ApiGatewayService {
public function proxyRequest(string $serviceName, string $endpoint): array {
$circuitBreaker = $this->circuitBreakerFactory->circuitFor($serviceName);
try {
return $circuitBreaker->call(function() use ($endpoint) {
$response = $this->httpClient->request('GET', $endpoint, ['timeout' => 5]);
if ($response->getStatusCode() !== 200) {
throw new \RuntimeException('API error');
}
return $response->toArray();
});
} catch (CircuitBreakerException $e) {
return $this->getCachedResponse($endpoint) ?? $this->getDefaultResponse();
}
}
}
Database Service with Circuit Breaker
class DatabaseService {
public function executeQuery(string $query, array $params = []): array {
$circuitBreaker = $this->circuitBreakerFactory->circuitFor('primary-db');
try {
return $circuitBreaker->call(fn() => $this->primaryDb->fetchAll($query, $params));
} catch (CircuitBreakerException $e) {
// Fallback to read-only DB
return $this->readOnlyDb->fetchAll($query, $params);
}
}
}
Go Further: Complementary Resilience Patterns
Circuit Breaker works best when combined with other resilience patterns. Here are key patterns that complement Circuit Breaker for comprehensive fault tolerance:
Bulkhead Pattern
Isolates different parts of your system to prevent failures from spreading. Like compartments in a ship, if one section fails, others remain operational.
// Resource isolation example
class ServicePool {
private array $pools = [
'critical' => ['max_connections' => 50, 'timeout' => 5],
'normal' => ['max_connections' => 20, 'timeout' => 10],
'background' => ['max_connections' => 10, 'timeout' => 30]
];
}
Retry Pattern
Automatically retries failed operations with configurable delays and limits. Essential for handling transient failures.
class RetryHandler {
public function executeWithRetry(callable $operation, int $maxAttempts = 3): mixed {
for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
try {
return $operation();
} catch (\Exception $e) {
if ($attempt === $maxAttempts) throw $e;
usleep(100000 * $attempt); // Exponential backoff
}
}
}
}
Timeout Pattern
Sets maximum wait times for operations to prevent indefinite blocking.
$context = stream_context_create([
'http' => ['timeout' => 5] // 5-second timeout
]);
Pattern Synergy
- Retry + Circuit Breaker: Retry handles transient failures; Circuit Breaker stops retries when service is consistently down
- Bulkhead + Circuit Breaker: Bulkhead isolates failures; Circuit Breaker prevents cascading within each compartment
- Timeout + All Patterns: Ensures operations don’t hang indefinitely, enabling other patterns to work effectively
We will explore these complementary patterns in future articles, so stay tuned!
Conclusion: Build Resilient PHP Systems with Circuit Breaker
Implementing the Circuit Breaker pattern is key to making your PHP applications fault tolerant and robust. It protects your system from cascading failures and improves user experience with graceful degradation.
Ready to add Circuit Breakers to your PHP projects? Check out our Circuit Breaker PHP library for an easy-to-use implementation.