Odporność: circuit breakery i ponowienia
Utrzymuj sprawność systemów podczas częściowych awarii
Odporność: circuit breakery i ponowienia to bezpłatna lekcja PHP Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej PHP Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs PHP Academy zawiera 4 lekcji w sumie.
Wzorce odporności
W systemie rozproszonym częściowe awarie są normą — jakaś zależność zawsze działa wolno, uruchamia się ponownie albo jest przeciążona. Odporność polega na projektowaniu każdej usługi tak, aby jedna niesprawna zależność nie pociągnęła za sobą usługi, a następnie całego systemu.
W tej lekcji omówimy podstawowy zestaw narzędzi: timeouty, ponowienia z backoffem, circuit breakery, bulkheady i kontrolowane ograniczanie funkcjonalności — wszystko w PHP.
Najpierw timeouty
Najważniejszym pojedynczym ustawieniem zwiększającym odporność jest timeout. Bez niego wolna usługa downstream blokuje workery PHP-FPM, które czekają; żądania się spiętrzają; kończą się workery; Państwa usługa przestaje działać tylko dlatego, że ktoś inny działał wolno. To podręcznikowy przykład awarii kaskadowej.
W każdym wywołaniu wychodzącym należy zawsze ustawić zarówno timeout nawiązania połączenia, jak i łączny timeout żądania.
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
$http = new Client([
'connect_timeout' => 0.5, // fail fast if we can't even connect
'timeout' => 2.0, // hard cap on the whole call
]);
// A hung dependency now fails in 2s instead of holding a worker forever.Ponowienia — ostrożnie
Przejściowe awarie (chwilowy problem, krótkotrwała utrata sieci, 503) często znikają przy drugiej próbie. Ponowienia są jednak niebezpieczne: jeśli będą wykonywane zbyt pochopnie, zwiększą obciążenie już przeciążonej usługi. Zasady:
- Należy ponawiać tylko operacje idempotentne oraz błędy ponawialne (timeouty, 5xx, 429).
- Należy ograniczyć liczbę prób.
- Nigdy nie należy ponawiać błędów klienta 4xx — ponownie zakończą się niepowodzeniem.
Wykładniczy backoff i jitter
Ponowienia wykonywane przez wielu klientów w stałych odstępach synchronizują się, tworząc lawinę żądań, która falami uderza w odzyskującą sprawność usługę. Rozwiązaniem jest wykładniczy backoff (podwajanie opóźnienia przy każdej próbie) połączony z jitterem (losowością), aby rozłożyć obciążenie w czasie.
<?php
function backoffDelay(int $attempt, float $base = 0.1, float $cap = 5.0): float {
$exp = min($cap, $base * (2 ** $attempt)); // 0.1, 0.2, 0.4, ...
return mt_rand(0, (int)($exp * 1000)) / 1000; // full jitter: 0..exp
}
for ($i = 0; $i < 5; $i++) {
printf("attempt %d -> wait %.3fs\n", $i, backoffDelay($i));
}Pętla ponowień
Połączenie wszystkich elementów: ograniczona liczba prób, ponawianie wyłącznie w przypadku błędów ponawialnych, oczekiwanie między próbami z użyciem backoffu i jittera oraz zwrócenie ostatniego błędu, jeśli wszystkie próby zakończą się niepowodzeniem.
<?php
function withRetry(callable $op, int $maxAttempts = 4): mixed {
$attempt = 0;
while (true) {
try {
return $op();
} catch (\Throwable $e) {
$attempt++;
if ($attempt >= $maxAttempts || !isRetryable($e)) {
throw $e; // give up
}
usleep((int)(backoffDelay($attempt) * 1_000_000));
}
}
}
function isRetryable(\Throwable $e): bool { return $e->getCode() === 0 || $e->getCode() >= 500; }
function backoffDelay(int $a): float { return min(5.0, 0.1 * (2 ** $a)) * (mt_rand(0, 100) / 100); }Circuit breaker
Ponowienia pomagają przy chwilowych problemach, ale jeśli zależność naprawdę nie działa, ponawianie każdego żądania tylko marnuje czas i zasoby. Mechanizm circuit breaker śledzi błędy i po przekroczeniu określonego progu otwiera się — przerywa wywołania i natychmiast zwraca błąd zamiast czekać na niedziałającą usługę.
Ma trzy stany: zamknięty (wywołania są przepuszczane, a błędy zliczane), otwarty (wywołania są natychmiast odrzucane) i półotwarty (kilka próbnych wywołań sprawdza, czy usługa odzyskała sprawność).
Maszyna stanów circuit breakera
Przejścia wyglądają następująco: stan zamknięty → otwarty, gdy liczba błędów przekroczy próg. Otwarty → półotwarty po okresie schładzania. Półotwarty → zamknięty po pomyślnym wywołaniu testowym albo z powrotem otwarty w razie błędu. Stan musi być współdzielony między procesami PHP (Redis/APCu), ponieważ każde żądanie jest obsługiwane przez nowy proces.
<?php
final class CircuitBreaker {
public function __construct(
private int $threshold = 5,
private int $coolDown = 30, // seconds
) {}
public function call(callable $op, array &$state): mixed {
if ($state['status'] === 'open') {
if (time() - $state['openedAt'] < $this->coolDown) {
throw new \RuntimeException('Circuit open - failing fast');
}
$state['status'] = 'half-open'; // time to probe
}
try {
$result = $op();
$state = ['status' => 'closed', 'failures' => 0]; // recovered
return $result;
} catch (\Throwable $e) {
$state['failures'] = ($state['failures'] ?? 0) + 1;
if ($state['failures'] >= $this->threshold || $state['status'] === 'half-open') {
$state['status'] = 'open';
$state['openedAt'] = time();
}
throw $e;
}
}
}Bulkheady
Nazwa wzorca bulkhead pochodzi od przegród w statku. Wzorzec ten izoluje zasoby, aby awaria jednego obszaru nie mogła zatopić całej jednostki. Jeśli wszystkie workery mogą wywoływać powolną usługę Reports, awaria Reports może zająć każdego workera i pozbawić zasobów usługę Checkout.
Należy podzielić zasoby — użyć osobnych pul połączeń, osobnych pul workerów lub kolejek dla każdej zależności albo limitów współbieżności dla poszczególnych usług downstream — aby jedna niesprawna zależność wyczerpała tylko przydzielony jej fragment zasobów.
Kontrolowane ograniczanie funkcjonalności i fallbacki
Gdy niekrytyczna zależność jest niedostępna, należy ograniczyć funkcjonalność zamiast zwracać błąd. Można zwrócić wartość z pamięci podręcznej, wartość domyślną albo zapewnić okrojoną wersję działania. Otwarty stan circuit breakera jest naturalnym wyzwalaczem ścieżki fallbacku.
<?php
function getRecommendations(callable $remoteCall, Redis $cache, string $userId): array {
try {
$recs = $remoteCall($userId);
$cache->setex("recs:$userId", 3600, json_encode($recs));
return $recs;
} catch (\Throwable $e) {
// Fallback 1: last-known-good from cache
if ($cached = $cache->get("recs:$userId")) {
return json_decode($cached, true);
}
// Fallback 2: generic popular items - never block the page
return ['popular-1', 'popular-2'];
}
}Klucze idempotencji do bezpiecznych ponowień
Ponowienia są bezpieczne tylko w przypadku operacji idempotentnych. W przypadku działania nieidempotentnego, takiego jak "obciążenie karty", należy dołączyć klucz idempotencji, którego serwer używa do deduplikacji: jeśli ponowienie nadejdzie po tym, jak pierwsza próba już się powiodła (ale odpowiedź zaginęła), serwer zwróci pierwotny wynik zamiast obciążyć kartę dwukrotnie.
<?php
function chargeWithRetry(callable $http, string $orderId, int $cents): array {
// Same key across all retries of THIS logical charge
$key = 'charge-' . $orderId;
return withRetry(fn() => $http('POST', '/charges', [
'headers' => ['Idempotency-Key' => $key],
'json' => ['order' => $orderId, 'amount' => $cents],
]));
}
function withRetry(callable $op) { return $op(); } // see earlier scenePołączenie wzorców
Wzorce można łączyć, a kolejność ma znaczenie. Odporne wywołanie wychodzące zazwyczaj ma następujące warstwy:
- Timeout dla każdej pojedynczej próby (warstwa najbardziej wewnętrzna).
- Ponowienie z backoffem opakowujące wywołanie z timeoutem, aby obsłużyć przejściowe problemy.
- Circuit breaker opakowujący ponowienie, aby szybko zareagować na długotrwałą awarię.
- Bulkhead ograniczający ilość zasobów, jaką ta zależność może zużyć.
- Fallback jako warstwa najbardziej zewnętrzna, przechwytująca wszystko, co wydostanie się na zewnątrz.
Istnieją biblioteki oferujące Resilience4PHP-owe wrappery, ale zrozumienie warstw ma większe znaczenie niż wybór konkretnego pakietu.
Szybkie sprawdzenie
Wybór właściwego wzorca.
Podsumowanie
Odporność na częściowe awarie:
- Timeouty w każdym wywołaniu zapobiegają wyczerpaniu workerów i awariom kaskadowym.
- Ponowienia tylko dla operacji idempotentnych i błędów ponawialnych, z użyciem wykładniczego backoffu i jittera.
- Circuit breakery szybko zwracają błąd (zamknięty → otwarty → półotwarty), gdy zależność nie działa.
- Bulkheady izolują zasoby, aby jedna awaria nie pozbawiła ich wszystkich pozostałych elementów.
- Kontrolowane ograniczanie funkcjonalności i fallbacki pozwalają zachować działanie kluczowych ścieżek.
Połączone warstwowo wzorce te zamieniają nieuniknione awarie w ograniczone i możliwe do opanowania zdarzenia.
Często zadawane pytania
Czy lekcja „Odporność: circuit breakery i ponowienia” jest bezpłatna?
Tak — pełny tekst „Odporność: circuit breakery i ponowienia” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu PHP Academy, przejdź na CoddyKit PRO. Kurs PHP Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Odporność: circuit breakery i ponowienia”?
Utrzymuj sprawność systemów podczas częściowych awarii Ćwiczysz PHP Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć PHP Academy?
Nie wymagamy żadnego doświadczenia. PHP Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.
Ile czasu zajmuje lekcja „Odporność: circuit breakery i ponowienia”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji PHP Academy?
Tak. Każda lekcja PHP Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Od monolitu do mikroserwisów
- Komunikacja usług: REST i gRPC
- Bramy API i wykrywanie usług
- Odporność: circuit breakery i ponowienia