Lab 11: Distributed Patterns

Time: 60 minutes | Level: Architect | Docker: docker run -it --rm php:8.3-cli bash

Overview

Distributed systems require patterns for handling failures gracefully. This lab implements Circuit Breaker, Retry with exponential backoff, Token Bucket rate limiting, Bulkhead, and distributed lock concepts—all in pure PHP with no external dependencies.


Step 1: Circuit Breaker

The circuit breaker prevents cascading failures by failing fast when a downstream service is unhealthy.

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";

📸 Verified Output:


Step 2: Retry with Exponential Backoff + Jitter

📸 Verified Output:


Step 3: Token Bucket Rate Limiter

📸 Verified Output:


Step 4: Bulkhead Pattern


Step 5: Health Check Interface


Step 6: Distributed Lock (Redlock Concept)


Step 7: Combining Patterns


Step 8: Capstone — Resilience Demo

📸 Verified Output:


Summary

Pattern
Purpose
Implementation

Circuit Breaker

Fail fast when service is down

CLOSED→OPEN→HALF-OPEN state machine

Retry

Recover from transient failures

Exponential backoff + jitter

Rate Limiter

Prevent overload

Token bucket refill algorithm

Bulkhead

Isolate concurrent failures

Max concurrent slots

Health Check

Monitor service status

Latency-based health levels

Distributed Lock

Coordinate across instances

Token-based SQLite/Redis lock

Timeout

Bound operation duration

Cancellation token or SIGALRM

Last updated