Table of Contents
Introdução: O desafio de resistência na computação sem servidor
A computação sem servidor reformou como os desenvolvedores constroem e implementam aplicativos abstraindo o gerenciamento de infraestrutura e oferecendo escala automática. Plataformas como AWS Lambda, Azure Functions e Google Cloud Functions permitem que as equipes se concentrem na lógica de negócios, enquanto pagam apenas para uso real. No entanto, este modelo introduz desafios de resiliência exclusivos. Funções são apátridas, efêmeras e muitas vezes se comunicam entre serviços distribuídos – uma única dependência falha pode desencadear uma cascata de timeouts, repetições e blowouts de custos.Começos frios, estrangulamento e falhas de rede transientes são comuns.Para manter a confiabilidade sem sacrificar os benefícios do servidor, os desenvolvedores devem adotar padrões comprovados para tolerância a falhas.Um dos mais eficazes é o padrão do Disjuntor.
Um disjuntor funciona como uma válvula de segurança para sua aplicação. Ele monitora chamadas para serviços remotos ou recursos e evita novas tentativas quando as taxas de falha excederem um limite. Isto protege o sistema de ser sobrecarregado, permite que o tempo de falha dos serviços para recuperar, e fornece um retorno limpo para os usuários. Neste artigo, nós expandimos o conteúdo original para lhe dar um guia abrangente e acionável para implementar disjuntores em aplicações sem servidor, incluindo explicações detalhadas, considerações específicas de plataforma, exemplos de código e melhores práticas.
Compreender o padrão do disjuntor na profundidade
O padrão do disjuntor foi popularizado por Michael Nygard em seu livro Liberte-o! e mais tarde formalizado em padrões nativos de nuvem. Ele se comporta como um disjuntor elétrico: quando um circuito detecta uma falha (por exemplo, um curto), ele abre e pára o fluxo de corrente. Em software, os estados de circuito são:
- Fechado: Solicita fluxo normalmente para o serviço a jusante. O disjuntor monitora as taxas de falha (por exemplo, erros HTTP 5xx, timeouts). Se falhas excederem um limite configurado dentro de uma dada janela de tempo (por exemplo, 10 falhas em 30 segundos), as viagens de circuito para Abrir[.
- Abrir:] Os pedidos são imediatamente rejeitados (ou invoca-se a lógica de recuo) sem chamar o serviço em falta. Isto evita o desperdício de recursos e dá ao serviço a jusante uma janela de recuperação. Após um período de tempo limite (por exemplo, 30 segundos), o circuito transições para Meio- Aberto[.
- Meio-Aberto:] É permitido um número limitado de pedidos de teste. Se estes tiverem sucesso (dentro de critérios definidos de sucesso), o circuito repõe-se para Closed[. Se persistirem falhas, ele retorna para Open[ e o tempo- limite é muitas vezes reposto ou incrementado.
Sem ela, uma breve falha pode fazer com que todos os clientes tentem de novo simultaneamente, criando uma manada trovejante que estende a falha. O padrão do Disjuntor também fornece feedback de falha precoce aos clientes, permitindo uma degradação graciosa – por exemplo, retornando dados em cache ou uma mensagem de erro amigável em vez de um tempo limite.
O artigo seminal de Martin Fowler sobre Disjuntor continua a ser a referência fundamental.Ele explica como o padrão se integra com outros padrões de resiliência como Retry e Bulkhead.
Parâmetros-chave para a sintonização
Cada implementação de disjuntor expõe parâmetros configuráveis que devem ser ajustados ao comportamento da sua aplicação:
- Limite de falha: Número de falhas consecutivas (ou taxa sobre uma janela) para abrir o circuito.
- Duração do tempo: Quanto tempo o circuito permanece aberto antes de se passar para semi-aberto.
- Contagem de testes em aberto: Número de pedidos bem sucedidos necessários para fechar o circuito.
- Classificação de erro: Quais respostas contam como falhas? Apenas 5xx? Tempo limite? 4xx? (Normalmente apenas erros no lado do servidor).
- Recuperar o tempo- limite: Opcionalmente incremental (retrocesso exponencial) para evitar comutar.
Aplicações sem servidor adicionam complexidade: porque as funções são efêmeras, você não pode confiar no estado de memória para o circuito. Se uma instância Lambda falhar, o estado de circuito pode ser perdido. Assim, o armazenamento externo de estado (DynamoDB, Redis ou um serviço gerenciado) é frequentemente necessário.
Implementação de disjuntores em ambientes sem servidor
A implementação de um disjuntor em uma arquitetura sem servidor requer a adaptação do padrão às restrições da plataforma. Vamos cobrir três abordagens primárias: usar recursos de API gerenciados, alavancar bibliotecas de terceiros dentro do seu código de função e empregar serviços de orquestração como Funções AWS Step.
Abordagem 1: Troteamento de Nível de Gateway API e quebra de circuito
O AWS API Gateway pode agir como um disjuntor rudimentar, acelerando solicitações para uma função Lambda backend. Quando a função retorna erros de 5xx demais ou excede os limites de concorrência, o API Gateway pode ser configurado para retornar uma resposta de retorno (por exemplo, uma mensagem estática de um autor personalizado ou resposta de integração). No entanto, este não é um disjuntor de estado verdadeiro, ele depende de limitação de taxa em vez de monitoramento de taxa de falha. Para cenários simples, pode ser suficiente.
Exemplo: Defina um plano de uso do Gateway API com um limite de taxa e limite de ruptura que reflita a capacidade da sua infraestrutura. Quando a função Lambda é sobrecarregada, o Gateway API responde com imediatamente, agindo como um disjuntor de uma só via. Mas isso não distingue entre falhas de estrangulamento e falhas de serviço reais.
Abordagem 2: Disjuntores em funcionamento com bibliotecas
A abordagem mais flexível é incorporar uma biblioteca de disjuntores dentro das suas funções Lambda. Como as funções Lambda são apátridas e escaladas horizontalmente, o estado do disjuntor deve ser armazenado externamente para que cada invocação possa verificar o estado atual. Um padrão comum usa Amazon DynamoDB (ou Redis com ElastiCache) para persistir o estado do circuito através das invocações de funções.
Para Node.js, a biblioteca Opossum é um disjuntor amplamente utilizado. Ele suporta funções de retorno, tempo de espera e limite de volume. Aqui está uma implementação simplificada adaptada para AWS Lambda:
const CircuitBreaker = require('opossum');
const AWS = require('aws-sdk');
const dynamo = new AWS.DynamoDB.DocumentClient();
const circuitBreakerState = {
state: 'CLOSED',
failureCount: 0,
lastFailureTime: null
};
// Persist state in DynamoDB after each transition
async function persistState(newState) {
await dynamo.put({
TableName: 'CircuitBreakerState',
Item: { serviceId: 'payment-service', ...newState }
}).promise();
}
async function loadState() {
const data = await dynamo.get({
TableName: 'CircuitBreakerState',
Key: { serviceId: 'payment-service' }
}).promise();
return data.Item || circuitBreakerState;
}
// The actual downstream call
async function callPaymentService(payload) {
const http = require('axios');
const response = await http.post('https://payment.example.com/charge', payload);
return response.data;
}
// Circuit breaker options
const options = {
errorThresholdPercentage: 50,
resetTimeout: 30000,
volumeThreshold: 10
};
// Create breaker with external state integration (simplified)
const breaker = new CircuitBreaker(callPaymentService, options);
breaker.fallback(() => ({ error: 'Payment service unavailable, order processed in offline mode' }));
exports.handler = async (event) => {
// Load state from DynamoDB and update breaker
const savedState = await loadState();
// Opossum doesn't natively restore state; you'd need to implement a wrapper.
// For brevity, assume the breaker is fresh per function invocation but uses external checks.
// In production, use a shared cache with TTL instead of per-invocation state load.
return breaker.fire(event.body);
};
Este exemplo omite a integração completa para clareza. Na prática, você precisa sincronizar o estado do disjuntor em muitas invocações de funções simultâneas usando escrita condicional no DynamoDB (bloqueio otimista) para evitar condições de corrida. Para cenários de alto rendimento, uma instância do Redis (por exemplo, usando o ElastiCache Serverless) é muitas vezes mais performante.
Abordagem 3: Funções de passo AWS – Disjuntor de nível de orquestração
Para fluxos de trabalho em várias etapas (por exemplo, checkout de comércio eletrônico), as Funções AWS Step podem modelar um disjuntor como uma máquina de estado. O estado pode verificar um contador ou bandeira armazenado em uma tabela DynamoDB. Se a contagem de falhas exceder um limiar, o fluxo de trabalho redireciona para um caminho de retorno (por exemplo, envie um e- mail para um administrador, fila para processamento manual). Isto fornece um disjuntor de nível superior que abrange várias chamadas de serviço.
Exemplo: Uma Função de Passo que chama dois serviços a jusante. Após uma falha, ele incrementa um contador DynamoDB. Antes de cada invocação subsequente, a Função de Passo lê o contador. Se exceder 5, o fluxo de trabalho imediatamente toma o caminho de retorno. Isto é efetivamente um disjuntor no nível de fluxo de trabalho.
Desafios e soluções específicas sem servidor
- Congelado inicia: O estado disjuntor precisa sobreviver à reciclagem de instância de função. Use o estado externo com um TTL curto para fechar automaticamente o circuito após um período de nenhuma atividade.
- Concorrencia: Muitas instâncias de função podem verificar e atualizar o estado simultaneamente. Use o bloqueio otimista (expressões de condição DynamoDB) ou padrões de incremento/decremento atômico.
- Custo: Cada verificação de estado adiciona custos de leitura/gravação. Estado de cache em memória com uma expiração curta (por exemplo, 1 segundo) dentro da mesma instância de função para reduzir o DynamoDB lê, mas aceita a consistência eventual.
- Tempo de pausa granularidade: As funções Lambda têm um tempo máximo de invocação (15 minutos). Tempos de disjuntor devem ser muito mais curtos (segundos) para evitar manter a função aberta.
Benefícios de usar disjuntores
As vantagens vão muito além do básico. Vamos explorar cada benefício em um contexto sem servidor:
Melhor resiliência – prevenção de falhas em cascata
As cadeias sem servidor são frágeis. Se o serviço A chama B, e B chama C, e C falha, a falha se propaga. Um disjuntor na chamada de B para C fará com que B abra seu circuito após algumas falhas. Agora, os pedidos de A para B são imediatamente rejeitados com um retrocesso, impedindo B de esgotar seu limite de concorrência e se tornar um gargalo. Isso isola a falha à sua origem.
Recuperação mais rápida – Auto-cura sem intervenção manual
Quando um circuito está aberto, o serviço em falha obtém um período de descanso. Não são enviados pedidos, permitindo- lhe recuperar (por exemplo, reiniciar, limpar uma fuga de memória ou reconfigurar). O estado semi- aberto sonda periodicamente o serviço. Uma vez que ele responda com sucesso, o circuito fecha automaticamente. Esta auto- cura é vital para as funções de depuração sem servidor onde a depuração é difícil.
Experiência de usuário aprimorada – Degradação graciosa
Em vez de mostrar uma página genérica "erro do servidor" ou carregador girando, você pode retornar dados obsoletos, uma versão simplificada do recurso, ou uma mensagem amigável. Por exemplo, um serviço de recomendação de produto pode usar um disjuntor: quando aberto, a página do produto mostra "Recomendações temporariamente indisponível" em vez de falhar inteiramente.
Poupança de custos – Evitar invocações desnecessárias
Os preços sem servidor são baseados em pedidos e duração. Quando um serviço a jusante está a falhar, continuando a chamá- lo de desperdício de dinheiro. Cada invocação da sua função que falha imediatamente (ou que resulta num tempo limite à espera da corrente) ainda custa. Um disjuntor pára estas chamadas, reduzindo os custos durante as janelas de falha.
Melhores práticas para a implantação de disjuntores
A implementação de um disjuntor não é uma atividade de tamanho único. Use estas práticas para maximizar a eficácia em um ambiente sem servidor.
Definir limites de falha adequados e prazos
Limiares de base em SLAs realistas. Por exemplo, se o seu serviço a jusante visa 99,9% de tempo de funcionamento, um limiar de 5 falhas por minuto pode ser demasiado sensível (poderia abrir-se durante pequenos blips). Comece com um limiar mais elevado (por exemplo, taxa de erro de 20% ao longo de uma janela de 1 minuto) e ajuste usando dados de monitorização. Os prazos de espera devem ser ligeiramente mais longos do que o tempo de resposta típico do serviço a jusante, mas mais curto do que o tempo de espera geral da sua função.
Aplicar mecanismos de recuperação
Cada solicitação de circuito aberto deve ter um retorno. As opções incluem:
- Recuperar dados em cache (de ElastiCache, CloudFront ou um banco de dados).
- Em fila para o processamento posterior (por exemplo, SQS DLQ).
- Devolve um valor padrão ou uma resposta estática.
- Redirecionar para uma versão degradada do recurso (por exemplo, desativar a personalização).
Os recuos devem ser idempotentes sempre que possível, especialmente para as escritas.
Monitor e estados de circuito de log
Instrumente o disjuntor para registrar todas as mudanças de estado e métricas de falha. Use o CloudWatch Metrics (por exemplo, métricas personalizadas para contagem aberta de circuito, testes semi-abertos, uso de retrocesso). Defina alarmes: se um circuito permanecer aberto por um período prolongado, notifique operações. Também registre o motivo de falha – tempo limite, código de erro, etc. – para ajudar a depuração.
Combine com outros padrões de resiliência
- [[FLT: 0]] Tente novamente: Use um padrão de reteste [[FLT: 2]] dentro [[FLT: 3]] do disjuntor, mas com retrocesso exponencial e jitter. O disjuntor em si não deve tentar novamente; em vez disso, a chamada do cliente é envolto com uma política de reteste (por exemplo, as repetições incorporadas do AWS SDK). O disjuntor abre depois de todas as repetições terem falhado.
- Bulkhead: Isole os recursos por função ou serviço. Por exemplo, reserve um limite de concorrência para funções críticas vs. não críticas. Quando um circuito se abre, reduz a carga no serviço em falta, impedindo-o de afetar outras partes do sistema.
- Tempo: Sempre defina um tempo limite em chamadas a jusante – mais curto do que a janela de falha do disjuntor. Isto impede que os pedidos de suspensão longa de distorcer a contagem de falhas.
- Endpoints de verificação de saúde:] Use um processo de fundo (por exemplo, um evento agendado do CloudWatch) para invocar periodicamente um endpoint de verificação de saúde. Se o exame de saúde falhar, abra o circuito antes que os usuários sejam afetados.
Teste seu disjuntor em falha
A engenharia do caos é seu amigo. Use ferramentas como o Simulador de Injeção de Falha (FIS) para injetar falhas em seus serviços a jusante e observar o comportamento do disjuntor. Verifique isso:
- O circuito abre dentro do prazo esperado.
- Os retrocessos executam corretamente.
- O circuito recupera (semeia-aberta e fechada) após a falha ser resolvida.
- Nenhum falso positivo ocorre sob picos de carga normais.
Testes em um ambiente de estadiamento que espelha a produção é essencial. Documente o comportamento esperado e execute brocas regularmente.
Usar uma Loja de Estado Externo com TTL
Em servidor sem, você não pode confiar na memória local através de invocações. Use o DynamoDB, o ElastiCache para o Redis ou um serviço feito como o Eureka. Defina um Tempo- ao- Vivo (TTL) no registo de estado para que, se a sua função estiver inactiva durante um longo período, o circuito reinicia automaticamente para fechar. Isto impede que um estado aberto obsoleto bloqueie o tráfego após a recuperação de um serviço.
Conclusão
Como o servidor se torna a espinha dorsal de aplicações modernas, padrões de resiliência como o Disjuntor não são mais opcionais – eles são essenciais para o controle de custos, tempo de funcionamento e satisfação do usuário. Ao entender a máquina de estado, implementando-a corretamente dentro das restrições de sua plataforma sem servidor (API Gateway, código de função ou Funções de Passo), e seguindo as melhores práticas para monitoramento e teste, você pode construir sistemas que graciosamente degradam sob falha e se recuperam sem intervenção manual.
O padrão disjuntor é apenas uma peça do quebra-cabeça resiliência. Combine-o com repetições, anteparas, verificações de saúde e observação abrangente para criar arquiteturas verdadeiramente robustas sem servidores. Comece pequeno, monitore de perto e iterate.