Arquitetura de software · Confiabilidade e segurança · 7 min de leitura
Circuit breaker e backpressure
Quando uma dependência fica lenta, insistir derruba quem chama. O circuit breaker para de chamar por um tempo; o backpressure faz o produtor desacelerar em vez de afogar o consumidor.
Arraste para o lado para ver o diagrama inteiro.
Falhas em cascata
Um serviço de recomendações fica lento. Cada requisição que o chama segura uma thread ou conexão esperando o timeout; em segundos o pool acaba e o seu serviço também para de responder — inclusive nas rotas que nem usam recomendações. Timeouts curtos ajudam, mas continuar chamando algo que está falhando só piora para os dois lados.
Circuit breaker
- Fechado: funcionamento normal; conta as falhas numa janela recente.
- Aberto: passou do limite (ex.: 50% de falhas em 20 chamadas) — falha imediatamente, sem chamar, por um tempo.
- Meio-aberto: deixa passar poucas chamadas de teste; se derem certo, fecha; senão, abre de novo.
Combine com um fallback útil: dados em cache, lista padrão, funcionalidade desligada (“recomendações indisponíveis”). E isole recursos por dependência (bulkhead): um pool de conexões para cada uma.
Backpressure
O problema inverso: um produtor mais rápido que o consumidor. Sem controle, a fila em memória cresce até estourar. Backpressure é o consumidor sinalizar quanto aguenta:
- Filas limitadas que bloqueiam ou recusam quando cheias.
- Reactive Streams: o consumidor pede
request(n)itens. - Kotlin Flow: o coletor suspende o emissor naturalmente;
buffer,conflate(fica só o mais recente) ecollectLatestescolhem o que fazer com o excesso. - No servidor: descarte de carga — responder 503 rápido para parte das requisições é melhor que atender todas devagar.
No código
class CircuitBreaker(
private val maxFailures: Int = 5,
private val openFor: Duration = 30.seconds,
private val clock: () -> Instant = Clock.System::now,
) {
private enum class State { CLOSED, OPEN, HALF_OPEN }
private var state = State.CLOSED
private var failures = 0
private var openedAt = Instant.DISTANT_PAST
private val lock = Mutex()
suspend fun <T> call(fallback: () -> T, block: suspend () -> T): T {
lock.withLock {
if (state == State.OPEN) {
if (clock() - openedAt < openFor) return fallback()
state = State.HALF_OPEN // tempo esgotado: deixa uma chamada testar
}
}
return try {
block().also { lock.withLock { state = State.CLOSED; failures = 0 } }
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
lock.withLock {
failures++
if (state == State.HALF_OPEN || failures >= maxFailures) {
state = State.OPEN
openedAt = clock()
}
}
fallback()
}
}
}
// Uso: recomendações nunca derrubam a tela de produto.
val recs = breaker.call(fallback = { emptyList() }) { api.recommendations(productId) } Versão didática: conta falhas seguidas, abre por 30 segundos e testa uma chamada antes de fechar. Em produção, use uma biblioteca (Resilience4j, Polly) com janela deslizante e métricas.
Em resumo
- Insistir numa dependência lenta derruba quem chama.
- Fechado → aberto → meio-aberto.
- Tenha fallback e pools separados (bulkhead).
- Backpressure: o consumidor dita o ritmo.
- Fila sem limite é um vazamento de memória adiado.
Vantagens e custos
- Evita falhas em cascata.
- Dá tempo para a dependência se recuperar.
- Degradação controlada em vez de queda total.
- Limites mal calibrados abrem à toa ou tarde demais.
- Fallback precisa ser pensado por funcionalidade.
- Mais um estado para observar e testar.
Perguntar à IA
Monte um prompt pronto sobre Circuit breaker e backpressure na sua linguagem e no seu caso, e abra num chat gratuito.
O prompt vai para o site que você escolher. Não cole senhas, tokens nem dados reais de clientes. Para Gemini e outros, copie e cole.