PHP Academy · Aula

Resiliência: disjuntores e novas tentativas

Mantenha os sistemas saudáveis durante falhas parciais.

Aula 4 de 413 etapas

Resiliência: disjuntores e novas tentativas é uma aula grátis de PHP Academy no CoddyKit. Esta é a aula 4 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de PHP Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de PHP Academy inclui 4 aulas no total.

Padrões de resiliência

Em um sistema distribuído, a falha parcial é o estado normal — alguma dependência está sempre lenta, reiniciando ou sobrecarregada. Resiliência consiste em projetar cada serviço para que uma dependência com problemas não derrube o seu serviço e, depois, todo o sistema.

Esta lição aborda o conjunto de ferramentas essenciais: tempos limite, novas tentativas com atraso, disjuntores, compartimentação e degradação controlada — tudo em PHP.

Primeiro, os tempos limite

A configuração de resiliência mais importante é o tempo limite. Sem ele, um serviço de destino lento mantém seus trabalhadores do PHP-FPM esperando; as requisições se acumulam; você fica sem trabalhadores; e seu serviço cai porque outra pessoa estava lenta. Essa é a clássica falha em cascata.

Defina um tempo limite de conexão e um tempo limite total de requisição em todas as chamadas externas. Sempre.

<?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.

Novas tentativas — com cuidado

Falhas transitórias (uma instabilidade breve, uma curta interrupção de rede, um 503) geralmente têm sucesso em uma segunda tentativa. Mas novas tentativas são perigosas: faça tentativas com excesso de rapidez e você amplificará a carga sobre um serviço que já está enfrentando dificuldades. Regras:

  • Faça novas tentativas apenas para operações idempotentes e erros que permitem nova tentativa (tempos limite, 5xx, 429).
  • Limite o número de tentativas.
  • Nunca faça novas tentativas para erros de cliente 4xx — eles simplesmente falharão novamente.

Atraso exponencial + aleatoriedade

Novas tentativas em intervalos fixos feitas por muitos clientes se sincronizam, formando uma avalanche de solicitações que martela o serviço em recuperação em várias ondas. A solução é usar atraso exponencial (dobrar o atraso a cada tentativa) mais aleatoriedade para distribuir a carga.

<?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));
}

Um ciclo de tentativas

Combinando as peças: limite o número de tentativas, faça novas tentativas apenas para erros que permitem nova tentativa, aguarde entre as tentativas usando um atraso com aleatoriedade e propague a última falha se todas as tentativas falharem.

<?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); }

O disjuntor

Novas tentativas ajudam com instabilidades breves, mas, se uma dependência estiver realmente indisponível, tentar novamente cada requisição apenas desperdiça tempo e recursos. Um disjuntor monitora as falhas e, quando elas ultrapassam um limite, abre — interrompendo as chamadas e falhando instantaneamente, em vez de esperar por um serviço indisponível.

Ele tem três estados: Fechado (as chamadas fluem e as falhas são contabilizadas), Aberto (as chamadas são rejeitadas imediatamente) e Semiaberto (algumas chamadas de teste verificam a recuperação).

Máquina de estados do disjuntor

As transições são estas: Fechado → Aberto quando as falhas ultrapassam o limite. Aberto → Semiaberto após um período de resfriamento. Semiaberto → Fechado após uma verificação bem-sucedida, ou de volta a Aberto em caso de falha. O estado precisa ser compartilhado entre os processos PHP (Redis/APCu), pois cada requisição é um processo novo.

<?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;
        }
    }
}

Compartimentação

Com nome inspirado nos compartimentos de navios, o padrão de compartimentação isola recursos para que uma falha em uma área não afunde toda a embarcação. Se todos os seus trabalhadores puderem acessar o serviço Reports lento, uma indisponibilidade do Reports poderá consumir todos os trabalhadores e deixar o Checkout sem recursos.

Particione os recursos — conjuntos de conexões separados, conjuntos de trabalhadores/filas separados por dependência ou limites de concorrência por serviço de destino — para que uma dependência com falha esgote apenas sua própria parcela.

Degradação controlada e alternativas

Quando uma dependência não crítica estiver indisponível, degrade o serviço em vez de gerar um erro. Exiba um valor armazenado em cache, um valor padrão ou uma experiência reduzida. O estado aberto do disjuntor é o gatilho natural para o caminho alternativo.

<?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'];
    }
}

Chaves de idempotência para novas tentativas seguras

Novas tentativas só são seguras em operações idempotentes. Para uma ação não idempotente, como "cobrar o cartão", anexe uma chave de idempotência que o servidor use para eliminar duplicatas: se uma nova tentativa chegar depois que a primeira já tiver sido bem-sucedida (mas a resposta tiver sido perdida), o servidor retornará o resultado original em vez de cobrar duas vezes.

<?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 scene

Juntando tudo

Os padrões se compõem, e a ordem importa. Uma chamada externa robusta normalmente é aninhada assim:

  • Tempo limite em cada tentativa individual (mais interna).
  • Nova tentativa com atraso envolvendo a chamada com tempo limite para falhas transitórias.
  • Disjuntor envolvendo a nova tentativa, para que uma indisponibilidade prolongada o acione rapidamente.
  • Compartimentação limitando a capacidade que essa dependência pode consumir.
  • Alternativa na camada mais externa, capturando tudo o que for propagado.

Existem bibliotecas com invólucros no estilo Resilience4PHP, mas entender as camadas é mais importante do que qualquer pacote específico.

Verificação rápida

Escolher o padrão correto.

Recapitulação

Sobrevivendo a falhas parciais:

  • Tempos limite em todas as chamadas evitam o esgotamento dos trabalhadores e as falhas em cascata.
  • Novas tentativas apenas para operações idempotentes e erros que permitem nova tentativa, com atraso exponencial + aleatoriedade.
  • Disjuntores falham rapidamente (Fechado → Aberto → Semiaberto) quando uma dependência está indisponível.
  • Compartimentação isola recursos para que uma falha não deixe tudo sem recursos.
  • Degradação controlada / alternativas mantêm os fluxos essenciais ativos.

Juntos, esses padrões transformam falhas inevitáveis em eventos contidos e recuperáveis.

Grátis para começar

Aprenda PHP com um tutor de IA — grátis

Escreva e execute código real no seu navegador, obtenha ajuda instantânea de um tutor de IA 24/7 e continue de onde parou na web ou no app.

Cursos
49
Aulas
195

Perguntas Frequentes

A aula “Resiliência: disjuntores e novas tentativas” é grátis?

Sim — o texto completo de “Resiliência: disjuntores e novas tentativas” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de PHP Academy, atualize para CoddyKit PRO. O curso de PHP Academy inclui 4 aulas no total.

O que vou aprender em “Resiliência: disjuntores e novas tentativas”?

Mantenha os sistemas saudáveis durante falhas parciais. Você pratica PHP Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar PHP Academy?

Nenhuma experiência prévia é necessária. PHP Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 4 de 4.

Quanto tempo leva a aula “Resiliência: disjuntores e novas tentativas”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de PHP Academy?

Sim. Cada aula de PHP Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. Do monólito aos microsserviços
  2. Comunicação entre serviços: REST e gRPC
  3. Gateways de API e descoberta de serviços
  4. Resiliência: disjuntores e novas tentativas
← Voltar para PHP Academy