beeline / yii2-circuit-breaker
Circuit Breaker pattern implementation for Yii2 to prevent cascading failures
Installs: 33
Dependents: 2
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
Type:yii2-extension
pkg:composer/beeline/yii2-circuit-breaker
Requires
- php: ^8.4
- yiisoft/yii2: ^2.0.45
- yiisoft/yii2-composer: ^2.0.11
Requires (Dev)
- phpunit/phpunit: ^12.0
README
Надёжная реализация паттерна Circuit Breaker для Yii2 приложений, предотвращающая каскадные сбои в распределённых системах.
Возможности
- Три состояния: CLOSED (нормальная работа), OPEN (блокировка запросов), HALF_OPEN (тестирование восстановления)
- Настраиваемые пороги: Гибкая настройка частоты отказов, размера окна наблюдения и таймаутов
- Скользящее окно: Отслеживание истории последних запросов для точного расчёта частоты отказов
- Автоматическое восстановление: Тестирование работоспособности бэкенда после истечения таймаута
- Нулевые зависимости: Требуется только фреймворк Yii2
- PHP 8.4+: Современный PHP со строгой типизацией и новейшими возможностями
Установка
composer require beeline/yii2-circuit-breaker
Требования
- PHP >= 8.4
- Yii2 >= 2.0.45
Использование
Базовое использование
use beeline\CircuitBreaker\CircuitBreaker; $breaker = new CircuitBreaker([ 'failureThreshold' => 0.5, // Открывать цепь при 50% отказов 'windowSize' => 10, // Отслеживать последние 10 запросов 'timeout' => 30, // Ждать 30 секунд перед тестированием восстановления 'successThreshold' => 2, // Требовать 2 успешных запроса для закрытия цепи ]); // Проверяем, разрешён ли запрос if ($breaker->allowsRequest()) { try { // Вызываем внешний сервис $result = $externalService->call(); $breaker->recordSuccess(); } catch (\Exception $e) { $breaker->recordFailure(); throw $e; } } else { // Цепь открыта, быстрый отказ throw new \RuntimeException('Сервис недоступен'); }
Конфигурация
| Свойство | Тип | По умолчанию | Описание |
|---|---|---|---|
failureThreshold |
float | 0.5 | Частота отказов (0.0-1.0) для открытия цепи |
windowSize |
int | 10 | Количество отслеживаемых запросов |
timeout |
int | 30 | Секунды ожидания перед попыткой восстановления |
successThreshold |
int | 1 | Необходимое количество успешных запросов в состоянии HALF_OPEN для закрытия цепи |
Состояния цепи
CLOSED (Нормальная работа)
- Все запросы разрешены
- Отказы отслеживаются в скользящем окне
- Цепь открывается при превышении порога отказов
OPEN (Блокировка запросов)
- Запросы немедленно отклоняются
- Нет обращений к отказавшему бэкенду
- Переход в состояние HALF_OPEN после истечения таймаута
HALF_OPEN (Тестирование восстановления)
- Ограниченное количество запросов для тестирования бэкенда
- Закрывается при успешных запросах
- Повторно открывается при любом отказе
Расширенное использование
Мониторинг состояния цепи
// Проверка текущего состояния if ($breaker->isClosed()) { // Нормальная работа } if ($breaker->isOpen()) { // Цепь открыта, используем запасной вариант } if ($breaker->isHalfOpen()) { // Тестирование восстановления } // Получение детальной статистики $stats = $breaker->getStats(); // Возвращает: ['total' => 10, 'failures' => 3, 'failureRate' => 0.3]
Ручное управление (для тестирования)
// Принудительное открытие цепи $breaker->forceOpen(); // Принудительное закрытие цепи $breaker->forceClose(); // Сброс в начальное состояние $breaker->reset();
Интеграция с внешними сервисами
use beeline\CircuitBreaker\CircuitBreaker; use yii\httpclient\Client; class ExternalApiClient { private CircuitBreaker $breaker; private Client $http; public function __construct() { $this->breaker = new CircuitBreaker([ 'failureThreshold' => 0.6, 'windowSize' => 20, 'timeout' => 60, ]); $this->http = new Client(['baseUrl' => 'https://api.example.com']); } public function fetchData(string $endpoint): array { if (!$this->breaker->allowsRequest()) { throw new \RuntimeException('Circuit API открыт'); } try { $response = $this->http->get($endpoint)->send(); if ($response->isOk) { $this->breaker->recordSuccess(); return $response->data; } $this->breaker->recordFailure(); throw new \RuntimeException('Ошибка запроса к API'); } catch (\Exception $e) { $this->breaker->recordFailure(); throw $e; } } }
Принцип работы
- Скользящее окно: Circuit breaker поддерживает окно фиксированного размера с результатами последних запросов
- Отслеживание отказов: Результат каждого запроса (успех/отказ) записывается в окно
- Проверка порога: Когда окно заполнено, частота отказов сравнивается с порогом
- Переходы между состояниями:
- CLOSED → OPEN: Частота отказов превышает порог
- OPEN → HALF_OPEN: Истекает таймаут
- HALF_OPEN → CLOSED: Достаточное количество успешных запросов
- HALF_OPEN → OPEN: Происходит любой отказ
Примеры использования
Предотвращение перегрузки базы данных
use beeline\CircuitBreaker\CircuitBreaker; class DatabaseService { private CircuitBreaker $breaker; public function __construct() { $this->breaker = new CircuitBreaker([ 'failureThreshold' => 0.5, 'windowSize' => 20, 'timeout' => 10, ]); } public function executeQuery(string $sql): array { if (!$this->breaker->allowsRequest()) { // Возвращаем кешированные данные или бросаем исключение throw new \RuntimeException('База данных временно недоступна'); } try { $result = Yii::$app->db->createCommand($sql)->queryAll(); $this->breaker->recordSuccess(); return $result; } catch (\Exception $e) { $this->breaker->recordFailure(); throw $e; } } }
Защита от сбоев внешних API
use beeline\CircuitBreaker\CircuitBreaker; class PaymentGateway { private CircuitBreaker $breaker; public function __construct() { $this->breaker = new CircuitBreaker([ 'failureThreshold' => 0.3, // Более чувствительный порог 'windowSize' => 50, 'timeout' => 60, 'successThreshold' => 5, // Требуем больше успехов для восстановления ]); } public function processPayment(array $paymentData): bool { if (!$this->breaker->allowsRequest()) { // Логируем событие и возвращаем ошибку Yii::error('Payment gateway circuit is open', __METHOD__); return false; } try { $response = $this->callPaymentApi($paymentData); $this->breaker->recordSuccess(); return $response['success']; } catch (\Exception $e) { $this->breaker->recordFailure(); Yii::error("Payment failed: {$e->getMessage()}", __METHOD__); return false; } } }
Мониторинг и алертинг
use beeline\CircuitBreaker\CircuitBreaker; class MonitoredService { private CircuitBreaker $breaker; public function __construct() { $this->breaker = new CircuitBreaker([ 'failureThreshold' => 0.5, 'windowSize' => 100, 'timeout' => 30, ]); } public function call(): mixed { $state = $this->breaker->getState(); // Отправляем метрики в систему мониторинга if ($state === 'open') { $this->sendAlert('Circuit breaker is OPEN'); } if (!$this->breaker->allowsRequest()) { return $this->getFallbackData(); } // ... выполнение запроса } public function getHealthStatus(): array { $stats = $this->breaker->getStats(); return [ 'state' => $this->breaker->getState(), 'total_requests' => $stats['total'], 'failures' => $stats['failures'], 'failure_rate' => $stats['failureRate'], 'is_healthy' => $this->breaker->isClosed(), ]; } }
Тестирование
# Запуск тестов vendor/bin/phpunit # Запуск тестов с покрытием vendor/bin/phpunit --coverage-html coverage/
Лицензия
GNU Lesser General Public License 3.0