...

Receive Side Scaling a 10 e 25 Gbit/s: otimização do desempenho para redes de servidores Linux modernas

Receive Side Scaling distribui o tráfego de rede de forma direcionada por várias núcleos, através de ligações de 10 e 25 Gbit/s, para que os servidores Linux processem elevadas taxas de débito com baixa latência. Vou mostrar, de forma prática, como ativo o RSS, que Tacos na pasta do Core, evitando assim gargalos nas interrupções e nos acertos de cache.

Pontos centrais

Vou resumir brevemente os aspetos mais importantes, para que possas planear rapidamente os próximos passos.

  • Distribuição da carga: Os pacotes são distribuídos por vários núcleos através de várias filas.
  • Localização da cache: Um fluxo permanece sempre na mesma fila.
  • Hashing: O hash de quartetos distribui os fluxos de forma uniforme pelas filas.
  • afinidade: O mapeamento específico de IRQ reduz as latências.
  • Escalonamento: A partir de 10/25 Gbit/s, a tecnologia RSS garante um elevado débito.

Estes pontos estão interligados e sustentam a Desempenho com base em cargas de trabalho reais. Dou prioridade, em primeiro lugar, ao número correto de filas e, em seguida, ao CPU-Afinidade. Em seguida, verifico os parâmetros de hash e os ajustes finos.

O que o Receive Side Scaling oferece

O RSS divide a receção de pacotes em várias partes Filas de receção, que atribuo a núcleos específicos da CPU, para que nenhum núcleo isolado se torne um gargalo. Isto diminui os picos de interrupções duras e suaviza o processamento através de SoftIRQs, o que reduz os picos de latência e aumenta o débito. Cada fila aciona as suas próprias interrupções, que atribuo de forma fixa a núcleos específicos, para manter a consistência dos percursos de dados. Esta consistência promove a Cache-Localização, porque um fluxo incide sempre no mesmo núcleo. É precisamente esta interação que, com taxas de PPS elevadas, se traduz diretamente numa eficiência mensurável.

É assim que o RSS funciona, do ponto de vista técnico

A NIC forma, a partir do IP de origem/destino e da porta de origem/destino, um Haxixe e utiliza-o como índice para a tabela de indireção, que aponta para as filas. Desta forma, os pacotes de um fluxo acabam sempre na mesma fila e permanecem, assim, ligados ao mesmo núcleo. Os diferentes fluxos distribuem-se uniformemente, desde que as chaves de hash e os campos de protocolo estejam configurados de forma adequada. O trabalho é, assim, transferido para perto da Hardware, o que faz com que o kernel tenha de realizar menos ajustes de equilíbrio e reduz a sobrecarga. É exatamente isso que pretendo, para manter baixo o processamento de pacotes por núcleo em 10G/25G.

Por que é que o RSS a partir de 10 e 25 Gbit/s é importante

A 1 Gbit/s, muitas vezes basta um único Núcleo a carga de pacotes, mas a partir dos 10 Gbit/s o equilíbrio altera-se rapidamente. Os pacotes pequenos fazem aumentar o número de PPS, o que faz com que um núcleo atinja rapidamente 100 por cento da carga e ocorram perdas de pacotes. É precisamente nessa altura que o RSS funciona como um multiplicador da largura de banda útil. Distribuo a carga por vários Núcleos, reduz as mudanças de contexto e mantém as curvas de latência mais estáveis. O resultado: só com um RSS limpo é que a taxa de transferência real se aproxima da taxa de ligação.

Configurar o RSS no servidor Linux

No Linux, controlo o RSS principalmente através de ferramenta eletrónica, opções do controlador e sysfs, para que as capacidades da NIC sejam efetivamente aproveitadas. Primeiro, leio o número máximo de canais de receção (RX) e, em seguida, defino um número de filas adequado à CPU. Em seguida, verifico o hash RSS para TCP/UDP e, opcionalmente, para VLAN ou tunelamento, para que os perfis de carga se mantenham distribuídos de forma adequada. Para o ruído de fundo da distribuição de interrupções, o que me ajuda é Equilíbrio de IRQ, embora prefira marcar manualmente as filas críticas. É assim que as associo Tacos adaptar-se estreitamente à topologia do anfitrião e impedir deslocações indesejáveis.

Tabela de indireção, chave RSS e ajuste fino do hash: comandos concretos

Primeiro, verifico a distribuição atual e a chave da placa de rede:

ethtool -x eth0 # Mostrar a tabela de indireção (filas de receção) e a chave RSS
ethtool -n eth0 rx-flow-hash tcp4
ethtool -n eth0 rx-flow-hash udp4

Para uma distribuição limpa e uniforme, defino a tabela de indireção para o número desejado de filas. Com 16 filas, opto por um mapeamento uniforme:

ethtool -X eth0 equal 16  Distribuição # uniformemente por 16 filas

Se for necessário, ajusto os campos de hash. Para o TCP4 com quadruplas (s=IP de origem, d=IP de destino, f=porta de origem, n=porta de destino):

ethtool -N eth0 rx-flow-hash tcp4 sdfn
ethtool -N eth0 rx-flow-hash udp4 sdfn
ethtool -N eth0 rx-flow-hash tcp6 sdfn
ethtool -N eth0 rx-flow-hash udp6 sdfn

Alguns controladores também permitem definir uma chave RSS personalizada (por exemplo, para uma melhor distribuição em casos específicos):

ethtool -X eth0 hkey   # apenas se o controlador/NIC o suportar

Definir corretamente a afinidade da CPU e o NUMA

Mapeio cada fila de receção através de Afinidade IRQ atribuir a núcleos dedicados, tendo em conta o NUMA, para que os dados percorram uma distância curta através do controlador de memória. Se a NIC estiver no Nó 0, também atribuo as filas principais a núcleos no Nó 0 e coloco as cargas de trabalho nas proximidades. Esta proximidade reduz os acessos remotos e diminui significativamente as latências de memória. Para tal, é útil dispor de um perfil para filas produtivas, bem como de núcleos separados para tarefas de gestão e de descarregamento. Quem quiser aprofundar o assunto encontrará orientações para o ajuste fino em Afinidade IRQ, no que diz respeito ao planeamento por Núcleo simplificado.

Guia prático sobre a afinidade de IRQ: das IRQ à ligação estável ao núcleo

Primeiro, determino quais são os IRQs associados às filas de receção e, em seguida, atribuo-lhes uma ligação fixa:

grep -E "eth0.*Rx" /proc/interrupts
cat /sys/class/net/eth0/device/numa_node

Defino a atribuição através de smp_affinity_list, para não ter de fazer cálculos com máscaras hexadecimais. Exemplo: filas RX 0–7 nos núcleos 2–9:

Exemplo #: Atribuir IRQs aos núcleos 2-9 (uma linha por IRQ)
echo 2  > /proc/irq//smp_affinity_list
echo 3  > /proc/irq//smp_affinity_list
echo 4  > /proc/irq//smp_affinity_list
...
echo 9  > /proc/irq//smp_affinity_list

Importante: o MSI-X tem de estar ativo para que cada fila tenha as suas próprias interrupções. Se utilizar a atribuição manual de pinos, bloqueio equilíbrio do Iraque para estes IRQs (por exemplo, através de uma lista negra) ou desative o serviço especificamente em hosts com layout estático. Verifico também o NUMA com lscpu e a atribuição PCIe, para não criar percursos entre nós.

Configuração de hash e protocolos

Defino os campos hash de forma a que os verdadeiros Tráfego-Distribuir bem os padrões, em vez de concentrá-los em poucas filas. Para TCP/UDP, utilizo o quadruplo; para IPv6, faço o mesmo, enquanto que, no caso de VXLAN ou GRE, tenho em conta campos adicionais de encapsulamento. Algumas placas de rede (NICs) oferecem chaves de hash configuráveis, que adapto à carga de trabalho dominante. Assim que deteto aglomeração de carga em filas específicas, reajusto a seleção de hash. Este passo demora pouco tempo, mas evita uma desequilíbrio com um elevado número de ligações.

Coalescência de interrupções e PPS

Combino o RSS com um uso moderado Coalescência de interrupções, para agrupar o tráfego com elevado consumo de PPS em lotes mais fáceis de gerir. Isto reduz a sobrecarga das interrupções, mas não pode prejudicar a latência de serviços mais sensíveis. Por isso, medo os tempos de ida e volta e altero os valores de coalescência gradualmente. Quem gere cargas de armazenamento ou de cópia de segurança pode agrupar de forma mais significativa do que no caso das APIs de camada 7 ou do VoIP. Em suma, eu equilibro Latência em função do caudal, até que ambos estejam em equilíbrio.

A coalescência na prática: perfis e pontos de medição

Começo com valores predefinidos moderados e vou ajustando até chegar ao ideal para cada carga de trabalho. Três perfis iniciais comprovados:

  • API/Baixa latência: rx-usecs 2–6, rx-frames 16–32, desativar a adaptação
  • Versátil: rx-usecs 8–16, rx-frames 32–64, adaptável a
  • A granel/Armazenamento: 24–48 rx-usecs, rx-frames 128–256, adaptável a
ethtool -c eth0
ethtool -C eth0 rx-usecs 12 rx-frames 64 adaptive-rx on

Para isso, medi as latências p95/p99, o PPS, a carga da CPU por núcleo e as retransmissões. Assim que detetar um aumento da variância na API/VoIP, procedo da seguinte forma: rx-usecs novamente para baixo. No que diz respeito ao armazenamento, prefiro escalar através dos fotogramas para poupar interrupções.

RSS em ambientes de 10 Gbit

Nas placas de rede de 10G, costumo trabalhar com 8 a 16 Tacos por porta, desde que a CPU disponha de núcleos suficientes. Desta forma, os servidores Web, os gateways de armazenamento e os anfitriões de virtualização escalam de forma eficiente através de várias ligações paralelas. Atribuo as filas principais a núcleos livres e, em seguida, meço o PPS, a latência e as retransmissões. Se ocorrerem perdas de pacotes, verifico a coalescência, o hash e a utilização de cada fila. Depois, faço o ajuste fino da afinidade, até que a carga de trabalho pareça uniforme.

RSS em configurações de 25 Gbit e Multi-25G

A 25 Gbit/s, o PPS e a carga do barramento aumentam, razão pela qual eu NUMA- Estou mais atento à gestão de filas e à utilização de mecanismos de descarregamento. O Large Receive Offload (LRO) ou o RSC podem reduzir a pressão de pacotes na pilha, desde que as aplicações o permitam. Além disso, verifico as pistas PCIe para excluir possíveis estrangulamentos fora da rede. Em hosts com várias ligações de 25G, separo as filas e a afinidade rigorosamente de acordo com as tarefas e os nós. Assim, utilizo Largura de banda e núcleos de forma eficiente, sem incorrer em tráfego entre nós.

Detalhes sobre hardware/drivers: o que tenho em conta

Nem todas as placas de rede (NIC) funcionam da mesma forma. As gerações da Intel (por exemplo, ixgbe, i40e, ice) oferecem funcionalidades como o Flow Director/ATR, que associam fluxos de forma específica a filas – o que é útil quando pretendo nivelar pontos de congestionamento. A Mellanox mlx5 suporta aRFS a nível de hardware, o que reduz a carga da CPU quando a pilha atende a muitos sockets. Decido caso a caso se ativo estas funcionalidades e avalio se melhoram a distribuição. Em sistemas de encaminhamento/NAT, costumo desativar o LRO e optar pelo GRO para manter a consistência dos cabeçalhos; em cargas de trabalho exclusivamente de servidor, o LRO/GRO pode ajudar a atenuar a pressão de PPS. São também importantes vetores MSI-X suficientes por fila e versões de firmware corretas.

RSS na virtualização e nos contentores

No hipervisor, combino os recursos físicos RSS-Filas com vNICs compatíveis com múltiplas filas, como o virtio-net, para que os convidados não enfrentem estrangulamentos artificiais. Presto atenção ao «CPU-pinning» das máquinas virtuais e defino a proximidade NUMA das suas vCPUs em relação à NIC física. Desta forma, os dados permanecem localmente e o anfitrião paga menos pelos acessos à memória. No caso dos contentores, associo os pods críticos a núcleos adequados e mantenho as filas do anfitrião livres de carga indesejada. Esta organização aumenta a Eficiência no caso dos microsserviços, onde surgem muitos pequenos fluxos.

Como utilizar corretamente o SR-IOV e o VF-RSS

Com o SR-IOV, atribuo às máquinas virtuais (VMs) as suas próprias VF, que, por sua vez, podem disponibilizar várias filas e RSS. Planeio um número suficiente de VF por porta, tenho em conta a capacidade MSI-X e mapeio os IRQs das VF na VM de acordo com as respetivas vCPUs. Em máquinas virtuais Linux, ativo explicitamente o Multi-Queue; caso contrário, a vNIC permanece frequentemente em modo de nível único:

# no sistema convidado (exemplo do virtio-net)
ethtool -l eth0
ethtool -L eth0 combined 4

Distribuo os hosts com várias VFs estritamente por nó NUMA e por carga de trabalho, para que as máquinas virtuais não interfiram entre si nas mesmas vias físicas de receção.

Monitorização e resolução de problemas

Eu monitorizo a utilização por Fila de espera, núcleos individuais, perdas de pacotes e retransmissões, para detetar desequilíbrios numa fase inicial. Quando um núcleo fica sobrecarregado e os outros permanecem ociosos, muitas vezes a afinidade ou o número de filas não está correto. Nesses casos, verifico os campos de hash, as máscaras de IRQ e os valores de coalescência, um a um. Além disso, analiso o Carga de SoftIRQ, porque fornece indícios de efeitos de deslocamento. Só quando esses sinais parecerem estáveis é que aumento o tráfego ou expando Tacos continuar.

RPS, RFS e XPS: complementos de software para o RSS

Se uma placa de rede tiver poucas filas ou se eu utilizar bonding/tunneling, complemento o RSS com RPS (Receive Packet Steering) e RFS (Receive Flow Steering). O RPS distribui os SoftIRQs pelos núcleos, enquanto o RFS associa os fluxos ao núcleo no qual o socket correspondente está ativo. Ativo ambos de forma seletiva:

# Aumentar globalmente o número de entradas de fluxo (RFS)
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries

# Definir CPUs por fila de receção (RPS) (máscara de exemplo, adaptar!)
for f in /sys/class/net/eth0/queues/rx-*/rps_cpus; do echo ffff > "$f"; done

Configurar a tabela de fluxo da fila de receção # para RFS
for f in /sys/class/net/eth0/queues/rx-*/rps_flow_cnt; do echo 4096 > "$f"; done

No lado do TX, utilizo XPS (Transmit Packet Steering), para que os pacotes de saída sejam enviados pelo núcleo que os gerou:

for f in /sys/class/net/eth0/queues/tx-*/xps_cpus; do echo ffff > "$f"; done

O RPS/RFS/XPS consomem algum recurso da CPU, mas ajudam quando me faltam filas a nível de hardware ou quando pretendo manter rigorosamente a localidade do socket.

Desempenho de fluxo único, GRO/TSO e Busy-Polling

Um único fluxo permanece ligado a um núcleo por uma boa razão. Se pretender aumentar a largura de banda de um único fluxo, recorro a offloads (GRO/TSO), a uma frequência elevada do núcleo e a uma coalescência adequada. Para percursos em que a latência é crítica, pode Sondagem ocupada ajudar:

Definir o valor de # para um valor baixo e efetuar a medição
sysctl -w net.core.busy_read=25
sysctl -w net.core.busy_poll=25

O Busy-Polling reduz as mudanças de contexto, mas consome tempo de CPU. Só o ativo nos casos em que as latências p99 são importantes e valido sempre os efeitos na carga total e na latência de cauda. Normalmente, mantenho o GRO ativado nos servidores; quanto ao LRO, utilizo-o consoante a função; no caso das «middleboxes», opto por uma abordagem conservadora, para não interferir com o processamento de cabeçalhos e a consistência do hash.

Recomendações e exemplos: filas, afinidade, comandos

Como ponto de partida, escolho um número de filas que corresponda à CPU ajuste, observe depois a carga por fila e vá ajustando gradualmente. Com 10G, muitas vezes bastam 8 a 16 filas; com 25G, costumo definir um número mais elevado, desde que haja núcleos disponíveis. Para a afinidade, utilizo máscaras claras por IRQ, para facilitar a análise posterior dos percursos. A tabela seguinte fornece valores de referência concisos, que posteriormente verifico através de medições. Só os resultados das medições determinam se eu mais aumentar ou diminuir.

Velocidade da ligação Filas RX típicas Comandos de exemplo Notas
10 Gbit/s 8-16 ethtool -l eth0 | ethtool -L eth0 rx 16 Coalescência manter a intensidade moderada, verificar a latência L7
25 Gbit/s 16–32+ grep . /proc/interrupts | Máscaras de IRQ por eco NUMA ter em conta, verificar as pistas PCIe
Multi-25G Por porta, separadamente Ativar o vNIC Multi-Queue (por exemplo, virtio) Filas por núcleos e Cargas de trabalho dividir

Estes valores de referência constituem apenas o ponto de partida, não o objetivo, uma vez que as cargas de trabalho variam consideravelmente. Registo as alterações, faço medições antes e depois do ajuste e, fora isso, mantenho o ambiente inalterado. Assim que o sistema se mantiver estável sob carga de produção, congelo a configuração. Posteriormente, repito as medições após atualizações do kernel ou dos controladores. Desta forma, mantenho-me fiel a RSS Em bom caminho e com resultados seguros e reproduzíveis.

Dificuldades típicas e medidas para as contornar

Muito poucos Tacos Sobrecarregam núcleos individuais; em número excessivo, aumentam a carga administrativa e prejudicam a taxa de acertos na cache. Uma afinidade mal definida redireciona as interrupções para núcleos já sobrecarregados ou para nós NUMA incorretos. Além disso, um hash inadequado faz com que fluxos dominantes entupam as filas. Resolvo isto passo a passo: ajustar o número de filas, corrigir a afinidade, expandir os campos de hash, afinar a coalescência. Documento cada alteração com Métricas, antes de passar para a alavanca seguinte.

Cenários práticos

Um servidor de armazenamento com 10G beneficia rapidamente de 8–12 Tacos além de uma coalescência moderada, para garantir que as transferências em massa decorram sem problemas. Um servidor de API com um elevado número de ligações necessita frequentemente de campos de hash mais refinados e de uma menor latência nas interrupções. Os anfitriões de virtualização beneficiam significativamente quando o vNIC Multi-Queue está ativo no lado do convidado e se adapta à configuração do anfitrião. As cargas de trabalho em contentores funcionam melhor quando os pods críticos são executados perto da NIC e da memória NUMA. Alargo estes padrões consoante a situação, ao PPS, compare as retransmissões e a distribuição das filas.

As plataformas de alto desempenho como vantagem

Configurações de alojamento com RSS, as placas de rede com múltiplas filas (Multi-Queue-NICs) e uma afiniidade bem definida proporcionam reservas percetíveis em picos de carga. Quem estiver a avaliar ofertas de servidores deve informar-se especificamente sobre a capacidade de múltiplas filas, o NUMA-Pinning e a monitorização. Um fornecedor que implemente visivelmente estes aspetos alcança frequentemente curvas de débito visivelmente melhores. Para soluções de servidores e alojamento de alto desempenho, recomendo claramente a webhoster.de. Esta orientação compensa em Desempenho e estabilidade, especialmente quando há muitos fluxos paralelos.

Resumo para a prática

Eu ativo Receber Side Scaling: defina um número razoável de filas, atribua IRQs aos núcleos adequados e verifique a configuração do hash. Em seguida, otimizo o coalescing em função da latência, presto atenção à proximidade NUMA e distribuo as cargas de trabalho de forma consistente. Na virtualização, utilizo o Multi-Queue até aos sistemas convidados e mantenho o pinning e a afinidade sincronizados. As medições relativas a PPS, carga das filas, retransmissões e latência determinam o próximo passo. Quem proceder desta forma tira o máximo partido de 10G e 25G na prática, mantendo Latência no âmbito deste contexto e obtém, de forma fiável, um retorno da rede.

Artigos actuais