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:
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
- Labs 7 (o serviço
order) e 8 (dashboards e alertas) concluídos. - Leia a aula de confiabilidade: padrões de resiliência e engenharia do caos.
Passos
- Pagamento, provedor e Toxiproxy — o adaptador idempotente
payment, um provedor WireMock, os proxies do Toxiproxy e o seu primeiro toxic. - Padrões de resiliência — reproduza a cascata, depois adicione timeout, retry, circuit breaker e bulkhead, um de cada vez.
- Game day — papéis, hipóteses, falhas escondidas, linha do tempo e uma revisão sem culpados.
- Meça — o script do k6 com um cenário
paye umbrowse, 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.javaenviaIdempotency-Key: order-<id>; oapplication.yamlpermite 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.javachama opaymentprimeiro 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.sheinfra/toxiproxy/toxiproxy.json; oresetremove 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
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,pspetoxiproxyrodam nodocker-compose.yaml;orderepaymentchegam às suas dependências pelo Toxiproxy - o
POST /paymentsé idempotente pelaIdempotency-Key, e a chave é repassada ao provedor - o
POST /orders/{id}/payconfirma o pedido (statusCONFIRMED+OrderConfirmedno outbox) e é protegido por timeout, retry, circuit breaker e bulkhead - a execução sem proteção mostra a cascata (
browsedegradado); 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.mde explicada - autoverificação:
curl -s -X POST localhost:8474/reset, depoisPAYMENT_RESILIENCE=true docker compose up -d --wait order, e uma chamadapaydevolveCONFIRMED; push com a taglab9
Relacionados
- Teoria: Confiabilidade · SLOs e orçamentos de erro · Padrões de resiliência · Engenharia do caos · Integrações externas
- Estudo de caso: Sistema de pagamentos
- Projeto: Game day de caos
- Próximo lab: 10. Kubernetes adiciona autocura e autoscaling.