Pular para conteúdo

9. Resiliência e Caos

Objetivo

Fazer o checkout sobreviver a um dia ruim do provedor de pagamento. Construir o adaptador payment e um provedor externo falso, colocar o Toxiproxy entre os serviços, ver uma dependência lenta derrubar o order inteiro (uma falha em cascata) e corrigir isso com o Resilience4j — timeout, retry com backoff e jitter, circuit breaker, bulkhead —, medindo depois de cada padrão. Terminar com um game day: um incidente ensaiado, com falhas escondidas e uma revisão sem culpados.

Às 10:00 a venda relâmpago abre e o provedor de pagamento, sob a carga de todos os sites de ingressos do país, começa a responder em três segundos em vez de trezentos milissegundos. Nada está "fora do ar". Mesmo assim, em um minuto o order para de responder qualquer coisa — até o GET /orders/{id}, que nunca fala com o provedor —, porque todas as suas threads estão esperando o payment. Este lab reproduz essa falha no seu laptop e aplica os padrões da aula de confiabilidade, com um número para cada um.

Este lab constrói no BoxOffice o capítulo 10 da história do curso: a falha em cascata e as suas defesas — timeout, retry com jitter, circuit breaker e bulkhead —, depois um game day. Ele vem logo depois do lab 8 de propósito: caos exige observabilidade antes, então todo experimento é acompanhado no dashboard do lab 8 e aciona o alerta de burn rate dele.

Slides

Clique nos slides e use ← → para navegar; F abre em tela cheia e P abre a visão do apresentador, com as notas. Abrir os slides em uma nova aba

Antes de começar

Tempo cerca de 4 horas, mais 1 hora para o game day
Ferramentas as do lab 8: Docker com Compose v2, k6, curl, jq, um navegador para o Grafana; watch (macOS: brew install watch)
Recursos do Docker pelo menos 4 CPUs e 8 GB (mais três contêineres: payment, psp, toxiproxy)
Portas livres na sua máquina: 8085, 8474 (além das do lab 8)

Todo comando roda na raiz do seu repositório boxoffice, no estado em que o lab 8 o deixou:

Start state (end of lab 8)
boxoffice/
├── docker-compose.yaml     # labs 1-8: ... kafka, order, notification, otel-lgtm (+ x-otel anchor)
├── infra/                  # nginx/, postgres/, pgbouncer/, grafana/, otel/
├── load/  scripts/
└── services/
    ├── auth/  catalog/  gateway/  inventory/  notification/
    └── order/              # outbox with traceparent (lab 8)

Recupere o atraso

Pulou ou quebrou o lab 8? Baixe boxoffice-after-lab-8.zip, descompacte, cd boxoffice, rode sh scripts/get-otel-agent.sh e continue daqui.

Todo arquivo deste lab aparece completo nestas páginas e é publicado em https://insper.github.io/platform/2027.1/hands-on/9/code/<caminho>.

Arquitetura

flowchart LR
    k6[k6: pay + browse] --> order["order (Resilience4j)"]
    order -->|"timeout · retry · breaker · bulkhead"| tp1["toxiproxy: payment"]
    tp1 --> payment[payment]
    payment -->|"timeouts, Idempotency-Key"| tp2["toxiproxy: psp"]
    tp2 --> psp["psp (WireMock)"]
    gm[mestre do jogo] -.->|"latência, reset, queda"| tp1
    gm -.-> tp2
    order -.->|OTLP| lgtm[otel-lgtm]
    payment -.->|OTLP| lgtm

Componentes do BoxOffice envolvidos

Componente da paleta Neste lab
Payment psp, um WireMock que finge ser um provedor externo
3rd Party API o provedor atrás de um adaptador, com timeouts e chaves de idempotência
App Server order e payment, protegidos pelo Resilience4j
Alerting o alerta de burn rate do lab 8, usado para detectar as falhas do game day

O que você vai medir

Pergunta Métrica Ferramenta
Uma dependência lenta prejudica endpoints que não a usam? p95 e erros do browse enquanto o payment está lento k6
Quanto cada padrão ajuda, e quanto custa? p95 do pay, % confirmados, % de falha rápida k6
Quão rápido o breaker abre e se recupera? transições de estado ao longo do tempo /actuator/circuitbreakers
Quão rápido o time detecta e mitiga? tempo para detectar, tempo para mitigar linha do tempo do game day

Pré-requisitos

Passos

  1. Pagamento, provedor e Toxiproxy — o adaptador idempotente payment, um provedor WireMock, os proxies do Toxiproxy e o seu primeiro toxic.
  2. Padrões de resiliência — reproduza a cascata, depois adicione timeout, retry, circuit breaker e bulkhead, um de cada vez.
  3. Game day — papéis, hipóteses, falhas escondidas, linha do tempo e uma revisão sem culpados.
  4. Meça — o script do k6 com um cenário pay e um browse, e a tabela de resultados.

Experimente no playground

Experimente: em cascading-failure, rode o perfil load e aumente a latência do nó de terceiro (3rd party): veja a utilização do app server chegar a 100% e os erros dele se espalharem para todas as requisições. Depois defina um timeout e ative o circuit breaker na aresta para o terceiro, e rode de novo. Em retry-storm, compare retries: 3 com e sem backoff exp+jitter durante uma queda.

O simulador completo está no playground.

Meça

O passo Meça tem o script do k6, os comandos e a tabela de resultados para guardar em results/lab9.md; as revisões do game day vão para results/lab9-gameday.md.

Solução de problemas

Sintoma Causa provável Correção
curl localhost:8474/proxies falha o toxiproxy não está rodando, ou falta o infra/toxiproxy/toxiproxy.json docker compose ps toxiproxy; docker compose logs toxiproxy
todo pagamento falha com I/O error … Connection refused um experimento deixou o proxy do payment desativado curl -s -X POST localhost:8474/reset (remove os toxics, reativa os proxies)
o pay devolve 503 mesmo sem toxic o circuit breaker ainda está OPEN desde o último experimento, ou a primeira chamada foi a frio espere 10 s (wait-duration-in-open-state) e tente de novo; confira /actuator/circuitbreakers
o psp responde 404 o stub do WireMock não foi montado o arquivo precisa ser infra/psp/mappings/charge.json; docker compose restart psp
as variáveis PAYMENT_* parecem ignoradas elas só chegam ao contêiner pelo docker-compose.yaml e por uma recriação PAYMENT_BULKHEAD=10 docker compose up -d order (não restart)
os resultados variam muito entre execuções o laptop roda o k6 e 20 contêineres feche outros aplicativos, rode cada experimento duas vezes, compare razões

Boas práticas neste lab

  • Um timeout em toda chamada de saída, menor que o de quem chama — PaymentClient.java (500 ms de conexão, 1 s de leitura). Por quê: Timeout.
  • Retries só com chave de idempotência, com backoff exponencial e jitter — o PaymentClient.java envia Idempotency-Key: order-<id>; o application.yaml permite 3 tentativas, 200 ms ± 50%. Por quê: Retry com backoff e jitter.
  • Um bulkhead menor que o pool de threads, falhando rápido quando está cheio — application.yaml: 10 das 50 threads, max-wait-duration: 0. Por quê: Bulkhead.
  • Nenhuma transação de banco aberta enquanto se espera uma chamada remota — o OrderService.java chama o payment primeiro e atualiza o status numa transação curta. Por quê: Falhas em cascata (senão a espera esgotaria o pool de conexões).
  • Caos como código, com um aborto de um comando — scripts/game-day.sh e infra/toxiproxy/toxiproxy.json; o reset remove todo toxic. Por quê: Game days.

Limpeza

curl -s -X POST localhost:8474/reset    # no toxic left behind
docker compose stop                     # keep the stack for lab 10's images
docker compose down -v                  # or remove everything

Estado final

End state (what this lab added or changed)
boxoffice/
├── docker-compose.yaml     # + psp, toxiproxy, payment; order -> toxiproxy:18085 + PAYMENT_* knobs
├── infra/
│   ├── grafana/alerting/slo-order.yaml   # the lab 8 burn-rate alerts, for order (game day)
│   ├── psp/mappings/charge.json
│   └── toxiproxy/toxiproxy.json
├── load/pay-under-chaos.js
├── scripts/game-day.sh
├── results/lab9.md, lab9-gameday.md
└── services/
    ├── order/              # pom (+ resilience4j, aspectj), application.yaml, OrderService,
    │                       # OrderController (+ /pay), payment/ package
    └── payment/            # new: idempotent adapter to the provider (+ Dockerfile, .dockerignore)

Faça commit e tag: git add -A && git commit -m "lab 9: resilience" && git tag lab9.

Checklist

  • payment, psp e toxiproxy rodam no docker-compose.yaml; order e payment chegam às suas dependências pelo Toxiproxy
  • o POST /payments é idempotente pela Idempotency-Key, e a chave é repassada ao provedor
  • o POST /orders/{id}/pay confirma o pedido (status CONFIRMED + OrderConfirmed no outbox) e é protegido por timeout, retry, circuit breaker e bulkhead
  • a execução sem proteção mostra a cascata (browse degradado); a execução de cada padrão mostra o seu efeito, em números
  • um game day com pelo menos três experimentos escondidos, uma linha do tempo e uma revisão sem culpados por rodada
  • tabela de resultados preenchida em results/lab9.md e explicada
  • autoverificação: curl -s -X POST localhost:8474/reset, depois PAYMENT_RESILIENCE=true docker compose up -d --wait order, e uma chamada pay devolve CONFIRMED; push com a tag lab9

Começar: Pagamento, provedor e Toxiproxy