Por Cooper Bethea, Gráinne Sheerin, Jennifer Mace e Ruth King
com Gary Luo e Gary O’Connor
Nenhum serviço está disponível 100% do tempo: os clientes podem ser inconvenientes, a demanda pode crescer cinquenta vezes, um serviço pode falhar em resposta a um pico de tráfego, ou uma âncora pode puxar um cabo transatlântico. Existem pessoas que dependem do seu serviço, e como proprietários de serviço, nos importamos com nossos usuários. Quando confrontados com essas cadeias de gatilhos de interrupção, como podemos tornar nossa infraestrutura o mais adaptável e confiável possível?
Este capítulo descreve a abordagem do Google para o gerenciamento de tráfego com a esperança de que você possa utilizar essas melhores práticas para melhorar a eficiência, confiabilidade e disponibilidade de seus serviços. Ao longo dos anos, descobrimos que não há uma solução única para equalizar e estabilizar a carga da rede. Em vez disso, usamos uma combinação de ferramentas, tecnologias e estratégias que trabalham em harmonia para ajudar a manter nossos serviços confiáveis.
Antes de mergulharmos neste capítulo, recomendamos a leitura das filosofias discutidas nos Capítulos 19 (“Balanceamento de carga no frontend“) e 20 (“Balanceamento de Carga no Datacenter“) do nosso primeiro livro de SRE.
Balanceamento de Carga do Google Cloud
Atualmente, a maioria das empresas não desenvolve e mantém suas próprias soluções de balanceamento de carga global, optando, em vez disso, por utilizar serviços de balanceamento de carga de um provedor de nuvem pública maior. Discutiremos o Balanceador de Carga do Google Cloud (GCLB) como um exemplo concreto de balanceamento de carga em grande escala, mas quase todas as melhores práticas que descrevemos também se aplicam aos balanceadores de carga de outros provedores de nuvem.
O Google passou os últimos 18 anos construindo infraestrutura para tornar nossos serviços rápidos e confiáveis. Hoje, usamos esses sistemas para fornecer YouTube, Maps, Gmail, Search e muitos outros produtos e serviços. O GCLB é nossa solução de balanceamento de carga global de consumo público e é a externalização de um de nossos sistemas de balanceamento de carga global desenvolvidos internamente.
Esta seção descreve os componentes do GCLB e como eles trabalham juntos para atender às solicitações do usuário. Traçamos uma solicitação típica do usuário, desde sua criação até a entrega em seu destino. O estudo de caso do Niantic Pokémon GO fornece uma implementação concreta do GCLB no mundo real.
O Capítulo 19 do nosso primeiro livro de SRE descreveu como o balanceamento de carga baseado em DNS é a maneira mais simples e eficaz de equilibrar a carga antes mesmo que a conexão do usuário comece. Também discutimos um problema endêmico dessa abordagem: ela depende da cooperação do cliente para expirar e buscar corretamente os registros DNS novamente. Por esse motivo, o GCLB não utiliza o balanceamento de carga baseado em DNS.
Em vez disso, utilizamos anycast, um método para enviar clientes para o cluster mais próximo sem depender da geolocalização do DNS. O balanceador de carga global do Google sabe onde os clientes estão localizados e direciona os pacotes para o serviço web mais próximo, proporcionando baixa latência aos usuários ao utilizar um único IP virtual (VIP). O uso de um único VIP significa que podemos aumentar o tempo de vida (TTL) de nossos registros DNS, o que reduz ainda mais a latência.
Anycast
Anycast é uma metodologia de endereçamento e roteamento de rede. Ele roteia datagramas de um único remetente para o nó topologicamente mais próximo em um grupo de receptores potenciais, todos identificados pelo mesmo endereço IP de destino. O Google anuncia IPs via Protocolo de Gateway de Borda (BGP) a partir de múltiplos pontos em nossa rede. Dependemos da malha de roteamento do BGP para enviar pacotes de um usuário para o local de frontend mais próximo que pode encerrar uma sessão de protocolo de controle de transmissão (TCP). Esta implantação elimina os problemas de proliferação de IPs unicast e de encontrar o frontend mais próximo para um usuário. Dois problemas principais permanecem:
- Muitos usuários próximos podem sobrecarregar um site de frontend.
- O cálculo de rota do BGP pode reiniciar conexões.
Considere um provedor de serviços de Internet (ISP) que frequentemente recalcula suas rotas BGP de modo que um de seus usuários prefere um dos dois sites de frontend. Cada vez que a rota BGP “flutua”, todos os fluxos TCP em andamento são reiniciados, pois os pacotes do usuário infeliz são direcionados para um novo frontend sem estado de sessão TCP. Para enfrentar esses problemas, aproveitamos nosso balanceador de carga em nível de conexão, Maglev (descrito em breve), para manter coesos os fluxos TCP, mesmo quando as rotas flutuam. Referimo-nos a essa técnica como anycast estabilizado.
Anycast Estabilizado
Como mostrado na Figura 11-1, o Google implementa o anycast estabilizado usando o Maglev, nosso balanceador de carga personalizado. Para estabilizar o anycast, fornecemos a cada máquina Maglev um meio de mapear IPs de clientes para o site de frontend do Google mais próximo. Às vezes, o Maglev processa um pacote destinado a um VIP anycast para um cliente que está mais próximo de outro site de frontend. Nesse caso, o Maglev encaminha esse pacote para outro Maglev em uma máquina localizada no site de frontend mais próximo para entrega. As máquinas Maglev no site de frontend mais próximo então tratam o pacote simplesmente como tratariam qualquer outro pacote e o encaminham para um backend local.
Figura 11-1. Anycast Estabilizado
Maglev
O Maglev, mostrado na Figura 11-2, é o balanceador de carga distribuído personalizado do Google a nível de pacotes. Uma parte integral de nossa arquitetura de nuvem, as máquinas Maglev gerenciam o tráfego de entrada para um cluster. Elas fornecem balanceamento de carga em nível TCP com estado em nossos servidores de frontend. O Maglev difere de outros balanceadores de carga de hardware tradicionais de algumas maneiras-chave:
- Todos os pacotes destinados a um determinado endereço IP podem ser distribuídos uniformemente por um pool de máquinas Maglev através do encaminhamento de Multi-Caminho de Custo Igual (ECMP). Isso nos permite aumentar a capacidade do Maglev simplesmente adicionando servidores a um pool. Distribuir os pacotes uniformemente também permite que a redundância do Maglev seja modelada como N + 1, melhorando a disponibilidade e confiabilidade em relação aos sistemas de balanceamento de carga tradicionais (que normalmente dependem de pares ativo/passivo para fornecer redundância 1 + 1).
- O Maglev é uma solução personalizada do Google. Controlamos o sistema de ponta a ponta, o que nos permite experimentar e iterar rapidamente.
- O Maglev é executado em hardware comum em nossos data centers, o que simplifica bastante a implantação.
Figura 11-2. Maglev
A entrega de pacotes do Maglev utiliza hash consistente e rastreamento de conexão. Essas técnicas unem os fluxos TCP em nossos proxies reversos HTTP (também conhecidos como Google Front Ends, ou GFEs), que encerram as sessões TCP. O hash consistente e o rastreamento de conexão são fundamentais para a capacidade do Maglev de escalar por pacote em vez de por número de conexões. Quando um roteador recebe um pacote destinado a um VIP hospedado pelo Maglev, o roteador encaminha o pacote para qualquer máquina Maglev no cluster por meio do ECMP. Quando um Maglev recebe um pacote, ele calcula o hash de 5-tuplas do pacote e procura o valor de hash em sua tabela de rastreamento de conexão, que contém os resultados de roteamento para conexões recentes. Se o Maglev encontrar uma correspondência e o serviço de backend selecionado ainda estiver saudável, ele reutiliza a conexão. Caso contrário, o Maglev recorre ao hash consistente para escolher um backend. A combinação dessas técnicas elimina a necessidade de compartilhar o estado da conexão entre as máquinas Maglev individuais.
Balanceador de Carga de Software Global (GSLB)
GSLB é o Balanceador de Carga de Software Global do Google. Ele nos permite equilibrar o tráfego de usuários em tempo real entre clusters para que possamos corresponder à demanda dos usuários à capacidade de serviço disponível e lidar com falhas de serviço de uma forma que seja transparente para os usuários. Como mostrado na Figura 11-3, o GSLB controla tanto a distribuição de conexões para GFEs quanto a distribuição de solicitações para serviços de backend. O GSLB nos permite atender usuários a partir de backends e GFEs em execução em diferentes clusters. Além de balancear a carga entre front-ends e back-ends, o GSLB entende a saúde dos serviços de backend e pode automaticamente desviar o tráfego de clusters com falha.
Figura 11-3. GSLB
Google Front End
Como mostrado na Figura 11-4, o GFE fica entre o mundo exterior e vários serviços do Google (pesquisa na web, pesquisa de imagens, Gmail, etc.) e é frequentemente o primeiro servidor do Google que uma solicitação HTTP(S) do cliente encontra. O GFE encerra as sessões TCP e SSL do cliente e inspeciona o cabeçalho HTTP e o caminho do URL para determinar qual serviço de backend deve lidar com a solicitação. Uma vez que o GFE decide para onde enviar a solicitação, ele reencripta os dados e encaminha a solicitação. Para obter mais informações sobre como funciona esse processo de criptografia, consulte nosso whitepaper “Criptografia em Trânsito no Google Cloud”.
O GFE também é responsável por verificar a saúde de seus backends. Se um servidor de backend retornar um reconhecimento negativo (“NACK” a solicitação) ou não responder dentro do tempo limite das verificações de saúde, os GFEs param de enviar tráfego para o backend com falha. Usamos esse sinal para atualizar os backends do GFE sem impactar a disponibilidade. Ao colocar os backends do GFE em um modo no qual falham nas verificações de saúde enquanto continuam a responder às solicitações em andamento, podemos remover os backends do GFE do serviço de forma suave sem interromper quaisquer solicitações de usuário. Chamamos isso de modo “pato manco” e discutimos em mais detalhes no Capítulo 20 do primeiro livro de SRE.
Figura 11-4. GFE
Os GFEs também mantêm sessões persistentes com todos os seus backends recentemente ativos para que uma conexão esteja pronta para uso assim que uma solicitação chegar. Essa estratégia ajuda a reduzir a latência para nossos usuários, especialmente em cenários em que usamos SSL para proteger a conexão entre o GFE e o backend.
GCLB: Baixa Latência
Nossa estratégia de provisionamento de rede visa reduzir a latência do usuário final para nossos serviços. Como a negociação de uma conexão segura via HTTPS requer duas idas e voltas na rede entre cliente e servidor, é particularmente importante que minimizemos a latência desta parte do tempo de solicitação. Para isso, estendemos a borda de nossa rede para hospedar o Maglev e o GFE. Esses componentes encerram o SSL o mais próximo possível do usuário, e em seguida encaminham as solicitações para os serviços de backend mais internos em nossa rede por meio de conexões criptografadas de longa duração.
Construímos o GCLB sobre essa rede de borda combinada, aprimorada com Maglev/GFE. Quando os clientes criam um balanceador de carga, provisionamos um VIP anycast e programamos o Maglev para balanceá-lo globalmente entre os GFEs na borda de nossa rede. O papel do GFE é encerrar o SSL, aceitar e armazenar em buffer as solicitações HTTP, encaminhar essas solicitações para os serviços de backend do cliente e, em seguida, encaminhar as respostas de volta aos usuários. O GSLB fornece a ligação entre cada camada: ele permite que o Maglev encontre o local do GFE mais próximo com capacidade disponível e permite que o GFE encaminhe as solicitações para o grupo de instâncias de VM mais próximo com capacidade disponível.
GCLB: Alta Disponibilidade
Visando fornecer alta disponibilidade aos nossos clientes, o GCLB oferece um SLA de disponibilidade de 99,99%. Além disso, o GCLB fornece ferramentas de suporte que permitem aos nossos clientes melhorar e gerenciar a disponibilidade de suas próprias aplicações. É útil pensar no sistema de balanceamento de carga como uma espécie de gerenciador de tráfego. Durante a operação normal, o GCLB roteia o tráfego para o backend mais próximo com capacidade disponível. Quando uma instância do seu serviço falha, o GCLB detecta a falha em seu nome e roteia o tráfego para instâncias saudáveis.
O uso de canários e implementações graduais ajuda o GCLB a manter alta disponibilidade. O canário é um dos nossos procedimentos padrão de lançamento. Como descrito no Capítulo 16, esse processo envolve implantar uma nova aplicação em um número muito pequeno de servidores e, em seguida, aumentar gradualmente o tráfego e observar cuidadosamente o comportamento do sistema para verificar se não há regressões. Essa prática reduz o impacto de quaisquer regressões ao detectá-las precocemente na fase do canário. Se a nova versão falhar ou não passar nos testes de integridade, o balanceador de carga a contorna. Se você detectar uma regressão não fatal, poderá remover administrativamente o grupo de instâncias do balanceador de carga sem afetar a versão principal da aplicação.
Estudo de Caso 1: Pokémon GO no GCLB
A Niantic lançou o Pokémon GO no verão de 2016. Foi o primeiro novo jogo Pokémon em anos, o primeiro jogo oficial de Pokémon para smartphones e o primeiro projeto da Niantic em conjunto com uma grande empresa de entretenimento. O jogo foi um sucesso estrondoso e mais popular do que qualquer um esperava – naquele verão, você regularmente via jogadores se reunindo para duelar em torno de marcos que eram Ginásios Pokémon no mundo virtual.
O sucesso do Pokémon GO excedeu grandemente as expectativas da equipe de engenharia da Niantic. Antes do lançamento, eles testaram a carga de sua pilha de software para processar até 5 vezes suas estimativas de tráfego mais otimistas. A taxa real de solicitações por segundo (RPS) no lançamento foi quase 50 vezes maior do que essa estimativa – o suficiente para representar um desafio de escalabilidade para quase qualquer pilha de software. Para complicar ainda mais o assunto, o mundo do Pokémon GO é altamente interativo e compartilhado globalmente entre seus usuários. Todos os jogadores em uma área específica veem a mesma visão do mundo do jogo e interagem uns com os outros dentro desse mundo. Isso requer que o jogo produza e distribua atualizações quase em tempo real para um estado compartilhado por todos os participantes.
Dimensionar o jogo para 50 vezes mais usuários exigiu um esforço verdadeiramente impressionante da equipe de engenharia da Niantic. Além disso, muitos engenheiros em toda a Google ofereceram sua assistência para dimensionar o serviço para um lançamento bem-sucedido. Dentro de dois dias após migrar para o GCLB, o aplicativo Pokémon GO tornou-se o maior serviço do GCLB, facilmente em pé de igualdade com os outros 10 principais serviços do GCLB.
Como mostrado na Figura 11-5, quando foi lançado, o Pokémon GO usava o Balanceador de Carga de Rede (NLB) regional da Google para balancear o tráfego de entrada em um cluster Kubernetes. Cada cluster continha pods de instâncias do Nginx, que serviam como proxies reversos da Camada 7 que encerravam o SSL, armazenavam em buffer as solicitações HTTP e realizavam o roteamento e balanceamento de carga entre os pods dos backends do servidor de aplicativos.
Figura 11-5. Pokémon GO (antes do GCLB)
O NLB é responsável pelo balanceamento de carga na camada de IP, então um serviço que usa NLB efetivamente se torna um backend do Maglev. Nesse caso, depender do NLB teve as seguintes implicações para a Niantic:
- Os backends do Nginx eram responsáveis por encerrar o SSL para os clientes, o que exigia duas idas e voltas de um dispositivo cliente para os proxies de frontend da Niantic.
- A necessidade de armazenar em buffer as solicitações HTTP dos clientes levou à exaustão de recursos na camada de proxy, especialmente quando os clientes só podiam enviar bytes lentamente.
- Ataques de rede de baixo nível, como SYN flood, não puderam ser efetivamente mitigados por um proxy de nível de pacote.
Para escalar adequadamente, a Niantic precisava de um proxy de alto nível operando em uma grande rede de borda. Essa solução não era possível com o NLB.
Migrar para o GCLB
Um grande ataque de SYN flood tornou a migração do Pokémon GO para o GCLB uma prioridade. Esta migração foi um esforço conjunto entre a Niantic e as equipes de Engenharia de Confiabilidade do Cliente (CRE) e SRE da Google. A transição inicial ocorreu durante uma diminuição do tráfego e, na época, não foi marcante. No entanto, surgiram problemas imprevistos tanto para a Niantic quanto para a Google conforme o tráfego aumentava para o pico. Tanto a Google quanto a Niantic descobriram que a verdadeira demanda dos clientes por tráfego do Pokémon GO era 200% maior do que o observado anteriormente. Os proxies de frontend da Niantic receberam tantas solicitações que não conseguiram acompanhar todas as conexões de entrada. Qualquer conexão recusada dessa forma não foi detectada no monitoramento das solicitações de entrada. Os backends nunca tiveram uma chance.
Essa onda de tráfego causou um cenário clássico de falha em cascata. Numerosos serviços de suporte para a API – Cloud Datastore, backends e servidores de API do Pokémon GO e o próprio sistema de balanceamento de carga – excederam a capacidade disponível para o projeto de nuvem da Niantic. A sobrecarga fez com que os backends da Niantic ficassem extremamente lentos (em vez de recusar solicitações), manifestando-se como solicitações expirando para a camada de balanceamento de carga. Nessas circunstâncias, o balanceador de carga tentava novamente as solicitações GET, aumentando a carga do sistema. A combinação do volume extremamente alto de solicitações e das tentativas adicionais estressou o código do cliente SSL no GFE a um nível sem precedentes, enquanto tentava reconectar aos backends não responsivos. Isso induziu uma regressão de desempenho severa no GFE, de forma que a capacidade mundial do GCLB foi efetivamente reduzida em 50%.
Conforme os backends falhavam, o aplicativo Pokémon GO tentava novamente as solicitações falhadas em nome dos usuários. Na época, a estratégia de tentativas novamente do aplicativo era uma única tentativa imediata, seguida por um retrocesso constante. Conforme a interrupção continuava, o serviço às vezes retornava um grande número de erros rápidos – por exemplo, quando um backend compartilhado era reiniciado. Essas respostas de erro serviam para sincronizar efetivamente as tentativas de novo dos clientes, produzindo um problema de “manada de trovões”, no qual muitas solicitações de clientes eram emitidas essencialmente ao mesmo tempo. Como mostrado na Figura 11-6, esses picos de solicitações sincronizadas aumentaram enormemente para 20 vezes o pico global anterior de RPS.
Figura 11-6. Picos de tráfego causados por tentativas de cliente síncronas
Resolvendo o problema
Esses picos de solicitações, combinados com a regressão de capacidade do GFE, resultaram em filas e alta latência para todos os serviços do GCLB. Os engenheiros de tráfego de plantão da Google agiram para reduzir os danos colaterais para outros usuários do GCLB, fazendo o seguinte:
- Isolando os GFEs que poderiam atender ao tráfego do Pokémon GO do pool principal de balanceadores de carga.
- Ampliando o pool isolado do Pokémon GO até que ele pudesse lidar com o tráfego de pico, apesar da regressão de desempenho. Essa ação moveu o gargalo de capacidade do GFE para o stack da Niantic, onde os servidores ainda estavam expirando, especialmente quando as tentativas de cliente começaram a se sincronizar e aumentar.
- Com a aprovação da Niantic, os engenheiros de tráfego de plantão implementaram substituições administrativas para limitar a taxa de tráfego que os balanceadores de carga aceitariam em nome do Pokémon GO. Essa estratégia conteve a demanda dos clientes o suficiente para permitir que a Niantic restabelecesse a operação normal e começasse a escalar para cima.
A Figura 11-7 mostra a configuração final da rede.
Figura 11-7. Pokémon GO GCLB
Proteção para o futuro
Após esse incidente, tanto a Google quanto a Niantic fizeram mudanças significativas em seus sistemas. A Niantic introduziu jitter e retrocesso exponencial truncado em seus clientes, o que limitou os picos massivos de tentativas sincronizadas de novo experimentados durante a falha em cascata. A Google aprendeu a considerar os backends do GFE como uma fonte potencialmente significativa de carga e instituiu práticas de qualificação e teste de carga para detectar a degradação de desempenho do GFE causada por backends lentos ou mal comportados. Finalmente, ambas as empresas perceberam que deveriam medir a carga o mais próximo possível do cliente. Se a Niantic e o Google CRE tivessem conseguido prever com precisão a demanda de RPS do cliente, teríamos escalado preventivamente os recursos alocados para a Niantic ainda mais do que fizemos antes de fazer a troca para o GCLB.
Autoscaling
Ferramentas como o GCLB podem ajudá-lo a balancear a carga de forma eficiente em toda a sua frota, tornando seu serviço mais estável e confiável. Às vezes, simplesmente não há recursos reservados suficientes para gerenciar seu tráfego existente. Você pode usar o autoscaling para dimensionar sua frota estrategicamente. Seja aumentando os recursos por máquina (dimensionamento vertical) ou aumentando o número total de máquinas no pool (dimensionamento horizontal), o autoscaling é uma ferramenta poderosa e, quando usada corretamente, pode melhorar a disponibilidade e utilização do seu serviço. Por outro lado, se configurado incorretamente ou usado de maneira inadequada, o autoscaling pode impactar negativamente seu serviço. Esta seção descreve algumas melhores práticas, modos de falha comuns e limitações atuais do autoscaling.
Lidando com máquinas não saudáveis
O autoscaling normalmente calcula a utilização média em todas as instâncias, independentemente do estado delas, e assume que as instâncias são homogêneas em termos de eficiência no processamento de solicitações. O autoscaling encontra problemas quando as máquinas não estão servindo (conhecidas como instâncias não saudáveis), mas ainda são contadas na média de utilização. Nesse cenário, o autoscaling simplesmente não ocorrerá. Uma variedade de problemas pode acionar esse modo de falha, incluindo:
- Instâncias que levam muito tempo para se preparar para servir (por exemplo, ao carregar um binário ou fazer um aquecimento)
- Instâncias presas em um estado de não atendimento (ou seja, zumbis)
Podemos melhorar essa situação usando uma variedade de táticas. Você pode implementar as seguintes melhorias em combinação ou individualmente:
Balanceamento de carga
Dimensionamento automático usando uma métrica de capacidade conforme observado pelo balanceador de carga. Isso automaticamente desconsiderará instâncias não saudáveis da média.
Aguarde que novas instâncias se estabilizem antes de coletar métricas.
Você pode configurar o dimensionador automático para coletar informações sobre novas instâncias somente quando estas se tornarem saudáveis (GCE se refere a este período de inatividade como um período de resfriamento).
Autoscale e Autoheal
O Autoheal monitora suas instâncias e tenta reiniciá-las se estiverem não saudáveis. Normalmente, você configura seu autohealer para monitorar uma métrica de saúde exposta por suas instâncias. Se o autohealer detectar que uma instância está fora do ar ou não saudável, ele tentará um reinício. Ao configurar seu autohealer, é importante garantir que você deixe tempo suficiente para suas instâncias se tornarem saudáveis após um reinício.
Usando uma combinação dessas soluções, você pode otimizar o dimensionamento automático horizontal para acompanhar apenas máquinas saudáveis. Lembre-se de que, ao executar seu serviço, o dimensionador automático ajustará continuamente o tamanho de sua frota. Criar novas instâncias nunca é instantâneo.
Trabalhando com Sistemas Estaduais
Um sistema estadual envia todas as solicitações em uma sessão de usuário consistentemente para o mesmo servidor de backend. Se esses caminhos estiverem sobrecarregados, adicionar mais instâncias (ou seja, dimensionamento horizontal) não ajudará. O roteamento inteligente ao nível da tarefa que distribui a carga (por exemplo, usando hashing consistente) é uma estratégia melhor para sistemas estaduais.
O dimensionamento vertical pode ser útil em sistemas estaduais. Quando usado em combinação com o balanceamento de carga ao nível da tarefa para distribuir uniformemente a carga em seu sistema, o dimensionamento vertical pode ajudar a absorver picos de curto prazo. Use essa estratégia com cautela: como o dimensionamento vertical é tipicamente uniforme em todas as instâncias, suas instâncias de baixo tráfego podem crescer desnecessariamente grandes.
Configuração conservadora
Configurar o dimensionamento automático de forma conservadora é mais importante e menos arriscado do que usá-lo para reduzir o tamanho, uma vez que uma falha em escalar para cima pode resultar em sobrecarga e tráfego perdido. Por design, a maioria das implementações de dimensionamento automático são intencionalmente mais sensíveis a picos de tráfego do que a quedas de tráfego. Ao escalar para cima, os dimensionadores automáticos têm a tendência de adicionar capacidade de serviço extra rapidamente. Ao escalar para baixo, eles são mais cautelosos e esperam mais tempo para que sua condição de dimensionamento se mantenha verdadeira antes de reduzir os recursos lentamente.
Os picos de carga que você pode absorver aumentam à medida que seu serviço se afasta mais de um gargalo. Recomendamos configurar seu dimensionador automático para manter seu serviço longe de gargalos importantes do sistema (como CPU). O dimensionador automático também precisa de tempo adequado para reagir, especialmente quando novas instâncias não podem ser ativadas e servir instantaneamente. Recomendamos que os serviços voltados para o usuário reservem capacidade suficiente para proteção contra sobrecarga e redundância.
Definindo restrições
O dimensionador automático é uma ferramenta poderosa; se configurado incorretamente, pode escalar fora de controle. Você pode inadvertidamente desencadear consequências graves ao introduzir um bug ou alterar uma configuração. Por exemplo, considere os seguintes cenários:
- Você configurou o autoscaling para dimensionar com base na utilização da CPU. Você lança uma nova versão do seu sistema, que contém um bug fazendo com que o servidor consuma CPU sem fazer nenhum trabalho. O autoscaler reage aumentando repetidamente esse trabalho até que toda a cota disponível seja desperdiçada.
- Nada mudou no seu serviço, mas uma dependência está falhando. Essa falha faz com que todas as solicitações fiquem presas em seus servidores e nunca terminem, consumindo recursos o tempo todo. O autoscaler dimensionará os trabalhos para cima, causando mais e mais tráfego a ficar preso. A carga aumentada na sua dependência com falha pode impedir que ela se recupere.
É útil limitar o trabalho que seu dimensionador automático pode realizar. Defina um limite mínimo e máximo para dimensionamento, garantindo que você tenha cota suficiente para dimensionar até os limites definidos. Fazê-lo impede que você esgote sua cota e auxilia no planejamento de capacidade.
Incluir interruptores de desligamento e substituições manuais
É uma boa ideia ter um interruptor de desligamento caso algo dê errado com seu dimensionamento automático. Certifique-se de que os engenheiros de plantão saibam como desativar o dimensionamento automático e como dimensionar manualmente, se necessário. A funcionalidade do interruptor de desligamento do seu dimensionamento automático deve ser fácil, óbvia, rápida e bem documentada.
Evitando sobrecarregar backends
Um dimensionador automático configurado corretamente irá dimensionar para cima em resposta a um aumento no tráfego. Um aumento no tráfego terá consequências na pilha de tecnologia. Serviços de backend, como bancos de dados, precisam absorver qualquer carga adicional que seus servidores possam criar. Portanto, é uma boa ideia realizar uma análise detalhada de dependências em seus serviços de backend antes de implantar seu dimensionador automático, especialmente porque alguns serviços podem escalar de forma mais linear do que outros. Garanta que seus backends tenham capacidade extra suficiente para atender ao aumento no tráfego e sejam capazes de degradar graciosamente quando sobrecarregados. Use os dados de sua análise para informar os limites da configuração do seu dimensionador automático.
Implantações de serviços comumente executam uma variedade de microsserviços que compartilham cota. Se um microsserviço dimensionar para cima em resposta a um pico de tráfego, ele pode usar a maior parte da cota. Se o aumento do tráfego em um único microsserviço significa um aumento do tráfego em outros microsserviços, não haverá cota disponível para os microsserviços restantes crescerem. Nesse cenário, uma análise de dependência pode ajudar a orientá-lo a implementar o dimensionamento limitado preventivamente. Alternativamente, você pode implementar cotas separadas por microsserviço (o que pode exigir dividir seu serviço em projetos separados).
Evitando desequilíbrio de tráfego
Alguns dimensionadores automáticos (por exemplo, AWS EC2, GCP) podem balancear instâncias entre grupos regionais de instâncias (RMiGs). Além do dimensionamento automático regular, esses dimensionadores automáticos executam um trabalho separado que constantemente tenta equalizar o tamanho de cada zona em toda a região. Reequilibrar o tráfego dessa forma evita ter uma zona grande. Se o sistema que você está usando aloca cota por zona, essa estratégia equilibra o uso da sua cota. Além disso, o dimensionamento automático em várias regiões oferece mais diversidade para domínios de falha.
Combinação de estratégias para gerenciar carga
Se o seu sistema se tornar suficientemente complexo, você pode precisar usar mais de um tipo de gerenciamento de carga. Por exemplo, você pode executar vários grupos de instâncias gerenciadas que dimensionam com base na carga, mas são clonados em várias regiões para capacidade; portanto, você também precisa balancear o tráfego entre regiões. Nesse caso, seu sistema precisa usar tanto balanceamento de carga quanto dimensionamento automático baseado em carga.
Ou talvez você execute um site em três instalações colocadas em todo o mundo. Você gostaria de atender localmente para latência, mas como leva semanas para implantar mais máquinas, a capacidade excedente precisa se espalhar para outras localidades. Se o seu site se tornar popular nas redes sociais e de repente experimentar um aumento de cinco vezes no tráfego, você preferiria atender aos pedidos que puder. Portanto, você implementa o “load shedding” para descartar o tráfego excessivo. Nesse caso, seu sistema precisa usar tanto o balanceamento de carga quanto o load shedding.
Ou talvez seu pipeline de processamento de dados resida em um cluster Kubernetes em uma região de nuvem. Quando o processamento de dados diminui significativamente, ele provisiona mais pods para lidar com o trabalho. No entanto, quando os dados chegam tão rapidamente que a leitura deles faz com que você fique sem memória, ou diminui a coleta de lixo, seus pods podem precisar descartar essa carga temporária ou permanentemente. Nesse caso, seu sistema precisa usar tanto o dimensionamento automático baseado na carga quanto técnicas de load shedding.
O balanceamento de carga, o “load shedding” e o dimensionamento automático são todos sistemas projetados para o mesmo objetivo: equalizar e estabilizar a carga do sistema. Como os três sistemas são frequentemente implementados, instalados e configurados separadamente, eles parecem independentes. No entanto, como mostrado na Figura 11-8, eles não são totalmente independentes. O estudo de caso a seguir ilustra como esses sistemas podem interagir.
Figura 11-8. Um sistema completo de gerenciamento de tráfego
Estudo de Caso 2: Quando o “Load Shedding” Ataca
Imagine uma empresa fictícia, Dressy, que vende vestidos online por meio de um aplicativo. Como este é um serviço orientado pelo tráfego, a equipe de desenvolvimento da Dressy implantou seu aplicativo em três regiões. Essa implantação permite que seu aplicativo responda rapidamente às solicitações dos usuários e supere falhas em uma única zona – ou assim eles pensavam.
A equipe de atendimento ao cliente da Dressy começa a receber reclamações de que os clientes não conseguem acessar o aplicativo. As equipes de desenvolvimento da Dressy investigam e percebem um problema: seu balanceamento de carga está inexplicavelmente direcionando todo o tráfego de usuários para a região A, mesmo que essa região esteja cheia até transbordar e tanto B quanto C estejam vazias (e igualmente grandes). A linha do tempo dos eventos (veja a Figura 11-9) é a seguinte:
- No início do dia, os gráficos de tráfego mostravam todos os três clusters estáveis em 90 RPS.
- Às 10h46min, o tráfego começou a aumentar em todas as três regiões, à medida que os compradores ávidos começaram a caçar pechinchas.
- Às 11h00min, a região A atingiu 120 RPS pouco antes das regiões B e C.
- Às 11h10min, a região A continuou a crescer para 400 RPS, enquanto B e C caíram para 40 RPS.
- O balanceador de carga se estabilizou nesse estado.
- A maioria das solicitações que atingiram a região A estava retornando erros 503.
- Os usuários cujas solicitações atingiram este cluster começaram a reclamar.
Figura 11-9. Tráfego regional
Se a equipe de desenvolvimento tivesse consultado os gráficos de ocupação completos do balanceador de carga, eles teriam visto algo muito estranho. O balanceador de carga estava ciente da utilização: estava lendo a utilização da CPU dos contêineres da Dressy e usando essas informações para estimar a ocupação. Até onde podia dizer, a utilização da CPU por solicitação era 10 vezes menor na região A do que em B ou C. O balanceador de carga determinou que todas as regiões estavam igualmente carregadas, e seu trabalho estava feito.
O que estava acontecendo?
No início da semana, para se proteger contra a sobrecarga cascata, a equipe habilitou o despejo de carga. Sempre que a utilização da CPU atingia um certo limite, um servidor retornava um erro para qualquer nova solicitação que recebia em vez de tentar processá-las. Neste dia em particular, a região A atingiu esse limite ligeiramente antes das outras regiões. Cada servidor começou a rejeitar 10% das solicitações que recebia, depois 20% das solicitações e, finalmente, 50%. Durante este período, o uso da CPU permaneceu constante.
Na visão do sistema de balanceamento de carga, cada solicitação descartada sucessivamente representava uma redução no custo da CPU por solicitação. A região A era muito mais eficiente do que as regiões B e C. Estava atendendo a 240 RPS com 80% da CPU (o limite de descarte), enquanto B e C estavam gerenciando apenas 120 RPS. Logicamente, ele decidiu enviar mais solicitações para A.
O que deu errado?
Em resumo, o balanceador de carga não sabia que as solicitações “eficientes” eram erros porque os sistemas de descarte de carga e de balanceamento de carga não estavam se comunicando. Cada sistema foi adicionado e habilitado separadamente, provavelmente por diferentes engenheiros. Ninguém os havia examinado como um sistema unificado de gerenciamento de carga.
Lições aprendidas
Para gerenciar efetivamente a carga do sistema, precisamos ser deliberados – tanto na configuração de nossas ferramentas individuais de gerenciamento de carga quanto no gerenciamento de suas interações. Por exemplo, no estudo de caso da Dressy, adicionar tratamento de erro à lógica do balanceador de carga teria resolvido o problema. Digamos que cada solicitação de “erro” conte como 120% de utilização de CPU (qualquer número acima de 100 funcionará). Agora, a região A parece sobrecarregada. As solicitações serão distribuídas para B e C, e o sistema se equalizará.
Você pode usar uma lógica semelhante para extrapolar este exemplo para qualquer combinação de táticas de gerenciamento de carga. Ao adotar uma nova ferramenta de gerenciamento de carga, examine cuidadosamente como ela interage com outras ferramentas que seu sistema já está usando e instrumente sua interseção. Adicione monitoramento para detectar loops de feedback. Certifique-se de que seus gatilhos de desligamento de emergência possam ser coordenados em todos os sistemas de gerenciamento de carga e considere adicionar gatilhos de desligamento automáticos se esses sistemas estiverem se comportando de forma descontrolada. Se você não tomar as precauções adequadas desde o início, provavelmente terá que fazê-lo no rescaldo de uma análise postmortem.
É fácil dizer “tomar precauções”. Mais especificamente, aqui estão algumas precauções que você pode considerar, dependendo do tipo de gerenciamento de carga que você implementar:
Balanceamento de carga
O balanceamento de carga minimiza a latência direcionando para o local mais próximo do usuário. O escalonamento automático pode trabalhar em conjunto com o balanceamento de carga para aumentar o tamanho dos locais próximos ao usuário e então rotear mais tráfego para lá, criando um ciclo de feedback positivo.
Se a demanda for principalmente próxima a um local, esse local aumentará de tamanho até que toda a capacidade de atendimento esteja em um só lugar. Se esse local falhar, os locais restantes ficarão sobrecarregados e o tráfego poderá ser descartado. Dimensionar esses locais para cima não será instantâneo. Você pode evitar essa situação definindo um número mínimo de instâncias por local para manter capacidade de reserva para failover.
Alívio de carga
É uma boa ideia definir seus limites de forma que seu sistema escalone automaticamente antes que o alívio de carga entre em ação. Caso contrário, seu sistema poderá começar a descartar tráfego que poderia ter sido atendido se tivesse escalonado primeiro.
Gerenciar a carga com RPC
Lidar com as solicitações corretas é importante para a eficiência: você não quer escalar automaticamente para atender solicitações que não beneficiarão os usuários, nem descartar carga desnecessariamente porque está processando solicitações não importantes. Ao usar tanto o escalonamento automático quanto o alívio de carga, é importante definir prazos para suas solicitações RPC.
Os processos retêm recursos para todas as solicitações em andamento e liberam esses recursos quando as solicitações são concluídas. Na ausência de um prazo específico, o sistema reterá recursos para todas as solicitações em andamento, até o limite máximo possível. Por padrão, este prazo é um número muito grande (que depende da implementação da linguagem – algumas APIs de linguagem funcionam em termos de um ponto fixo no tempo, e outras com uma duração de tempo). Esse comportamento causa latência mais alta para os clientes e, ultimamente, para os usuários. O serviço também corre o risco de ficar sem recursos (como memória) e falhar.
Para lidar com esse cenário de forma graciosa, recomendamos que o servidor termine as solicitações que levam muito tempo e que os clientes cancelem as solicitações que não são mais úteis para eles. Por exemplo, um servidor não deve iniciar uma operação de busca cara se o cliente já retornou um erro para o usuário. Para definir expectativas de comportamento para um serviço, você pode simplesmente fornecer um comentário no arquivo .proto da API para sugerir um prazo padrão. Além disso, defina prazos deliberados para o cliente (para exemplos, consulte nossa postagem no blog “gRPC e Prazos”).
Conclusão
Na experiência do Google, não existem configurações perfeitas de gerenciamento de tráfego. O escalonamento automático é uma ferramenta poderosa, mas é fácil errar. A menos que seja cuidadosamente configurado, o escalonamento automático pode resultar em consequências desastrosas, como ciclos de feedback potencialmente catastróficos entre balanceamento de carga, alívio de carga e escalonamento automático quando essas ferramentas são configuradas de forma isolada. Como ilustra o estudo de caso do Pokémon GO, o gerenciamento de tráfego funciona melhor quando é baseado em uma visão holística das interações entre os sistemas.
Vez após vez, vimos que nenhuma quantidade de alívio de carga, escalonamento automático ou limitação de taxa salvará nossos serviços quando todos falharem sincronizadamente. Por exemplo, no estudo de caso do Pokémon GO, tivemos um “rebanho de trovões” de retentativas de clientes sincronizadas combinadas com balanceadores de carga que esperavam por servidores de back-end não responsivos. Para falhar seus serviços graciosamente, você precisa planejar com antecedência para mitigar problemas potenciais. Sua estratégia de mitigação pode envolver a definição de flags, alteração de comportamentos padrão, ativação de registro de eventos caros ou exposição do valor atual de parâmetros que o sistema de gerenciamento de tráfego usa para tomar decisões.
Esperamos que as estratégias e insights fornecidos neste capítulo possam ajudá-lo a gerenciar o tráfego para seus próprios serviços e manter seus usuários satisfeitos.