BLOG | NGINX

MRA, Parte 6: Implementando o padrão Circuit Breaker com NGINX Plus

NGINX-Parte-de-F5-horiz-preto-tipo-RGB
Miniatura de Chris Stetson
Chris Stetson
Publicado em 10 de novembro de 2016

Nota do autorEsta postagem do blog é a sexta de uma série:

  1. Apresentando a Arquitetura de Referência de Microsserviços da NGINX
  2. MRA, Parte 2: O modelo proxy
  3. MRA, Parte 3: O modelo de malha do roteador
  4. MRA, Parte 4: O modelo de tecido
  5. MRA, Parte 5: Adaptando o aplicativo Twelve‑Factor para microsserviços
  6. MRA, Parte 6: Implementando o padrão Circuit Breaker com NGINX Plus (este post)

Todos os seis blogs, além de um blog sobre frontends web para aplicativos de microsserviços<.htmla>, foram reunidos em um e-book gratuito .

Confira também estes outros recursos do NGINX sobre microsserviços:

 

O design de aplicativos de microsserviços causou uma mudança completa na maneira como os aplicativos funcionam. Em uma arquitetura de microsserviços, um “aplicativo” é agora uma coleção de serviços que dependem uns dos outros para executar tarefas e fornecer funcionalidade. Em aplicações complexas, o gráfico de serviços pode ser bastante profundo e ter múltiplas interdependências entre os vários serviços.

Por exemplo, um serviço de usuário pode ser parte integrante de muitos outros serviços que dependem dos dados fornecidos pelo serviço. Nesse cenário, uma falha no serviço do usuário pode causar uma cascata de falhas em todo o aplicativo.

O padrão Circuit Breaker – um termo popularizado por Martin Fowler – vem ganhando força entre arquitetos de microsserviços como um padrão de design de aplicativo para evitar falhas de serviço em cascata. A ideia do padrão Circuit Breaker é monitorar seus serviços de aplicativo e o tráfego que flui entre eles para evitar falhas e, quando elas ocorrerem, minimizar o impacto dessas falhas em seus aplicativos.

Para microsserviços, o padrão Circuit Breaker é especialmente valioso, fornecendo resiliência de baixo para cima. Se implementado corretamente, ele pode ajudar a evitar falhas em cascata, fornecendo continuidade de serviço mesmo quando os serviços não estão disponíveis. O padrão Circuit Breaker foi adotado pela Netflix como um componente crítico em sua filosofia de design de aplicativos.

Não evite o fracasso, abrace-o

Um princípio fundamental do design de aplicativos modernos é que falhas ocorrerão. O bolo em camadas do qual os aplicativos modernos dependem – de máquinas virtuais hospedadas na nuvem a contêineres, bibliotecas de aplicativos e redes dinâmicas – significa que as partes móveis em qualquer aplicativo são inúmeras. Você precisa presumir que uma ou mais partes do seu aplicativo falharão de alguma forma em algum momento. Esperar falhas e criar mecanismos para mitigar seus efeitos contribui muito para tornar seu aplicativo mais resiliente.

Um dos objetivos mais críticos do padrão do disjuntor é tentar evitar falhas em primeiro lugar. Para alguns tipos de condições de erro, como falta de memória, é possível reconhecer que a falha é iminente e tomar medidas para evitá-la. Isso normalmente é feito pelo serviço sinalizando que ele não está íntegro, e o disjuntor então dá ao serviço uma chance de se recuperar, reduzindo o número de solicitações ou redirecionando-as completamente. Após a recuperação do serviço, também é prudente que o disjuntor aumente lentamente as solicitações ao serviço para não sobrecarregá-lo imediatamente e correr o risco de ele ficar com problemas novamente.

Na Arquitetura de Referência de Microsserviços NGINX , temos um serviço chamado resizer . Quando uma foto grande é carregada no sistema, o redimensionador a descompacta, corrige sua rotação, a encolhe e depois a encolhe novamente, salvando a imagem original corrigida e as duas imagens redimensionadas em um armazenamento de objetos. A natureza desses processos faz com que o redimensionador seja a parte do aplicativo que mais exige processamento e memória.

Quando muitas imagens estão sendo redimensionadas simultaneamente, o redimensionador pode ficar sem memória e, em alguns cenários, falhar completamente. Para evitar problemas, colocamos um disjuntor entre as instâncias do serviço de redimensionamento e o serviço de upload que envia imagens para elas. O carregador consulta regularmente as instâncias do redimensionador para saber seu status de integridade. A consulta aciona o redimensionador para avaliar se ele usou mais de 80% da memória disponível, entre outras verificações de integridade, e responde ao carregador com seu status de integridade.

Se uma instância do redimensionador indicar que não está íntegra, o carregador encaminhará as solicitações para outras instâncias, conforme mostrado na Figura 1, mas continuará verificando se a instância do redimensionador foi recuperada. Quando a instância do redimensionador indica que está saudável novamente, ela é colocada de volta no pool com balanceamento de carga e o carregador aumenta lentamente o tráfego até a capacidade total da instância. Esse design evita que instâncias do redimensionador falhem completamente, impede que o trabalho seja iniciado, mas não concluído, evita espera excessiva de usuários cujos processos falhariam e ajuda o sistema a lidar de forma mais eficaz com o fluxo de solicitações enviado a ele.

O padrão do disjuntor corta o tráfego para instâncias não íntegras. Um disjuntor e o NGINX funcionam bem juntos.
Figura 1. Verificações de integridade ativas evitam chamadas para uma instância de microsserviço não íntegra

O padrão do disjuntor melhora a consistência

Um dos benefícios de implementar o disjuntor no nível do NGINX é que ele cria uma camada universal, consistente e altamente flexível para gerenciar disjuntores em seu aplicativo de microsserviços. Essa universalidade e consistência significa que você não precisa gerenciar e construir em torno das nuances e inconsistências das bibliotecas de disjuntores para cada linguagem.

Você ganha muitas vantagens mantendo a maior parte da funcionalidade do disjuntor fora do código de cada serviço e implementando-a no NGINX Plus:

  • O disjuntor para um serviço escrito em, por exemplo, Java, é o mesmo que para um serviço escrito em PHP – e o próprio disjuntor pode ser escrito em outra linguagem, conforme necessário
  • Você evita ter que reimplementar a funcionalidade do disjuntor em toda a mistura de linguagens e bibliotecas de suporte usadas por cada um dos seus serviços
  • Cada serviço que não precisa incluir o código do disjuntor é simplificado; ele roda mais rápido e é mais fácil de escrever, depurar, executar e manter
  • O código de suporte para cada serviço é simplificado; a combinação de bibliotecas e sistemas usados pode refletir apenas a funcionalidade principal do serviço
  • O código do disjuntor é simplificado; existindo apenas em um lugar, ele pode ser reduzido ao essencial, sem a necessidade de acomodar contextos locais
  • O código do disjuntor pode aproveitar os recursos do NGINX Plus, como cache, tornando-o muito mais poderoso
  • Você pode ajustar seu código de disjuntor de nível NGINX Plus e reutilizá-lo em outros aplicativos e em plataformas de implantação, como no local, em diferentes plataformas de nuvem e em ambientes combinados.

É importante observar, no entanto, que os disjuntores não podem ser implementados apenas no NGINX Plus. Um verdadeiro disjuntor exige que o serviço forneça uma verificação de integridade introspectiva e ativa em um URI designado (normalmente /health ). O exame de saúde deve ser adequado às necessidades daquele serviço específico.

Ao desenvolver a verificação de integridade, você precisa entender o perfil de falha do serviço e os tipos de condições que podem causar falhas, como uma falha de conexão com o banco de dados, uma condição de falta de memória, falta de espaço em disco ou uma CPU sobrecarregada. Essas condições são avaliadas no processo de verificação de saúde, que então fornece um status binário de saudável ou não saudável.

O padrão do disjuntor fornece flexibilidade

Ao implementar o padrão de disjuntor no nível do NGINX, conforme descrito aqui, cabe ao NGINX Plus lidar com a situação em que uma instância de serviço comunica que não está íntegra. Há várias opções.

A primeira opção é redirecionar solicitações para outras instâncias saudáveis e continuar consultando a instância não saudável para ver se ela se recupera. A segunda opção é fornecer respostas em cache aos clientes que solicitam o serviço, mantendo a estabilidade mesmo se o serviço estiver indisponível. Esta solução funciona bem com serviços orientados à leitura, como um serviço de conteúdo.

Outra opção é fornecer fontes de dados alternativas. Por exemplo, um cliente nosso tem um servidor de anúncios personalizado que usa dados de perfil para veicular anúncios direcionados para seus usuários. Se o servidor de anúncios personalizados estiver inativo, a solicitação do usuário será redirecionada para um servidor de backup que fornece um conjunto genérico de anúncios apropriados para todos. Essa abordagem de fonte de dados alternativa pode ser bastante poderosa.

Por fim, se você tiver uma compreensão muito clara do perfil de falha de um serviço, poderá mitigar a falha adicionando limitação de taxa ao disjuntor. As solicitações são permitidas no serviço somente na taxa que ele pode processar. Isso cria um buffer dentro do disjuntor para que ele possa absorver picos de tráfego.

A limitação de taxa pode ser particularmente poderosa em um cenário de balanceamento de carga centralizado, como o Modelo de Malha de Roteador , onde o tráfego do aplicativo é roteado por meio de um número limitado de balanceadores de carga que podem ter uma boa compreensão do uso total do tráfego no site.

Implementando o padrão Circuit Breaker no NGINX Plus

Como descrevemos acima, o padrão do disjuntor pode evitar falhas antes que elas aconteçam, reduzindo o tráfego para um serviço não íntegro ou encaminhando solicitações para longe dele. Isso requer uma verificação de saúde ativa conectada a um monitor de saúde introspectivo em cada serviço. Infelizmente, uma verificação de saúde passiva não resolve o problema, pois apenas verifica falhas – momento em que já é tarde demais para tomar medidas preventivas. É por esse motivo que o NGINX Open Source não pode implementar o padrão de disjuntor – ele suporta apenas verificações de integridade passivas.

O NGINX Plus, no entanto, tem um sistema de verificação de integridade ativo e robusto com muitas opções para verificar e responder a problemas de saúde. Analisar a implementação de alguns tipos de serviço para a Arquitetura de Referência de Microsserviços fornece bons exemplos de opções e casos de uso para implementar o disjuntor.

Vamos começar com o serviço de upload que se conecta ao redimensionador. O carregador coloca as imagens em um armazenamento de objetos e então informa ao redimensionador para abrir uma imagem, corrigi-la e redimensioná-la. Esta é uma operação que exige muita computação e memória. O carregador precisa monitorar a integridade do redimensionador e evitar sobrecarregá-lo, pois o redimensionador pode literalmente matar o host no qual está sendo executado.

A primeira coisa a fazer é criar um bloco de localização específico para a verificação de integridade do redimensionador. Este bloco é um local interno , o que significa que não pode ser acessado com uma solicitação à URL padrão do servidor ( http://example.com/health-check-resizer ). Em vez disso, ele atua como um espaço reservado para as informações de verificação de integridade. A diretiva health_check envia uma verificação de integridade para o URI /health a cada três segundos e usa os testes definidos no bloco de correspondência chamado conditions para verificar a integridade da instância do serviço. Uma instância de serviço é marcada como não íntegra quando perde uma única verificação. As diretivas proxy_* enviam a verificação de integridade para o grupo upstream do redimensionador , usando TLS 1.2 sobre HTTP 1.1 com os cabeçalhos HTTP indicados definidos como nulos.

localização /health-check-resizer { interno;
health_check uri=/health match=conditions falhas=1 intervalo=3s;

proxy_pass https://resizer;
proxy_ssl_session_reuse ativado;
proxy_ssl_protocols TLSv1.2;
proxy_http_version 1.1;
proxy_set_header Conexão "";
proxy_set_header Aceitar-Codificação "";
}

O próximo passo é criar o bloco de correspondência de condições para especificar as respostas que representam condições saudáveis e não saudáveis. A primeira verificação é do código de status da resposta: se ele está no intervalo de200 através399 , o teste prossegue para a próxima declaração de avaliação. A segunda verificação é se o Content-Type é application/json . Por fim, a terceira verificação é uma correspondência de expressão regular com o valor das métricas deadlocks , Disk e Memory . Se todos estiverem saudáveis, então o serviço é considerado saudável.

condições de correspondência { status 200-399;
cabeçalho Content-Type ~ "application/json";
corpo ~ '{
"deadlocks":{"healthy":true},
"Disco":{"healthy":true},
"Memória":{"healthy":true}
}';
}

O sistema de disjuntor/verificação de integridade do NGINX Plus também tem um recurso de inicialização lenta. O parâmetro slow_start para a diretiva de servidor para o serviço de redimensionamento no bloco upstream informa ao NGINX Plus para moderar o fluxo de tráfego quando uma instância de redimensionamento retorna pela primeira vez de um estado não íntegro. Em vez de apenas sobrecarregar o serviço com o mesmo número de solicitações enviadas para serviços saudáveis, o tráfego para o serviço em recuperação é lentamente aumentado para a taxa normal durante o período indicado pelo parâmetro slow_start – neste caso, 30 segundos. O início lento aumenta as chances de o serviço retornar à capacidade total, ao mesmo tempo que reduz o impacto caso isso não aconteça.

redimensionador upstream { redimensionador de servidor slow_start=30s;
zona backend 64k;
least_time last_byte;
keepalive 300;
}

A limitação de solicitações gerencia e modera o fluxo de solicitações ao serviço. Se você entender o perfil de falhas do aplicativo bem o suficiente para saber o número de solicitações que ele pode manipular a qualquer momento, implementar a limitação de solicitações pode ser uma verdadeira vantagem para o processo. No entanto, esse recurso só funciona se o NGINX Plus tiver total conhecimento do número total de conexões que estão sendo passadas para o serviço. Por isso, é mais útil implementar o disjuntor de limitação de solicitações em uma instância do NGINX Plus em execução em um contêiner com o próprio serviço, como no Fabric Model, ou em um balanceador de carga centralizado que tem a tarefa de gerenciar todo o tráfego em um cluster.

O seguinte trecho de código de configuração define um limite de taxa em solicitações a serem aplicadas às instâncias do serviço de redimensionamento em seus contêineres. A diretiva limit_req_zone define o limite de taxa em 100 solicitações por segundo. A variável $server_addr é usada como chave, o que significa que todas as solicitações no contêiner do redimensionador são contadas em relação ao limite. O nome da zona é moderateReqs e o período para manter a contagem de solicitações é de 1 minuto. A diretiva limit_req permite que o NGINX Plus armazene em buffer rajadas de até 150 solicitações. Quando esse número for excedido, os clientes recebem o503 código de erro conforme especificado pela diretiva limit_req_status , indicando que o serviço não está disponível.

http { # Entrega moderada
limit_req_zone $server_addr zone=moderateReqs:1m rate=100r/s;
# ...
server {
# ...
limit_req zone=moderateReqs burst=150;
limit_req_status 503;
# ...
}
}

Outro benefício poderoso de executar o disjuntor no NGINX Plus é a capacidade de incorporar o cache e manter os dados armazenados em cache centralmente, para uso em todo o sistema. Isso é particularmente valioso para serviços orientados à leitura, como servidores de conteúdo, onde os dados lidos do backend não mudam com frequência.

proxy_cache_path /app/cache levels=1:2 keys_zone=oauth_cache:10m max_size=10m inactive=15s use_temp_path=off;
upstream user-manager {
server user-manager;
zone backend 64k;
least_time last_byte;
keepalive 300;
}

server {
listen 443 ssl;
location /v1/users {
proxy_pass http://user-manager;
proxy_cache oauth_cache;
proxy_cache_valid 200 30s;
proxy_cache_use_stale erro timeout invalid_header atualizando
http_500 http_502 http_503 http_504;
}
}

Conforme mostrado na Figura 2, o armazenamento em cache de dados significa que muitas solicitações de dados do cliente nunca chegam às instâncias de microsserviço, liberando capacidade para solicitações que não foram recebidas anteriormente.

O NGINX Plus, atuando como um disjuntor de microsserviços, também oferece suporte ao cache.
Figura 2. Embora o cache seja geralmente usado para acelerar o desempenho, evitando chamadas para instâncias de microsserviços, ele também serve para fornecer continuidade de serviço em caso de falha completa do serviço.

Entretanto, com um serviço onde os dados podem mudar, por exemplo, um serviço de gerenciamento de usuários, um cache precisa ser gerenciado criteriosamente. Caso contrário, você pode acabar com um cenário em que um usuário faz uma alteração em seu perfil, mas vê dados antigos em alguns contextos porque os dados estão armazenados em cache. Um tempo limite razoável e aceitar o princípio de alta disponibilidade com consistência eventual podem resolver esse enigma.

Um dos recursos interessantes do cache NGINX é que ele pode continuar servindo dados em cache mesmo se o serviço estiver completamente indisponível – no snippet acima, se o serviço estiver respondendo com um dos quatro mais comuns500 códigos de erro da série ‑.

O cache não é a única opção para responder aos clientes, mesmo que um servidor esteja inativo. Como mencionamos em O padrão do disjuntor fornece flexibilidade , um de nossos clientes precisava de uma solução resiliente caso seu servidor de anúncios personalizados caísse e as respostas em cache não fossem uma boa solução. Em vez disso, eles queriam um servidor de anúncios genérico para fornecer anúncios generalizados até que o servidor personalizado voltasse a ficar online. Isso é facilmente alcançado usando o parâmetro de backup na diretiva do servidor . O snippet a seguir especifica que, quando todos os servidores definidos para o domínio personal-ad-server estiverem indisponíveis, os servidores definidos para o domínio generic-ad-server serão usados.

upstream personal-ad-server { servidor personal-ad-server;
servidor generic-ad-server backup;
zona backend 64k;
least_time last_byte;
keepalive 300;
}

E, finalmente, é possível que o NGINX avalie os códigos de resposta de um serviço e lide com eles individualmente. No snippet a seguir, se um serviço retornar um503 erro, o NGINX Plus envia a solicitação para um serviço alternativo. Por exemplo, se o redimensionador tiver esse recurso e a instância local estiver sobrecarregada ou parar de funcionar, as solicitações serão enviadas para outra instância do redimensionador.

localização / { página_de_erro 503 = @fallback;
}

localização @fallback {
proxy_pass http://alternative-backend;
}

Conclusão

O padrão de disjuntor é uma ferramenta poderosa para fornecer resiliência e controle em seu aplicativo de microsserviços. O NGINX Plus oferece muitos recursos e opções para implementar o disjuntor em seu ambiente. A chave para implementar o padrão do disjuntor é entender o perfil de falha do serviço que você está protegendo e, então, escolher as opções que melhor previnem falhas, sempre que possível, e que melhor mitigam os efeitos da falha quando ela acontece.

Para experimentar o NGINX Plus, comece hoje mesmo seu teste gratuito de 30 dias ou entre em contato conosco para discutir seus casos de uso.


"Esta postagem do blog pode fazer referência a produtos que não estão mais disponíveis e/ou não têm mais suporte. Para obter as informações mais atualizadas sobre os produtos e soluções F5 NGINX disponíveis, explore nossa família de produtos NGINX . O NGINX agora faz parte do F5. Todos os links anteriores do NGINX.com redirecionarão para conteúdo semelhante do NGINX no F5.com."