Retries, Timeouts & Backoff
Torne as chamadas HTTP de saída resilientes com timeouts, backoff exponencial, jitter e consciência de idempotência.
Receita
Cartão de receita de referência rápida - pronto para copiar e colar.
async fn with_retry<F, Fut, T, E>(mut f: F, max: u32) -> Result<T, E>
where
F: FnMut() -> Fut,
Fut: std::future::Future<Output = Result<T, E>>,
{
let mut attempt = 0;
loop {
match f().await {
Ok(v) => return Ok(v),
Err(e) if attempt >= max => return Err(e),
Err(_) => {
let delay = std::time::Duration::from_millis(100 * 2u64.pow(attempt));
tokio::time::sleep(delay).await;
attempt += 1;
}
}
}
}Quando usar isso: Qualquer chamada pela internet que possa falhar transitoriamente - 502, conexão redefinida, timeout.
Exemplo de Trabalho
use reqwest::StatusCode;
use std::time::Duration;
async fn fetch_with_policy(client: &reqwest::Client, url: &str) -> Result<String, reqwest::Error> {
let mut attempt = 0u32;
loop {
let result = client.get(url).send().await;
match result {
Ok(resp) if resp.status().is_success() => return resp.text().await,
Ok(resp) if resp.status() == StatusCode::TOO_MANY_REQUESTS => {
let retry_after = resp.headers()
.get("retry-after")
.and_then(|v| v.to_str().ok())
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(2u64.pow(attempt.min(5)));
tokio::time::sleep(Duration::from_secs(retry_after)).await;
}
Ok(_) | Err(_) if attempt < 3 => {
let base = 100u64 * 2u64.pow(attempt);
let jitter = rand::random::<u64>() % 50;
tokio::time::sleep(Duration::from_millis(base + jitter)).await;
}
Ok(resp) => return resp.error_for_status().map(|_| unreachable!()),
Err(e) if e.is_timeout() && attempt < 3 => {}
Err(e) => return Err(e),
}
attempt += 1;
}
}O que isso demonstra:
- Backoff exponencial limitado à tentativa 5.
- Jitter reduz tempestades de retentativas sincronizadas.
- Respeita
Retry-Afterem respostas 429. - Distingue erros de timeout de outras falhas.
Mergulho Profundo
Como Funciona
- Timeouts limitam o tempo de espera; retentativas recuperam falhas transitórias.
- Backoff espalha a carga após interrupções.
- Apenas métodos idempotentes (GET, PUT com chave de idempotência) são seguros para retentativas cegas.
Tabela de Política de Retentativa
| Condição | Retentativa? |
|---|---|
| Timeout de conexão | Sim, com backoff |
| 500/502/503/504 | Sim, limitado |
| 400/401/404 | Não |
| POST sem idempotência | Não |
Notas de Rust
// reqwest-middleware + reqwest-retry crates for production policiesArmadilhas
- Retentando pagamentos POST - Risco de cobrança dupla. Correção: Cabeçalho Idempotency-Key e dedup no lado do servidor.
- Sem tentativas máximas - Retentativa infinita sobrecarrega a dependência falha. Correção: Limitar tentativas; disjuntor após o limite.
- Timeout do cliente para toda a aplicação - Metadados rápidos misturados com upload lento. Correção: Substituições por solicitação.
- Jitter zero - Rebanho trovejante na recuperação. Correção: Jitter completo no atraso do backoff.
- Retentando leituras não idempotentes que modificam dados - Alguns GETs acionam efeitos colaterais. Correção: Conhecer o contrato da API.
Alternativas
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
Disjuntor (tower) | Interrupção sustentada | Apenas falhas raras |
| Fila + worker | Absorver picos assíncronos | Necessita de resposta síncrona |
| Cache de fallback | Modo de leitura degradado | Consistência forte necessária |
FAQs
Quantas retentativas?
3-5 para leituras idempotentes; 0-1 para escritas, a menos que a idempotência seja garantida.
Valores de timeout do cliente?
Conexão 2-5s, total 10-30s dependendo do SLA.
Retentativas gRPC?
Middleware do cliente tonic ou política de service mesh.
Registrar retentativas?
Avisar sobre retentativa com contagem de tentativas e host de upstream.
Hedging?
Enviar solicitação duplicada após um atraso - apenas para caminhos de leitura seguros.
Bulkhead?
Limitar chamadas de saída concorrentes por dependência.
Testar retentativas?
Sequência wiremock retornando 503 e depois 200.
Falhas de DNS?
Retentar com backoff; cache de DNS no nível do sistema operacional.
Limite de taxa global?
Parar retentativas quando o orçamento local estiver esgotado.
Observabilidade?
Métrica upstream_retries_total{dependency}.
Relacionado
- Clientes HTTP (reqwest) - Configuração do cliente
- Melhores Práticas de Rede - Regras de política
- Noções Básicas de Design de API - Idempotência
- Observabilidade - Métricas e traces
- Limitação de Taxa e Prevenção de Abuso - Tratamento de 429
Versões da Stack: Esta página foi escrita para Rust 1.97.0 (edição 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, e Polars 0.46+.