Lab 11: Distributed Patterns
Overview
Step 1: Circuit Breaker
States:
CLOSED → normal operation, requests pass through
OPEN → fast fail, no requests sent to service
HALF-OPEN → one trial request, decides to CLOSE or re-OPEN<?php
enum CircuitState: string {
case Closed = 'closed';
case Open = 'open';
case HalfOpen = 'half-open';
}
class CircuitBreaker {
private CircuitState $state = CircuitState::Closed;
private int $failureCount = 0;
private int $successCount = 0;
private float $lastFailTime = 0.0;
private array $log = [];
public function __construct(
private readonly string $name,
private readonly int $failureThreshold = 5,
private readonly float $openDuration = 30.0, // seconds
private readonly int $halfOpenMaxTries = 3
) {}
public function call(callable $fn): mixed {
return match ($this->state) {
CircuitState::Open => $this->handleOpen($fn),
CircuitState::HalfOpen => $this->handleHalfOpen($fn),
CircuitState::Closed => $this->handleClosed($fn),
};
}
private function handleOpen(callable $fn): mixed {
if (microtime(true) - $this->lastFailTime >= $this->openDuration) {
$this->transitionTo(CircuitState::HalfOpen);
return $this->handleHalfOpen($fn);
}
$this->record('fast-fail');
throw new CircuitOpenException("{$this->name} circuit is OPEN");
}
private function handleHalfOpen(callable $fn): mixed {
try {
$result = $fn();
$this->successCount++;
if ($this->successCount >= $this->halfOpenMaxTries) {
$this->transitionTo(CircuitState::Closed);
}
$this->record('half-open-success');
return $result;
} catch (Throwable $e) {
$this->transitionTo(CircuitState::Open);
$this->record('half-open-failure');
throw $e;
}
}
private function handleClosed(callable $fn): mixed {
try {
$result = $fn();
$this->failureCount = 0;
$this->record('success');
return $result;
} catch (Throwable $e) {
$this->failureCount++;
$this->lastFailTime = microtime(true);
$this->record("failure({$this->failureCount})");
if ($this->failureCount >= $this->failureThreshold) {
$this->transitionTo(CircuitState::Open);
}
throw $e;
}
}
private function transitionTo(CircuitState $newState): void {
$this->log[] = " → TRANSITION: {$this->state->value} → {$newState->value}";
$this->state = $newState;
if ($newState === CircuitState::Closed) {
$this->failureCount = 0;
$this->successCount = 0;
} elseif ($newState === CircuitState::HalfOpen) {
$this->successCount = 0;
}
}
private function record(string $action): void {
$this->log[] = " [{$this->state->value}] {$action}";
}
public function getState(): CircuitState { return $this->state; }
public function getLog(): array { return $this->log; }
}
class CircuitOpenException extends RuntimeException {}
// Demo
$cb = new CircuitBreaker('payment-service', failureThreshold: 3, openDuration: 0.05);
$fail = fn() => throw new RuntimeException('Connection refused');
$ok = fn() => 'success';
echo "=== Circuit Breaker Demo ===\n";
// Trigger failures to open circuit
for ($i = 1; $i <= 4; $i++) {
try {
$cb->call($fail);
} catch (CircuitOpenException $e) {
echo "Fast fail: " . $e->getMessage() . "\n";
} catch (RuntimeException $e) {
echo "Failure {$i}: " . $e->getMessage() . "\n";
}
}
echo "State: " . $cb->getState()->value . "\n";
foreach ($cb->getLog() as $entry) echo $entry . "\n";Step 2: Retry with Exponential Backoff + Jitter
Step 3: Token Bucket Rate Limiter
Step 4: Bulkhead Pattern
Step 5: Health Check Interface
Step 6: Distributed Lock (Redlock Concept)
Step 7: Combining Patterns
Step 8: Capstone — Resilience Demo
Summary
Pattern
Purpose
Implementation
Last updated
