Skip to content

Latest commit

 

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Node Microservices Resilience Patterns

Este projeto configura uma arquitetura de microserviços em Node.js com um API Gateway/Consumidor (order-service), um serviço de destino instável (payment-service), um executor de testes de carga (k6-runner) e um gerador de relatórios em formato texto em ./results/report.txt.

Este é um case de resiliência: o payment-service simula um memory leak real (não latência/erro artificial) que cresce até o limite de memória do container, entra em crash-loop (OOM-kill + restart automático) sob carga contínua, e o order-service foi evoluindo, branch a branch, para lidar com isso — retry com backoff exponencial e jitter, circuit breaker e bulkhead.


Arquitetura e Serviços

  1. order-service (Porta 3000): Gateway de entrada. Chamadas ao payment-service que falham momentaneamente (timeout ou erro 5xx) são reenviadas automaticamente com backoff exponencial e jitter (até 3 tentativas por padrão — ver order-service/README.md). Por fora do retry, um circuit breaker acompanha o resultado final de cada pedido: depois de falhas consecutivas seguidas ele abre e passa a falhar rápido (sem chamar o payment-service) por alguns segundos, protegendo o serviço já degradado de ser sobrecarregado enquanto reinicia. Um bulkhead (limite de concorrência, padrão 8 chamadas simultâneas) isola o event loop do order-service: pedidos além do limite são rejeitados na hora em vez de se acumularem esperando o payment-service responder.
  2. payment-service (Porta 3001 interna/mapeada): Serviço instável simulando um memory leak real, fazendo a memória física crescer continuamente até se aproximar do limite de memória do container (256MB). Tem restart: on-failure, então após um OOM-kill o Docker o reinicia sozinho (memória volta a zero) — sob carga contínua isso forma um ciclo real de crash-loop.
  3. k6-runner: Executor de testes de carga do Grafana k6, gerando resumo estruturado em ./results/k6-summary.json.

Como Executar e Gerar o Relatório

Você pode rodar todo o fluxo de teste e geração de relatório com um único comando:

./scripts/run-test.sh

Evolução do Case

Cada padrão de resiliência foi implementado e testado isoladamente em seu próprio branch, cada um com seu README congelado descrevendo a implementação e os números da execução naquele ponto (os valores variam um pouco a cada rodada, já que o timing do memory leak/GC não é perfeitamente determinístico — servem para mostrar a tendência, não como benchmark exato):

Branch O que foi adicionado Taxa de erro p(95)
feature/01-base-service-unstable Cenário base: payment-service com memory leak real, sem nenhuma proteção no order-service 16.43% 3005ms
feature/02-pattern-retry-backoff Retry com backoff exponencial + jitter, e restart: on-failure no payment-service 2.58% 1148ms
feature/03-pattern-circuit-breaker Circuit breaker: falha rápido quando o payment-service está degradado, em vez de insistir 7.84% 400ms
feature/04-pattern-bulkhead-isolation (estado atual, refletido abaixo) Bulkhead: limite de concorrência simultânea ao payment-service, isolando o event loop do order-service 7.00% 205ms

Vale notar que a taxa de erro sobe entre o branch 02 e o 03/04 — não é regressão, é o trade-off esperado: circuit breaker e bulkhead priorizam falhar rápido e proteger os dois serviços em vez de insistir tentando salvar cada pedido a qualquer custo (o que reduz bastante a latência p(95) e evita que uma indisponibilidade do payment-service derrube o order-service junto). O objetivo desses padrões é estabilidade do sistema sob falha parcial, não garantia de entrega de 100% dos pedidos — isso exigiria uma camada assíncrona (fila/outbox), que é um problema arquitetural diferente.


Situação Atual do Sistema (branch feature/04-pattern-bulkhead-isolation)

Resumo da última execução registrada em results/report.txt (retry com backoff/jitter + circuit breaker + bulkhead no order-service, restart: on-failure no payment-service, todos ativos):

  • Requisições HTTP: 443 no total (12.56 req/s), com 7.00% de taxa de erro.
  • Latência: média de 86.13ms, mediana de 16.08ms, p(95) de 204.89ms e máxima de 2561.69ms.
  • Checks de negócio: 412 de 443 pedidos foram confirmados (201); 31 pedidos (7.00%) voltaram como 502/503.
  • Ciclo de crash-loop do payment-service: dois ciclos de leak dentro dos ~44s de teste (RAM sobe até a faixa de 250-256MB, cai bruscamente por volta de t+31s com o OOM-kill/restart, e sobe de novo até o fim do teste).
  • Bulkhead em ação: com BULKHEAD_MAX_CONCURRENT=8 e fila zero, 19 pedidos foram rejeitados na hora por excesso de concorrência ("8/8 em execução, 0/0 na fila"), nem chegando a consumir uma tentativa de retry ou contar como falha do circuit breaker. O circuito ainda abriu 1 vez (5 fail-fast) e o retry disparou 11 vezes.
  • Efeito comparado com a execução anterior (sem bulkhead): o p(95) caiu bastante (400ms → 205ms) — o bulkhead evita que o event loop fique represado esperando várias chamadas lentas ao mesmo tempo, então a maioria dos pedidos passa rápido. Em compensação, a máxima subiu (1371ms → 2562ms) nesta execução: como o bulkhead é sem fila (rejeita na hora em vez de esperar), ele não "segura" pedidos elegantemente — quem não rejeitado ainda pode encontrar o payment-service lento em um momento ruim do ciclo de memória. A taxa de erro ficou estável (7.84% → 7.00%), mostrando que o bulkhead trocou parte das falhas por rejeições mais baratas (sem gastar retry/rede) sem piorar o resultado geral.

Em resumo, os resultados mostram que a combinação de retry com backoff exponencial e jitter, circuit breaker e bulkhead torna o sistema significativamente mais resiliente a falhas parciais. Mesmo diante de um payment-service degradado por um memory leak e entrando em crash-loop, o order-service permanece estável, limita o impacto da indisponibilidade e evita que a falha se propague para o restante da aplicação. O custo dessa estratégia é aceitar a rejeição imediata ou a falha rápida de uma parcela das requisições, mas esse trade-off preserva a disponibilidade geral do sistema e impede que um único serviço comprometido provoque um efeito cascata capaz de indisponibilizar toda a arquitetura.

Esse case é continuação dos estudos do TCC

About

Resiliencia sob estresse de carga: analise comparativa de circuit breaker, retry, bulkhead em microsserviços node.js

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages