Ao desenvolver aplicativos de rede altamente carregados, é necessário balancear a carga.
Uma ferramenta de balanceamento L7 popular é o Nginx. Permite armazenar respostas em cache, escolher estratégias diferentes e até scripts no LUA.
Apesar de todos os encantos do Nginx, se:
- Não há necessidade de trabalhar com HTTP (s).
- Você precisa extrair o máximo da rede.
- Não há necessidade de armazenar em cache nada - o balanceador possui servidores de API limpos com dinâmica.
A questão pode surgir: por que você precisa do Nginx? Por que gastar recursos equilibrando no L7, não é mais fácil simplesmente encaminhar o pacote SYN? (Roteamento direto L4).
Balanceamento da camada 4 ou como equilibrar na antiguidade
Uma ferramenta popular de encaminhamento de pacotes era o IPVS. Ele executou tarefas de balanceamento através do túnel e roteamento direto.
No primeiro caso, um canal TCP foi estabelecido para cada conexão e o pacote do usuário foi para o balanceador, depois para o lacaio e, em seguida, na ordem inversa.

Nesse esquema, o principal problema é visível: na direção oposta, os dados vão primeiro para o balanceador e depois para o usuário (o Nginx funciona da mesma maneira). Trabalho desnecessário é realizado, considerando que geralmente mais dados são enviados ao usuário, esse comportamento leva a alguma perda de desempenho.
Essa desvantagem é privada (mas dotada de novas) de um método de balanceamento chamado Roteamento Direto. Esquematicamente, fica assim:

No caso do roteamento direto, os pacotes de retorno vão diretamente para o usuário, ignorando o balanceador. Obviamente, os recursos do balanceador e a rede são salvos. Ao economizar recursos de rede, não é muito poupador de tráfego, porque a prática usual é conectar os servidores a uma grade separada e não contabilizar o tráfego, mas o fato de até transferir através do balanceador é uma perda de milissegundos.
Este método impõe certas restrições:
- O data center em que a infraestrutura está localizada deve permitir falsificação de endereços locais. No diagrama acima, cada subordinado deve enviar pacotes de volta em nome do IP 10.10.0.1, que é atribuído ao balanceador.
- O balanceador não sabe nada sobre o estado dos lacaios. Conseqüentemente, as estratégias Least Conn e Least Time não são viáveis imediatamente. Em um dos seguintes artigos, tentarei implementá-los e mostrar o resultado.
Aqui Vem NFTables
Alguns anos atrás, o Linux começou a promover ativamente o NFTables como um substituto para IPTables, ArpTables, EBTables e todos os outros [az] {1,} Tables. No momento em que em Adram tivemos a necessidade de extrair cada milissegundo de resposta da rede, decidi retirar o verificador e pesquisar - ou talvez o ipTables tenha aprendido a encaminhar iphash e você possa acelerá-lo para equilibrá-lo. Então me deparei com nftables, o que pode e não apenas isso, mas o iptables ainda não pode fazer isso.
Após vários dias de teste, finalmente consegui obter o roteamento direto e o roteamento de canais através das tabelas NFT no laboratório de testes e também compará-los com o nginx.
Então, o laboratório de teste. Temos 5 carros:
- nft-router - um roteador que executa a tarefa de conectar o cliente e a sub-rede AppServer. Possui 2 placas de rede: 192.168.56.254 - analisa a rede do servidor de aplicativos, 192.168.97.254 - analisa os clientes. Ip_forward está ativado e todas as rotas são registradas.
- nft-client: cliente do qual ab, ip 192.168.97.2 será perseguido
- nft-balancer: balanceador. Possui dois IPs: 192.168.56.4, que os clientes acessam e 192.168.13.1, da sub-rede minion.
- nft-minion-a e nft-minion-b: minions ipy: 192.168.56.2, 192.168.56.3 e 192.168.13.2 e 192.168.13.3 (tentei usar a mesma rede e a outra para equilibrar). Nos testes, parei com o fato de os lacaios terem tipos "externos" - na sub-rede 192.168.56.0/24
Todas as interfaces MTU 1500.
Roteamento direto
Configurações de NFTables no balanceador:
table ip raw { chain input { type filter hook prerouting priority -300; policy accept; tcp dport http ip daddr set jhash tcp sport mod 2 map { 0: 192.168.56.2, 1: 192.168.56.3 } } }
Uma cadeia bruta é criada, no gancho, com uma prioridade de -300.
Se um pacote com um endereço de destino http chegar, dependendo da porta de origem (feita para testar a partir de uma máquina, você realmente precisa de ip saddr), 56.2 ou 56.3 é selecionado e definido como o endereço de destino no pacote e enviado posteriormente pelas rotas. Grosso modo, para portas pares 56.2, para portas ímpares, respectivamente, 56.3 (de fato, não, porque para hashes pares / ímpares, mas é mais fácil entender exatamente isso). Após definir o IP de destino, o pacote volta para a rede. Não ocorre NAT, o pacote chega aos minions com o IP de origem do cliente, e não o balanceador, o que é importante para o roteamento direto.
Configurações do Minion NFT:
table ip raw { chain output { type filter hook output priority -300; policy accept; tcp sport http ip saddr set 192.168.56.4 } }
Um gancho de saída bruto é criado com uma prioridade de -300 (prioridade é muito importante aqui, em níveis mais altos a mengling necessária não funcionará para pacotes de resposta).
Todo o tráfego de saída da porta http é assinado por 56,4 (ip balancer) e enviado diretamente ao cliente, ignorando o balancer.
Para verificar se tudo funcionará corretamente, levei o cliente para outra rede e o deixei passar pelo roteador.
Também desabilitei arp_filter, rp_filter (para que a falsificação funcionasse) e habilitei o ip_forward no balanceador e no roteador.
Para bancos, no caso da NFT, o Nginx + php7.2-FPM é usado através de um soquete unix em cada minion. Não havia nada no balanceador.
No caso do Nginx, usamos: nginx no balanceador e php7.2-FPM sobre TCP em minions. Como resultado, não equilibrei o servidor da Web por trás do balanceador, mas imediatamente o FPM (que será mais honesto com o nginx e mais consistente com a vida real).
Para NFT, apenas a estratégia de hash foi usada (
nft dr na tabela), para nginx: hash (
ngx eq ) e menos conn (
ngx lc )
Vários testes foram feitos.
- Script rápido pequeno (pequeno) .
<?php system('hostname');
- O script com um atraso aleatório (rand) .
<?php usleep(mt_rand(100000,200000)); echo "ok";
- Um script com o envio de uma grande quantidade de dados (tamanho) .
<?php $size=$_GET['size']; $file='/tmp/'.$size; if (!file_exists($file)) { $dummy=""; exec ("dd if=/dev/urandom of=$file bs=$size count=1 2>&1",$dummy); } fpassthru (fopen($file,'rb'));
Foram utilizados os seguintes tamanhos:
512.1440.1460.1480.1500.2048.65535.655350 bytes.
Antes dos testes, eu esquentava os arquivos de estática de cada lacaio.
Testado ab três vezes cada teste:
Inicialmente, planejei trazer o tempo do teste, milissegundos e o resto, como resultado, decidi pelo RPS - eles são representativos e se correlacionam com os indicadores de tempo.
Obteve os seguintes resultados:
Teste de tamanho - colunas - o tamanho dos dados fornecidos.

Como você pode ver, o roteamento direto nft vence por uma margem enorme.
Eu estava contando com alguns outros resultados relacionados ao tamanho do quadro Ethernet, mas nenhuma correlação foi encontrada. Talvez 512 corpo não se encaixe em 1500 MTU, embora, duvido, o pequeno teste seja indicativo.
Notei que em grandes volumes (650k) o nginx reduz a separação. Talvez isso tenha algo a ver com buffers e tamanho do Windows TCP.
O resultado do teste de rand. Mostra como o menos conn lida em condições de velocidade diferente da execução do script em diferentes minions.

Surpreendentemente, o nginx hash funcionou mais rápido que o menor número de conexões, e somente no passe final o menor número de conexões foi um pouco à frente, o que não pretende ser significativo.
Os números de passes são muito diferentes devido ao fato de 100 threads saírem de uma só vez, e o FPM-ok desde o início carregar cerca de 10. Na terceira passagem, eles tiveram tempo para se acostumar - o que mostra a aplicabilidade das estratégias para as rajadas.
A NFT provavelmente perdeu esse teste. O Nginx otimiza bem a interação com os FPMs nessas situações.
pequeno teste

nft vence marginalmente no RPS, menos conn é novamente um outsider.
A propósito, neste teste, é visto que 400-500RPS são emitidos, embora, no teste com o envio de 512 bytes, fosse para 1500 - parece que o sistema consome mil.
Conclusões
A NFT teve um bom desempenho em uma situação de otimização de cargas uniformes: quando muitos dados são fornecidos e o tempo de operação do aplicativo é determinado e os recursos do cluster são suficientes para processar o fluxo de entrada sem entrar em queda.
Em uma situação em que a carga para cada solicitação é caótica e é impossível equilibrar uniformemente a carga do servidor com o restante primitivo da divisão de hash, a NFT perde.