Estratégias relacionadas à otimização de desempenho de rede Linux

Este artigo apresenta a estratégia de otimização do desempenho da rede Linux de baixo para cima "

00

Otimização da configuração da placa de rede

A partir de 0 está a qualidade básica dos produtores de código

Configuração da função da placa de rede

De modo geral, para completar a mesma função, o desempenho do hardware excede em muito o do software. Com o desenvolvimento do hardware, mais e mais funções são suportadas. Portanto, devemos tentar descarregar a função para o hardware

Use ethtool -k para ver a lista de funções suportadas pela placa de rede e o status atual. A seguir está a saída de uma máquina virtual do autor.
Insira a descrição da imagem aqui

Nota: A saída de diferentes placas de rede é diferente, e a saída de diferentes versões do kernel será um pouco diferente.

Geralmente, as seguintes funções precisam ser ativadas:

  1. rx-checksumming: verifica o checksum da mensagem recebida.

  2. tx-checksumming: Calcula a soma de verificação das mensagens enviadas.

  3. scatter-collect: suporta o modo de memória scatter-collect, ou seja, a memória da parte dos dados da mensagem enviada pode ser descontínua e dispersa em várias páginas.

  4. tcp-segment-offload: suporta segmentação de grandes pacotes TCP.

  5. udp-fragmentation-offload: Suporta fragmentação automática de pacotes UDP.

  6. genérico-segmento-offload: Ao usar TSO e UFO, esta função geralmente é ativada. Tanto o TSO quanto o UFO são suportados pelo hardware da placa de rede, enquanto o GSO é implementado principalmente por software na camada de driver do Linux. Para encaminhar equipamentos, eu pessoalmente recomendo não habilitar o GSO. Ele foi testado antes que ligar o GSO aumentará o atraso de encaminhamento.

  7. rx-vlan-offload: É habilitado quando implantado em um ambiente de rede vlan.

  8. tx-vlan-offload: mesmo que acima

  9. recebimento de hash: Se você usar a função RPS / RFS do software, ative-a.

Você pode usar ethtool -K para habilitar funções específicas.

[Benefícios do artigo] Materiais de aprendizagem do arquiteto do servidor Linux C / C ++ mais o grupo 812855908 (dados incluindo C / C ++, Linux, tecnologia golang, Nginx, ZeroMQ, MySQL, Redis, fastdfs, MongoDB, ZK, mídia de streaming, CDN, P2P, K8S, Docker, TCP / IP, corrotina, DPDK, ffmpeg, etc.)
Insira a descrição da imagem aqui

Configuração de buffer de anel da placa de rede

O buffer de anel padrão do driver da placa de rede geralmente não é grande. Considere a máquina virtual do autor como exemplo.
Insira a descrição da imagem aqui

O tamanho de recebimento e envio é de 256 bytes, o que é obviamente pequeno. Ao encontrar tráfego de rajada, isso pode fazer com que o buffer de recebimento do anel da placa de rede fique cheio e perca o pacote.
Em servidores ou dispositivos de encaminhamento de alto desempenho e alto tráfego, geralmente é configurado como 2048 ou superior. E, se o autor se lembrar corretamente, no driver da placa de rede intel, é recomendado definir o tamanho do buffer de envio para duas vezes o tamanho do buffer de recebimento - o motivo não é especificado.
Use ethtool -G para definir o tamanho do buffer em anel da placa de rede. O autor geralmente define para 2048 e 4096. Se for um dispositivo de encaminhamento, ele pode ser definido como maior.

Configurações de interrupção

A maioria das placas de rede atuais são placas de rede com várias filas e cada fila tem uma interrupção independente. Para melhorar os recursos de processamento simultâneo, precisamos distribuir diferentes interrupções para diferentes núcleos da CPU.
Verifique o status das interrupções rígidas por cat / proc / interrupts.
Insira a descrição da imagem aqui

Na imagem acima, as interrupções da placa de rede da máquina virtual do autor são distribuídas de maneira relativamente uniforme em diferentes núcleos de CPU.
Veja a afinidade de CPU da interrupção correspondente
Insira a descrição da imagem aqui

As interrupções correspondentes a diferentes filas de recebimento / envio são alocadas para CPU0 ~ 7.

Por padrão, o smp_affinity correspondente à interrupção é geralmente definido como ff, ou seja, a interrupção pode ser distribuída para todos os núcleos. Neste momento, parece que todas as interrupções de fila podem ser distribuídas para qualquer núcleo, o que em teoria parece ser melhor do que o núcleo especificado acima. No entanto, o efeito real muitas vezes não é o caso. Isso depende do hardware e da implementação do sistema operacional. Na experiência do autor, não encontrei uma situação em que a carga de interrupção de disco rígido seja muito equilibrada após smp_affinity ser definido como ff. Geralmente, eles são distribuídos para alguns núcleos designados, enquanto outros núcleos recebem apenas algumas interrupções.

Portanto, em geral, atribuímos diferentes filas de recebimento da placa de rede a diferentes CPUs em ordem. Nesse momento, surgiu um problema. Como a placa de rede decide em qual fila colocar a mensagem?

Configurações de RSS da placa de rede

A placa de rede também usa a operação hash para determinar em qual fila de recepção colocar a mensagem. Embora não possamos alterar o algoritmo de hash, podemos definir a chave de hash, que é calculada por qual campo da mensagem, o que afeta o resultado final.
Use ethtool --show-tuple para ver o protocolo especificado.
Diferentes placas de rede têm diferentes recursos RSS, e os protocolos suportados e os campos que podem ser definidos também são diferentes. Mas o que é estranho é que a chave padrão do protocolo UDP é diferente do TCP, apenas IP de origem + IP de destino. Desta forma, ao fazer o teste de desempenho UDP, devemos prestar atenção especial ao uso do mesmo dispositivo que o cliente, e os pacotes UDP gerados serão distribuídos apenas para uma fila, resultando em apenas uma interrupção de processamento da CPU no servidor, que irá afetam os resultados do teste.
Portanto, geralmente temos que alterar a chave do RSS UDP por meio de ethtool --config-tuple para torná-lo semelhante ao TCP.

01
-

Estratégia ideal para receber direção

Agora comece a entrar na estratégia de otimização do campo do software.

Mecanismo NAPI

Os drivers de dispositivo de rede Linux modernos geralmente oferecem suporte ao mecanismo NAPI, que integra interrupções e pesquisa. Uma interrupção pode pesquisar o dispositivo várias vezes. Isso pode ter as vantagens de interrupção e pesquisa. —— Obviamente, para equipamento de encaminhamento puro, a votação pode ser usada diretamente. Então, quantas pesquisas você tem que pesquisar para uma interrupção? Ele pode ser configurado em / proc / sys / net / core / netdev_budget, o padrão é 300. Isso é compartilhado por todos os dispositivos. Ao receber o processamento de interrupção suave, é possível que vários dispositivos de rede tenham acionado a interrupção, que é adicionada à lista NAPI. Então, o orçamento compartilhado por esses dispositivos é 300, e cada dispositivo tem uma chamada NAPI, e o número máximo de pesquisas geralmente é escrito diretamente no driver, geralmente 64. Aqui, seja 64 ou 300, refere-se ao número máximo de enquetes.Se não houver mensagens preparadas no hardware, mesmo que o número de orçamentos não seja atingido, ele será encerrado. Se o dispositivo tiver mensagens o tempo todo, a interrupção suave de recebimento sempre coletará as mensagens até o número do orçamento.
Quando a interrupção suave ocupa muita CPU, ela fará com que o aplicativo nesta CPU não seja agendado. Portanto, o valor do orçamento deve ser selecionado de acordo com o negócio. Porque o tempo de processamento de diferentes pacotes de protocolo é diferente, e não é intuitivo controlar o uso da CPU para receber interrupções suaves definindo o número do orçamento. Portanto, na nova versão do kernel, um novo parâmetro netdev_budget_usecs é introduzido para controlar o tempo máximo da CPU para receber interrupções suaves.

RPS e RFS
na era em que não havia placa de rede com várias filas, a placa de rede só pode gerar uma interrupção e enviá-la para uma CPU. Neste momento, como usar multi-core para melhorar a capacidade de processamento paralelo? A RPS nasceu para resolver esse problema. O RPS é semelhante ao RSS da placa de rede, exceto que a CPU calcula um valor de hash de acordo com o protocolo de mensagem e, em seguida, usa esse valor de hash para selecionar uma CPU e armazenar a mensagem na fila de recebimento da CPU. E envie uma interrupção IPI para a CPU de destino para notificá-la para processar. Nesse caso, mesmo que apenas uma CPU receba a interrupção, o RPS pode distribuir a mensagem para várias CPUs novamente.
Ao gravar no arquivo / sys / class / net / ethx / queues / rx-0 / rps_cpus, você pode definir para quais CPUs a fila de recebimento da placa de rede pode distribuir.
RFS é semelhante ao RPS, exceto pela letra do meio, o primeiro é Flow e o último é Packet. Isso também explica seu princípio de realização. O RPS é distribuído totalmente de acordo com as características da mensagem atual, enquanto o RFS leva em consideração o fluxo - o fluxo aqui não é um fluxo simples, mas considera o comportamento da "aplicação", ou seja, qual núcleo da CPU processou esse fluxo da última vez A CPU é a CPU de destino.
Agora que existem placas de rede com várias filas e você pode definir um ntuplo personalizado para afetar o algoritmo de hash, o RPS não tem muita utilidade.
O RFS também entra na poeira da história? Eu pessoalmente acho que é negativo. Imagine, no cenário a seguir, um serviço S é implantado em um servidor de 8 núcleos e seus 6 threads de trabalho ocupam CPU 0-5, e a CPU 6-7 restante é responsável pelo processamento de outros serviços. Como há 8 núcleos de CPU, a fila da placa de rede geralmente é definida como 8. Supondo que essas 8 filas correspondam à CPU0 ~ 7, o problema surge. A mensagem de serviço do serviço S é recebida pela placa de rede e, após o cálculo do RSS, é colocada na fila 6. A interrupção correspondente também é enviada à CPU6, mas o thread do serviço S não está rodando na CPU6, e os dados a mensagem é acrescentada a 6. O soquete em um thread de trabalho recebe o buffer. Por um lado, pode haver uma relação de competição com a operação de leitura do thread de trabalho em execução. Por outro lado, quando o thread de trabalho correspondente lê a mensagem, os dados da mensagem precisam ser relidos no cache do correspondente CPU. RSS pode resolver esse problema.Quando um thread de trabalho processa uma mensagem de soquete, o kernel registra que a mensagem é processada por uma determinada CPU e salva esse relacionamento de mapeamento em uma tabela de fluxo. Desta forma, mesmo que a CPU6 receba uma interrupção, ela encontra a entrada correspondente na tabela de fluxos de acordo com as características do protocolo de mensagens, devendo a mensagem ser processada pela CPU3. Neste momento, a mensagem será armazenada na fila de backlog da CPU3, evitando os problemas acima.

XPS
RPS e RFS são usados ​​para estabelecer a relação entre a fila de recebimento e a CPU de processamento, e XPS pode ser usado não apenas para estabelecer a relação entre a fila de envio e a CPU de processamento, mas também para estabelecer a relação entre a fila de recebimento e a fila de envio. O primeiro é quando o envio é concluído, seu trabalho é concluído pela CPU designada, e o último é selecionar a fila de envio por meio da fila de recebimento.

netfilter e nf_conntrack
netfilter é a implementação da ferramenta iptables no kernel, e seu desempenho é médio, especialmente quando há um grande número de regras ou quando são usadas condições de correspondência estendidas. Use de acordo com a sua situação. E nf_conntrack é a função de rastreamento de conexão exigida pelo netfilter como um firewall com estado. Na versão inicial do Linux, a tabela de sessão usava um grande bloqueio global, que prejudica o desempenho. Em um ambiente de produção, geralmente não é recomendado carregar este módulo, portanto, firewall dinâmico, NAT, synproxy, etc. não podem ser usados.

early_demux switch
Alunos que estão familiarizados com o kernel do Linux sabem que depois que o Linux recebe uma mensagem, ele irá procurar na tabela de roteamento para determinar se a mensagem é enviada para a máquina ou encaminhada. Se for determinado que ele é enviado para a máquina local, é necessário descobrir para qual socket ele é enviado de acordo com o protocolo de 4 camadas. Duas pesquisas estão envolvidas aqui e, para TCP estabelecido e alguns UDPs, a "conexão" foi concluída e a rota pode ser considerada "imutável", portanto, as informações de roteamento da "conexão" podem ser armazenadas em cache. Depois de abrir / proc / sys / net / ipv4 / tcp_early_demux ou udp_early_demux, as duas pesquisas acima podem ser combinadas em uma. Após o kernel receber a mensagem, se o protocolo da camada 4 habilitar early_demux, ele pesquisará o socket com antecedência e, se o encontrar, usará diretamente o resultado do roteamento armazenado em cache no socket. Para o dispositivo de encaminhamento, essa chave não precisa ser ligada - porque o dispositivo de encaminhamento está principalmente encaminhando e não tem programa de serviço nativo.

Habilitando busy_poll
busy_poll foi inicialmente denominado Sockets de Baixa Latência para melhorar o problema de atraso dos pacotes de processamento do kernel. A ideia principal é que ao fazer chamadas de sistema de soquete, como operações de leitura, a camada de soquete chama diretamente o método da camada de driver para pesquisar mensagens lidas dentro de um tempo especificado, o que pode aumentar a capacidade de processamento do PPS em várias vezes.
Existem duas configurações de nível de sistema para busy_poll: a primeira é / proc / sys / net / core / busy_poll, que define o período de tempo limite para execução de poll de ocupado durante chamadas de sistema de seleção e poll, em nós. O segundo é / proc / sys / net / core / busy_read, que define o período de tempo limite de busy_poll durante as operações de leitura, e a unidade também somos nós.
Pelo resultado do teste, o efeito de busy_poll é óbvio, mas também tem limitações. Somente quando a fila de recebimento de cada placa de rede tiver e apenas um aplicativo puder lê-la, o desempenho pode ser melhorado. Se vários aplicativos estiverem executando pesquisas de ocupado em uma fila de recebimento ao mesmo tempo, um agendador precisa ser introduzido para tomar uma decisão, o que aumentará o consumo em vão.

Tamanho do buffer de recebimento O buffer de recebimento do
soquete do Linux tem duas configurações, uma é o tamanho padrão e a outra é o tamanho máximo. Ele pode ser obtido usando sysctl -a | grep rmem_default / max ou lendo / proc / sys / net / core / rmem_default / max. De modo geral, os valores padrão dessas duas configurações do Linux são um pouco pequenos para o programa de serviço (cerca de algumas centenas de k). Então, podemos aumentar o tamanho padrão e o valor máximo do buffer de recebimento por meio de sysctl ou gravando diretamente no arquivo proc acima, de modo a evitar que os aplicativos de tráfego intermitente sejam tarde demais para lidar com a situação de que o buffer de recebimento está cheio e perda de pacotes .

Parâmetro de configuração TCP
/ proc / sys / net / ipv4 / tcp_abort_overflow: controla o comportamento quando uma conexão TCP é estabelecida, mas a fila de backlog está cheia. O padrão é geralmente 0 e o comportamento é retransmitir syn + ack, então o par retransmitirá ack. Quando o valor for 1, o RST será enviado diretamente. O primeiro é um tratamento relativamente suave, mas não é fácil expor o problema do backlog total.Você pode definir um valor adequado de acordo com o seu negócio.

/ proc / sys / net / ipv4 / tcp_allowed_congestion_control: exibe o algoritmo de controle de fluxo TCP compatível com o sistema atual

/ proc / sys / net / ipv4 / tcp_congestion_control: Configure o algoritmo de controle de fluxo TCP usado pelo sistema atual, que precisa ser o algoritmo mostrado acima.

/ proc / sys / net / ipv4 / tcp_app_win: usado para ajustar o tamanho do cache na camada do aplicativo e a alocação da janela TCP.

/ proc / sys / net / ipv4 / tcp_dsack: ativa ou não o SACK duplicado.

/ proc / sys / net / ipv4 / tcp_fast_open: se deve habilitar a extensão TCP Fast Open. Esta extensão pode melhorar o tempo de resposta das comunicações de longa distância.

/ proc / sys / net / ipv4 / tcp_fin_timeout: Usado para controlar o tempo limite de espera do pacote FIN da extremidade oposta após o final local desligar ativamente.É usado para evitar ataques DOS, em segundos.

/ proc / sys / net / ipv4 / tcp_init_cwnd: tamanho da janela de congestionamento inicial. Você pode definir um valor maior conforme necessário para melhorar a eficiência da transmissão.

/ proc / sys / net / ipv4 / tcp_keepalive_intvl: O intervalo para enviar pacotes keepalive.

/ proc / sys / net / ipv4 / tcp_keepalive_probes: Nenhuma resposta de mensagem de keepalive é recebida, o número máximo de keepalive enviado.

/ proc / sys / net / ipv4 / tcp_keepalive_time: O tempo ocioso para a conexão TCP enviar keepalive.

/ proc / sys / net / ipv4 / tcp_max_syn_backlog: O comprimento da fila do handshake TCP de três vias que não recebeu a confirmação do cliente. Para o servidor, ele precisa ser ajustado para um valor maior.

/ proc / sys / net / ipv4 / tcp_max_tw_buckets: O número de soquetes cujo TCP está no estado TIME_WAIT, usado para defesa contra ataques simples do DOS. Depois que esse número for excedido, o soquete será fechado diretamente.

/ proc / sys / net / ipv4 / tcp_sack: Defina se deseja ativar o SACK, ele é ativado por padrão.

/ proc / sys / net / ipv4 / tcp_syncookies: usado para evitar ataques de inundação de sincronização. Quando a fila de backlog de sincronização estiver cheia, o syncookie será usado para verificar o cliente.

/ proc / sys / net / ipv4 / tcp_window_scaling: Defina se deseja ativar a função de extensão de escala de janela TCP. Você pode notificar a outra parte sobre uma janela de recepção maior para melhorar a eficiência da transmissão. Ativado por padrão.

Opção de soquete comumente usada
SO_KEEPALIVE: se deve habilitar KEEPALIVE.

SO_LINGER: Defina o período de tempo limite para o soquete "desligamento normal" (meu nome pessoal). Quando a opção LINGER é habilitada, ao chamar o fechamento ou desligamento, se houver dados no buffer de envio do socket, ele não retornará imediatamente, mas aguardará o envio da mensagem ou até o tempo limite de LINGER. Há uma situação especial aqui, LINGER está habilitado, mas o tempo de LINGER é 0, o que acontecerá? Envia RST diretamente para a extremidade oposta.

SO_RCVBUFF: Defina o tamanho do buffer de recepção do socket.

SO_RCVTIMEO: Defina o tempo limite para o recebimento de dados.Para o programa de serviço, geralmente é sem bloqueio, ou seja, definido como 0.

SO_REUSEADDR: Se deve verificar se o endereço vinculado e a porta estão em conflito. Por exemplo, se uma porta foi vinculada a ANY_ADDR, nenhum endereço local pode ser usado posteriormente para vincular a mesma porta. Para o programa de serviço, é recomendável abri-lo para evitar a falha do endereço de ligação quando o programa for reiniciado.

SO_REUSEPORT: Permite vincular exatamente o mesmo endereço e porta. Mais importante, quando a mensagem recebida pelo kernel pode corresponder a vários sockets com o mesmo endereço e porta, o kernel irá alternar automaticamente entre esses sockets. Obtenha balanceamento de carga.

Outros parâmetros do sistema
Número máximo de descritores de arquivo: Para programas de serviço TCP, cada conexão ocupa um descritor de arquivo, portanto, o número máximo padrão de descritores de arquivo está longe de ser suficiente. Precisamos aumentar o limite máximo do descritor do sistema e do processo ao mesmo tempo. O primeiro pode usar / proc / sys / fs / file-max,
você pode usar as configurações / proc / sys / fs / file-max ou sysctl -n fs.file-max = xxxxxx. O último pode ser definido usando ulimit -n ou setrlimit.
Vincular CPU: Cada thread do programa de serviço é vinculado à CPU especificada. Você pode usar os comandos taskset ou cgroup para vincular um thread de serviço especificado a uma CPU especificada ou a um conjunto de CPUs. Você também pode chamar pthread_setaffinity_np para realizar. Vinculando a thread especificada à CPU, por um lado, a popularidade do cache (alta ocorrência) pode ser garantida e, por outro lado, a distribuição da carga da CPU de acordo com o negócio pode ser alcançada.

03
-

ignorar o kernel

O método anterior era principalmente para otimizar o desempenho da rede do Linux ajustando os parâmetros do kernel, mas para o programa de serviço da camada de aplicativo, ainda existem vários problemas que não podem ser evitados, como a cópia de dados dentro e fora do kernel. Assim nasceu o esquema de bypass do kernel, como dpdk, netmap, pfring, etc., entre os quais dpdk é o mais amplamente utilizado. Comparado com o kernel, ele tem três vantagens: 1. Evite a cópia de dados dentro e fora do kernel; 2. Use páginas grandes para melhorar a taxa de acerto de TLB; 3. Use poll por padrão para melhorar o desempenho da rede.

No entanto, essas ferramentas para enviar e receber pacotes ainda não podem conter uma pilha de protocolo completa e ferramentas de rede como o kernel. —— Claro, o DPDK já tem muitas bibliotecas e ferramentas.

Para equipamento de encaminhamento de rede, basicamente, apenas os pacotes de segunda e terceira camadas são processados, o que não requer altas pilhas de protocolo. Para o programa de serviço, uma pilha de protocolo mais completa é necessária. Atualmente existem soluções DPDK + mtcp, DPDK + fstack e DPDK + Nginx.

Como este artigo se concentra na melhoria do desempenho da rede Linux, o esquema de desvio é apenas uma introdução.

Preste atenção na conta oficial e compartilhe mais conteúdo de tecnologia da Internet de seu interesse!
Insira a descrição da imagem aqui

Acho que você gosta

Origin blog.csdn.net/qq_40989769/article/details/111281977
Recomendado
Clasificación