- Oi. Eu sou Sasha, trabalho na Yandex, nos últimos três anos tenho desenvolvido um balanceador de carga L7. Vou falar sobre uma maneira rápida e fácil de acelerar sua rede. Começaremos no sétimo nível, HTTP, e desceremos para o quarto nível, TCP. Hoje vamos apenas falar sobre esses dois níveis e nos deter neles com alguns detalhes.
Nos últimos oito anos, tenho feito mais desenvolvimento de back-end e, provavelmente, meu conhecimento permaneceu no nível de AngularJS nas primeiras versões. Você provavelmente sabe melhor do que eu como tudo funciona. Você já otimizou tudo, comprimiu tudo, e aqui não posso te aconselhar nada.
Mas posso aconselhá-lo sobre como acelerar sua rede otimizando o próprio servidor, o próprio sistema operacional.
Para acelerar algo, você precisa de métricas. Nesse caso, usamos o seguinte: o tempo médio para o primeiro byte nos mostra a velocidade da camada TCP e a segunda métrica é o tempo para receber o HTML do primeiro byte. Experimentamos, medimos nossas métricas e, depois de ligar o BBR, nossa aceleração era de cerca de dez por cento.
Para entender o que é dez por cento, nos voltamos para o valor absoluto, que é 66 milissegundos. Se você for ao seu jogo multiplayer online favorito, o ping para os servidores da Europa Ocidental durará aproximadamente 60-70 milissegundos.
Como fazer rápido
Todos os nossos servidores são gerenciados usando protocolos de controle remoto, neste caso SSH. Se você ainda não encontrou o SSH, pode pedir ao administrador do sistema para configurar o servidor. Vou te dizer como convencê-lo a fazer isso.
O que é BBR? Este é um dos algoritmos que nos permite controlar como os pacotes vão para a rede. E é configurado com os dois parâmetros a seguir. A primeira é definir o agendador de pacotes para FQ, então direi por que você deve usar FQ. A segunda é a inclusão do próprio controle de congestionamento, ou seja, o próprio BBR.
Parece que é aqui que podemos terminar. Mas, na verdade, existem muitas armadilhas e, muito provavelmente, seu administrador de sistema não ativará apenas o BBR no servidor. Portanto, iremos mais longe.
HTTP / 2 e multiplexação
Começaremos no nível 7, que é HTTP, e lentamente desceremos para revisar nossos protocolos.
Começaremos com nossos navegadores com os quais interagimos todos os dias. Vemos o console do desenvolvedor web. O console tem um campo interessante para nós - protocolo.
Em nosso caso, os protocolos são HTTP / 1 e HTTP / 2. Também existe o protocolo HTTP / 3, que é baseado no protocolo QUIC do Google. Mas hoje não vamos voltar a ele, pois está em desenvolvimento, ainda não totalmente aprovado. Vamos voltar para HTTP / 1.
No slide, vemos o utilitário Wireshark, que nos permite analisar nossos pacotes, a forma como interagimos com a rede. Vemos que um campo está destacado em verde. Esta é a nossa solicitação HTTP. Abaixo podemos ver os bytes, como eles serão apresentados na rede.
Qual é a aparência do HTTP / 1 na vida real? Este é um protocolo bastante simples. É totalmente textual, ou seja, apenas escrevemos o texto e enviamos para a rede. Nossos caracteres são codificados com valores hexadecimais especiais. À direita está uma tabela ASCII, um pequeno pedaço para você navegar.
Temos a primeira parte na forma de cabeçalhos, que são separados pelos caracteres “\ r \ n \ r \ n” do nosso corpo. Estamos apenas solicitando um recurso regular aqui com o método GET, portanto, esta solicitação não terá um corpo. E vemos que os bytes são aproximadamente semelhantes ao que está na tabela ASCII. Estamos solicitando algum tipo de JS, algum tipo de recurso. Há também um cabeçalho Host indicando o domínio com o qual estamos trabalhando no momento. E - algum conjunto adicional de cabeçalhos. Eles podem ser personalizados, você pode usar qualquer um.
HTTP / 2 é um protocolo mais complexo. É binário e a menor unidade de troca de informações são os quadros. Existem muitos casos especiais, tipos especiais dessas armações. Você pode ver no slide que eles estão destacados.
Também podemos observar na primeira linha que dois quadros podem caber em um pacote ao mesmo tempo. Não vamos insistir em quais frames existem, existem alguns deles. Nesse caso, estaremos interessados no quadro de cabeçalhos, pois ele apenas nos permite solicitar recursos. Estive um pouco envolvido no desenvolvimento do Wireshark, ajudando a melhorá-lo neste campo.
Podemos ver que há um pedido get. Vemos que no meio há uma representação textual dessa solicitação get. Mas na coluna direita vemos apenas um byte selecionado, e será apenas este método get. A seguir, explicarei por que isso acontece.
A seguir, temos o cabeçalho do caminho, que indica o caminho para o recurso, para nosso JS, que iremos solicitar. E há um conjunto de alguns cabeçalhos adicionais que também estarão presentes em nossa solicitação.
Então, por que nossos bytes na rede não são os mesmos como tudo isso é desenhado em nossa imagem? O fato é que o Wireshark nos mostra o resultado final, como ele decodificou tudo. E na rede, esses bytes, esses cabeçalhos, serão compactados com um formato HPACK especial. Mais detalhes são melhores para se familiarizar na Internet Procure informações, está bem documentado.
Vamos voltar às nossas mordidas. Existe um campo especial - identificador de conteúdo. Ele indica com qual recurso esses quadros estão trabalhando no momento. Neste caso, enviamos o primeiro quadro, dados recebidos. Quando o servidor nos fornece os próprios bytes de conteúdo, o quadro de dados já estará sendo usado.
Nossos protocolos HTTP / 1 e HTTP / 2 são muito diferentes. Já falamos sobre o fato de o HTTP / 1 ser um protocolo textual e o HTTP / 2 ser um protocolo binário, ou seja, funciona por meio de frames.
HTTP / 1, no caso de uma solicitação na forma de uma única conexão, retornará um resultado desconhecido, dependendo da implementação de como os desenvolvedores do servidor web o escreveram. Ou seja, se fizermos duas solicitações em uma conexão, muito provavelmente, uma resposta à primeira ou à segunda solicitação será retornada. Para fazer isso, a fim de carregar recursos em paralelo, o navegador estabelece várias conexões, geralmente cerca de seis, e carrega recursos em paralelo.
O HTTP / 2, por sua vez, usa uma única conexão. Ou seja, ele estabelece a conexão e dentro dele carrega todos os dados necessários por meio de frames. Essa técnica de empacotar vários recursos em uma conexão é chamada de multiplexação.
Fica claro como funcionam nossas conexões: em caso de perda de pacotes em uma das conexões, o HTTP / 1 funcionará melhor. Provavelmente, não tocaremos em outras conexões, elas continuarão a carregar na mesma velocidade. E no caso do HTTP / 2, se nosso pacote for perdido, o carregamento de todos os recursos começa a ficar lento.
Parece que o HTTP / 2 é pior, também é sensível à perda de pacotes. Na verdade, quando criamos cada uma dessas seis conexões, estamos executando a seguinte operação.
O cliente e o servidor estabelecem uma conexão confiável, uma conexão TCP. Enviamos dois pacotes do cliente para o servidor e um pacote é enviado do lado do servidor para o cliente. Portanto, parece que estamos prontos para transferir dados. Isso, é claro, cria recursos de sobrecarga, podemos fazer isso por muito tempo.
Também há criptografia. Se você olhar para o seu navegador agora, provavelmente verá um ícone de cadeado. Muitas pessoas o chamam de SSL, mas não é realmente SSL. Este é o TLS. SSL há muito tempo está desatualizado, praticamente não é mais compatível e deve ser abandonado.
O TLS também oferece troca de pacotes. Ou seja, nós, assim como no caso de um handshake TCP, definimos um determinado estado, após o qual podemos continuar a trabalhar. Neste ponto, também podemos fazer a otimização, mas os navegadores ainda não suportam as coisas que já habilitamos no lado do servidor. Vamos esperar que todos liguem.
Era uma vez, o HTTP / 1 tentou resolver o problema de carregamento simultâneo de recursos. A RFC tem isso. E uma vez, o pipelining foi implementado. Devido à complexidade de implementação, o Internet Explorer não oferece suporte, ao contrário do Firefox e do Chrome, mas o suporte foi diminuindo com o tempo.
Cada uma de nossas seis conexões que já criamos, na verdade, não será fechada. Ou seja, eles continuarão a trabalhar da mesma maneira que antes. Para isso, uma técnica como Keep-Alive é usada. Ou seja, criamos uma conexão confiável com um servidor específico e continuamos trabalhando.
No nível HTTP, isso é controlado pelo cabeçalho. Nesse caso, é conexão. E no nível do TCP, já usaremos o próprio sistema operacional, ele decidirá por nós o que fazer.
Existem outros problemas com HTTP / 2 também. Em HTTP / 2, podemos priorizar pacotes e enviar mais dados necessários com mais rapidez. Nesse caso, quando estamos tentando enviar muitos dados de uma vez, o buffer no servidor pode estourar. Então, os pacotes de prioridade mais alta simplesmente ficarão mais lentos e irão para o fim da fila.
Perda de pacote é observada. Eles diminuem a velocidade do nosso carregamento e esse bloqueio é chamado de bloqueio frontal.
Como o TCP resolve problemas de perda de pacotes
Agora falaremos sobre o TCP, ou seja, nossa quarta camada. Nos próximos dez minutos, abordarei como funciona o TCP.
Quando estamos visitando, pedimos a alguém que passe o sal. Quando uma pessoa nos dá sal, confirmamos que o sal nos alcançou. Nesse caso, também pegamos um segmento, o transmitimos e esperamos pela confirmação. Transmitimos novamente. E se houver prejuízo, a gente encaminha esse segmento e, como resultado, ele é entregue para nós. Essa técnica de envio de um segmento é chamada de Parar e esperar.
Mas nossas redes têm se acelerado tremendamente nos últimos 30 anos. Talvez alguns de vocês se lembrem de dial-up, Internet por megabytes. A esta altura, você já deve conseguir se conectar à Internet gigabit em casa.
Além disso, neste caso, podemos começar a enviar vários pacotes de uma vez. Em nosso exemplo, existem três deles. Enviamos a janela em forma de três pacotes e aguardamos que todos sejam confirmados.
Em caso de perda de pacotes, podemos começar a reenviar todos os pacotes, começando pela primeira perda. Essa técnica é chamada de Go-Back N. Caso contrário, podemos começar a rastrear todos os pacotes e encaminhar apenas aqueles que foram perdidos. Esta técnica é chamada de Repetição Seletiva. É mais caro do lado do servidor. Quando estávamos preparando os slides, demorou muito para descobrir como apresentá-los. Eu mesmo me confundi com isso e, portanto, fiz essa analogia.
Todos nós conhecemos canos pelos quais a água flui. Os tubos têm diâmetros diferentes, em algum lugar eles podem ser mais finos e, neste caso, o ponto mais estreito será apenas com nossa taxa de transferência máxima. Não seremos capazes de derramar mais água do que esse gargalo permite.
Tentaremos atirar bolas da esquerda para a direita. Do lado direito, teremos a confirmação de que as bolas voaram. Estamos começando a enviar um fluxo de bolas. Vamos dar uma olhada em seu corte. Agora as bolas voam em uma direção, elas são confirmadas, e o número de nossas bolas cresce exponencialmente. Em algum momento, o volume das bolas torna-se tão grande que elas diminuem a velocidade e começam as perdas. Depois da perda, desaceleramos um pouco, reduzimos nossa janela pela metade. Então, tentamos entender o que aconteceu conosco. O primeiro estágio é denominado TCP Slow Start.
Depois de fecharmos a janela duas vezes, podemos restaurar a conexão e pedir aos caras que nos enviem nossas bolas novamente. Eles gritam para nós que precisamos enviar as bolas, nós respondemos - aqui estão suas bolas. Esta fase é chamada de Recuperação Rápida e Retransmissão Rápida.
Quando percebemos que está tudo bem conosco, começamos a aumentar gradativamente o número de bolas enviadas, a partir daquela janela que caiu. Esta fase é chamada de Prevenção de Congestionamento. Ou seja, estamos tentando evitar a perda de nossos pacotes.
A fase em que nossa janela se fecha duas vezes é chamada de diminuição multiplicativa. E a fase lenta de aumento do número de bolas é chamada de Aumento Aditivo.
Quando nosso Congestion Avoidance perde pacotes novamente, podemos dar o próximo passo. Mas no momento estamos mais interessados na própria imagem desse gráfico. Vemos tal serra, e ela será útil para nós várias vezes. Lembre-se de como é.
Voltaremos aos problemas dos protocolos TCP convencionais. Por analogia com um cachimbo, colocamos sacos. Como existem outros usuários na Internet além de nós, eles também começam a despejar pacotes no cachimbo. Em algum ponto, os buffers de nossos roteadores podem estourar e criar problemas para o envio de pacotes.
Também há um problema com a perda de pacotes em redes sem fio. Provavelmente, seu laptop não tem uma porta Ethernet e você está assistindo a uma palestra por Wi-Fi. A perda de pacotes em redes Wi-Fi e móveis não é causada pelo roteador em si, mas por interferência de rádio. Essa métrica não será muito útil para nós.
Diferença de TCP BBR de outros algoritmos
Aqui chegamos ao BBR. Significa Bottleneck Bandwidth e Round Trip Time, que são métricas da largura de banda quando não obstruímos completamente nosso canal e o tempo de viagem do pacote de nós ao servidor e de volta.
Quando enviamos dados, o estado ideal no qual os pacotes estão em um estado estável, voam e ainda não foram reconhecidos é chamado de produto de atraso de largura de banda. Podemos aumentar o BDP usando buffers de dispositivos de rede. Quando esse buffer é excedido, as perdas começam.
E os algoritmos TCP usuais funcionam apenas no lado direito do gráfico, isto é, onde ocorrem perdas - despejamos tantos pacotes que as perdas são inevitáveis. Os pacotes ficam mais lentos e começamos a fechar a janela.
O BBR, por sua vez, trabalha com um princípio diferente, próximo ao nosso tubo. Nós apenas colocamos tantos sacos quanto podemos pular. Na fase inicial, ou seja, logo no início, enchemos os sacos até que comece o congestionamento.
E às vezes a perda de pacotes é possível. Mas a BBR está tentando evitar esse momento. Depois de encher o cachimbo, começamos a rolar para trás. Essa fase é chamada de drenagem.
Voltamos à nossa conexão estável, onde será totalmente preenchida, mas ao mesmo tempo não usaremos buffers adicionais, reservatórios adicionais. Dessa posição, o BBR continua operando.
De vez em quando, veremos o que está acontecendo com nossa rede. Rastreamos quase todos os pacotes que nos são devolvidos. Quando os pacotes são devolvidos para nós, começamos a tentar acelerar um pouco o número de pacotes, acelerar os próprios pacotes enviando-os para a rede.
E se não tivermos problemas, podemos ficar com esse valor. Ou seja, continuar trabalhando no ritmo que nos for confortável. Se, no entanto, houver perdas, podemos reverter.
Quando recebemos a confirmação, vimos que a velocidade melhorou, podemos esperar um pouco, olha o intervalo de dez segundos. E se durante este intervalo virmos que a velocidade de envio de pacotes aumenta e os pacotes são confirmados mais rapidamente, podemos entrar na fase de teste de RTT e verificar se tudo melhorou.
Tais fases se alternam, ou seja, vamos verificar constantemente o que temos com a rede.
O algoritmo BBR não é mais baseado na perda de pacotes, mas na largura do canal e no tempo de viagem do pacote.
Na verdade, ele é imune à perda de pacotes. Ele praticamente não reage a eles, por isso temos alguns problemas. O Google prometeu que esses problemas serão corrigidos no BBR v2.
Examinamos nossas fases e diante de nós está novamente o pente, que já mostrei. Os protocolos TCP regulares são destacados em vermelho. Então ele recolhe, recolhe, diminui a velocidade e novamente perde pacotes. E a BBR dá o ritmo de que ele precisa, com o qual trabalhará o tempo todo, e verifica constantemente nossa rede para ver se ela melhorou um pouco. E pode estar acelerando.
Nossas métricas são constantemente atualizadas, rastreamos todas as confirmações do cliente e verificamos se nossa rede acelerou ou não.
Como essa taxa de envio de pacotes é controlada? Controlamos o ritmo de envio usando a técnica de estimulação. Ele é implementado no agendador que mencionei anteriormente. Este é o escalonador FQ. Também é implementado no próprio kernel, mas falarei sobre isso mais tarde.
Tentamos, como em um tubo, despejar mais dados e, ao mesmo tempo, não desacelerar, não perder nossos pacotes. Mas o BBR não é tão simples. Provavelmente, você mora em contêineres ou usa vários servidores para bancos de dados - talvez para fotos.
E todos esses servidores interagem uns com os outros. Há TCP normal habilitado, não BBR. E quando você tiver a serra, que já vimos, quando a janela começar a entrar em colapso, talvez a BBR comece a perceber que a janela está colapsando e a aumentar a taxa de envio de pacotes. Assim, ele expulsará o TCP comum de nossa rede e o dominará.
Se a rede estiver muito ruim, outros problemas são possíveis. O TCP normal não funcionará de forma alguma e, como o BBR é praticamente insensível à perda de pacotes, ele continuará a funcionar a uma determinada taxa.
Podemos resolver esse problema com data centers com a opção TCP_CONGESTION. Ele é exposto para cada tomada, para cada conexão. Agora, pelo que eu sei, essa opção não é implementada em quase nenhum servidor web. E nosso balanceador L7 suporta isso. Mas voltando ao nosso ritmo. Se você estiver trabalhando com kernels mais antigos, houve um bug na implementação do ritmo nos kernels anteriores à versão 4.20. Nesse caso, vale a pena usar o escalonador FQ.
Agora que você sabe como o TCP funciona, pode ir ao administrador do sistema e dizer a ele por que você deve habilitar o BBR.
Vamos voltar aos nossos dez por cento. De onde eles podem vir? As redes de operadoras agora são muito grandes. É tudo sobre dinheiro. Você pode construir canais para 100, 200 terabits e pular uma grande quantidade de vídeos em 4K, por exemplo. Mas seu cliente ainda estará no terminal.
E muito provavelmente, esse último quilômetro até o cliente será a fonte dos problemas. Todos os nossos Wi-Fi e LTE perderão pacotes. No caso de usar o TCP regular, veremos lentidão. A BBR resolve esse problema. Você pode ligá-lo apenas com os dois comandos que indiquei. Obrigado a todos.