Receive Side Scaling distribuisce in modo mirato il traffico di rete su più core tramite collegamenti a 10 e 25 Gbit/s, consentendo ai server Linux di gestire elevate velocità di trasmissione con bassa latenza. Vi mostrerò in modo pratico come attivo RSS, che Spunti sulla mappa di Core, evitando così colli di bottiglia a livello di interrupt e di hit della cache.
Punti centrali
Riassumo brevemente gli aspetti più importanti, in modo che tu possa pianificare rapidamente i prossimi passi.
- Distribuzione del carico: I pacchetti vengono distribuiti su più core attraverso diverse code.
- Località della cache: Un flusso rimane sempre sulla stessa coda.
- Hashing: L'hash a 4 elementi distribuisce i flussi in modo uniforme tra le code.
- affinità: Una mappatura mirata degli IRQ riduce le latenze.
- Scala: A partire da 10/25 Gbit/s, RSS garantisce un’elevata velocità di trasmissione.
Questi punti sono strettamente interconnessi e costituiscono la Prestazioni su carichi di lavoro reali. Do la priorità innanzitutto al numero corretto di code, poi alla CPU-Affinità. Successivamente controllo i parametri hash e le impostazioni di precisione.
Cosa offre il Receive Side Scaling
RSS suddivide la ricezione dei pacchetti in più Code di ricezione, che assegno a specifici core della CPU, in modo che nessun singolo core diventi un collo di bottiglia. Ciò riduce i picchi di interrupt “hard” e uniforma l’elaborazione tramite SoftIRQ, riducendo i picchi di latenza e aumentando la velocità di elaborazione. Ogni coda genera i propri interrupt, che associo in modo fisso a determinati core per mantenere la coerenza dei percorsi dei dati. Questa coerenza favorisce la Cache-Località, poiché un flusso incontra sempre lo stesso nucleo. È proprio questa interazione che, in presenza di elevati tassi di PPS, si traduce direttamente in un’efficienza misurabile.
Ecco come funziona tecnicamente l'RSS
Il NIC crea, a partire dall'IP di origine/destinazione e dalla porta di origine/destinazione, un Hash e lo utilizza come indice per la tabella di indirezione, che punta alle code. In questo modo, i pacchetti di un flusso finiscono sempre nella stessa coda e rimangono quindi associati allo stesso kernel. I diversi flussi si distribuiscono in modo uniforme, a condizione che le chiavi hash e i campi di protocollo siano configurati in modo appropriato. Il carico di lavoro si sposta quindi vicino alla Hardware, il che riduce il carico di bilanciamento del kernel e diminuisce l'overhead. È proprio quello che voglio, per mantenere basso il numero di pacchetti elaborati per core a 10G/25G.
Perché l'RSS conta a partire da 10 e 25 Gbit/s
A 1 Gbit/s, spesso basta un singolo Nucleo il carico dei pacchetti, ma a partire da 10 Gbit/s l’equilibrio si altera rapidamente. I pacchetti di piccole dimensioni fanno aumentare il numero di PPS, per cui un core raggiunge rapidamente il 100% di carico e si verificano dei drop. Proprio in quel momento l’RSS agisce come un moltiplicatore della larghezza di banda utilizzabile. Distribuisco il carico su più Nuclei, riduce i cambi di contesto e mantiene le curve di latenza più stabili. Il risultato: solo con un RSS pulito la velocità effettiva si avvicina alla velocità di collegamento.
Configurare RSS sul server Linux
Su Linux gestisco i feed RSS principalmente tramite ettool, le opzioni del driver e sysfs, affinché le funzionalità della scheda di rete vengano effettivamente sfruttate. Per prima cosa rilevo il numero massimo di canali RX, poi imposto un numero di code adeguato alla CPU. Successivamente, verifico l’hash RSS per TCP/UDP e, facoltativamente, per VLAN o tunneling, in modo che i profili di carico rimangano distribuiti correttamente. Per il rumore di fondo della distribuzione degli interrupt mi aiuta Bilanciamento IRQ, anche se preferisco fissare manualmente le code critiche. Ecco come le associo Spunti si adatti perfettamente alla topologia dell'host e impedisca spostamenti indesiderati.
Tabella di indirezione, chiave RSS e ottimizzazione dell'hash: comandi specifici
Per prima cosa controllo la configurazione corrente e la chiave della scheda di rete:
ethtool -x eth0 # Visualizza la tabella di indirezione (code di ricezione) e la chiave RSS
ethtool -n eth0 rx-flow-hash tcp4
ethtool -n eth0 rx-flow-hash udp4
Per ottenere una distribuzione pulita e uniforme, imposto la tabella di indirezione sul numero desiderato di code. Con 16 code, scelgo una mappatura uniforme:
ethtool -X eth0 equal 16 Distribuzione # uniforme su 16 code
Se necessario, adeguerò i campi hash. Per TCP4 con quartette (s=IP sorgente, d=IP destinazione, f=porta sorgente, n=porta destinazione):
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
Alcuni driver consentono anche di impostare una propria chiave RSS (ad esempio, per una migliore distribuzione in casi particolari):
ethtool -X eth0 hkey # solo se il driver/la scheda di rete lo supportano
Impostare correttamente l'affinità della CPU e il NUMA
Mappo ogni coda RX tramite Affinità IRQ Assegna le code ai core dedicati, tenendo conto della struttura NUMA, in modo che i dati transitino per brevi tratti attraverso il controller di memoria. Se la scheda di rete (NIC) è sul nodo 0, associo anche le code principali ai core del nodo 0 e colloco i carichi di lavoro nelle vicinanze. Questa vicinanza riduce gli accessi a distanza e abbassa notevolmente le latenze di memoria. A tal fine è utile un profilo per le code di produzione, nonché core separati per le attività di gestione e di offload. Chi desidera approfondire l’argomento troverà indicazioni per la messa a punto in Affinità IRQ, per quanto riguarda la pianificazione per Core semplificato.
Guida pratica sull'affinità IRQ: dagli IRQ all'assegnazione stabile dei core
Per prima cosa individuo quali IRQ appartengono alle code RX, quindi le assegno in modo fisso:
grep -E "eth0.*Rx" /proc/interrupts
cat /sys/class/net/eth0/device/numa_node
Effettuo l'assegnazione tramite smp_affinity_list, così non devo fare calcoli complicati. Esempio: code RX da 0 a 7 sui core da 2 a 9:
Esempio #: assegnare gli IRQ ai core 2-9 (una riga per ogni 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: MSI-X deve essere attivo affinché ogni coda disponga dei propri interrupt. Se utilizzo il pinning manuale, blocco irqbalance per questi IRQ (ad esempio tramite blacklist) oppure disattivo il servizio in modo mirato sugli host con layout statico. Verifico inoltre il NUMA con lscpu e l'assegnazione PCIe, in modo da non creare percorsi cross-node.
Configurazione hash e protocolli
Definisco i campi hash in modo tale che i veri Traffico-Distribuire bene i modelli, invece di concentrarli su poche code. Per TCP/UDP utilizzo il quadruplo, per IPv6 in modo simile, mentre per VXLAN o GRE prendo in considerazione campi aggiuntivi relativi all’incapsulamento. Alcune schede di rete (NIC) offrono chiavi di hash configurabili, che adatto al carico di lavoro dominante. Non appena rilevo un accumulo di carico su singole code, adeguo la selezione delle chiavi di hash. Questa operazione richiede poco tempo, ma impedisce un squilibrio in presenza di un elevato numero di connessioni.
Coalescenza degli interrupt e PPS
Abbino l'RSS a un uso moderato Interruzione della coalescenza, per raggruppare il traffico ad alta intensità di PPS in batch gestibili. Ciò riduce l’overhead degli interrupt, ma non deve compromettere la latenza dei servizi più sensibili. Per questo motivo misuro i tempi di andata e ritorno e modifico gradualmente i valori di coalescing. Chi gestisce carichi di storage o backup può raggruppare i dati in modo più marcato rispetto alle API L7 o al VoIP. In sintesi, bilancio Latenza rispetto alla portata, finché entrambi i valori non sono coerenti.
Il coalescente nella pratica: profili e punti di misurazione
Comincio con impostazioni predefinite moderate e cerco di individuare la configurazione ottimale per ogni carico di lavoro. Tre profili di partenza collaudati:
- API/Bassa latenza:
rx-usecs 2–6,rx-frames 16–32, disattivato in modo adattivo - A tutto tondo:
rx-usecs 8–16,rx-frames 32–64, adattivo a - All'ingrosso/Stoccaggio:
24–48 rx-usecs,rx-frames 128–256, adattivo a
ethtool -c eth0
ethtool -C eth0 rx-usecs 12 rx-frames 64 adaptive-rx on
A tal fine misuro le latenze p95/p99, il PPS, il carico della CPU per core e le ritrasmissioni. Non appena noto un aumento della varianza per API/VoIP, procedo con rx-usecs di nuovo giù. Per quanto riguarda lo storage, tendo ad aumentare la scalabilità tramite frame, per risparmiare sugli interrupt.
RSS in ambienti a 10 Gbit
Sulle schede di rete da 10G, di solito lavoro con valori compresi tra 8 e 16 Spunti per porta, a condizione che la CPU metta a disposizione un numero sufficiente di core. In questo modo, i server web, i gateway di archiviazione e gli host di virtualizzazione scalano in modo ottimale su numerose connessioni parallele. Assegno le code principali ai core liberi e poi misuro PPS, latenza e ritrasmissioni. Se si verificano perdite di pacchetti, controllo il coalescing, l’hash e l’utilizzo per ciascuna coda. Successivamente ottimizzo la affinità, finché il carico di lavoro non appare uniforme.
RSS in configurazioni a 25 Gbit e Multi-25G
Con 25 Gbit/s aumentano il PPS e il carico della bus, motivo per cui io NUMA-Presto maggiore attenzione alla gestione delle code e agli offload. Il Large Receive Offload (LRO) o l’RSC possono ridurre il carico di pacchetti sullo stack, a condizione che le applicazioni lo consentano. Inoltre, controllo le lane PCIe per escludere eventuali colli di bottiglia al di fuori della rete. Negli host con più collegamenti a 25G, separo rigorosamente le code e l’affinità in base alle attività e ai nodi. In questo modo utilizzo Larghezza di banda e i core in modo efficiente, senza incorrere in traffico tra nodi.
Dettagli su hardware e driver: a cosa faccio attenzione
Non tutte le schede di rete si comportano allo stesso modo. Le generazioni Intel (ad es. ixgbe, i40e, ice) offrono funzionalità come Flow Director/ATR, che associano in modo mirato i flussi alle code – utili quando voglio livellare i picchi di traffico. Mellanox mlx5 è in grado di supportare aRFS a livello hardware, il che riduce il carico della CPU quando lo stack gestisce molti socket. Decido caso per caso se attivare queste funzioni e verifico se migliorano la distribuzione. Sui sistemi di routing/NAT spesso disattivo LRO e utilizzo GRO per mantenere la coerenza delle intestazioni; su carichi di lavoro esclusivamente server, LRO/GRO possono aiutare a smorzare la pressione PPS. Sono inoltre importanti un numero sufficiente di vettori MSI-X per coda e versioni corrette del firmware.
RSS nella virtualizzazione e nei container
Nell'hypervisor combino le risorse fisiche RSS-Code con vNIC compatibili con il multi-queue, come ad esempio virtio-net, affinché gli ospiti non subiscano colli di bottiglia artificiali. Presto attenzione al CPU-pinning delle VM e definisco la vicinanza NUMA delle loro vCPU alla scheda di rete fisica. In questo modo i dati rimangono in locale e l’host paga meno per gli accessi alla memoria. Per i container, associo i pod critici ai core appropriati e mantengo le code dell’host libere da carico di disturbo. Questo ordine aumenta la Efficienza nel caso dei microservizi, dove si generano molti piccoli flussi.
Come utilizzare correttamente SR-IOV e VF-RSS
Con SR-IOV assegno alle VM delle VF dedicate, che a loro volta possono fornire più code e RSS. Prevedo un numero sufficiente di VF per ogni porta, prendo in considerazione la capacità MSI-X e assegno gli IRQ delle VF nella VM in base alle rispettive vCPU. Nelle macchine virtuali Linux attivo esplicitamente il Multi-Queue, altrimenti la vNIC rimane spesso a un solo livello:
# come guest (esempio virtio-net)
ethtool -l eth0
ethtool -L eth0 combined 4
Distribuisco rigorosamente gli host con più VF in base al nodo NUMA e al carico di lavoro, in modo che le macchine virtuali non si interferiscano a vicenda negli stessi percorsi RX fisici.
Monitoraggio e risoluzione dei problemi
Controllo il carico di lavoro per Coda, singoli kernel, perdite di pacchetti e ritrasmissioni, per individuare tempestivamente eventuali squilibri. Se un kernel si distacca dagli altri mentre gli altri rimangono inattivi, spesso l’affinità o il numero di code non è corretto. In questi casi controllo uno dopo l’altro i campi hash, le maschere IRQ e i valori di coalescing. Inoltre, controllo il Carico SoftIRQ, poiché fornisce indicazioni sulla presenza di effetti di spiazzamento. Solo quando questi segnali sembrano stabili, aumento il traffico o espando Spunti continuare.
RPS, RFS e XPS: estensioni software per RSS
Se una scheda di rete ha solo poche code o se utilizzo il bonding/tunneling, integro RSS con RPS (Receive Packet Steering) e RFS (Receive Flow Steering). RPS distribuisce gli SoftIRQ tra i core, mentre RFS associa i flussi al core su cui è attivo il socket corrispondente. Attivo entrambi in modo mirato:
# Aumentare globalmente il numero di voci di flusso (RFS)
echo 32768 > /proc/sys/net/core/rps_sock_flow_entries
# Impostare le CPU per RPS per ogni coda RX (maschera di esempio, da adattare!)
for f in /sys/class/net/eth0/queues/rx-*/rps_cpus; do echo ffff > "$f"; done
Impostare la tabella dei flussi per coda RX # per RFS
for f in /sys/class/net/eth0/queues/rx-*/rps_flow_cnt; do echo 4096 > "$f"; done
Sul lato TX utilizzo XPS (Transmit Packet Steering), affinché i pacchetti in uscita vengano inviati dal core che li ha generati:
for f in /sys/class/net/eth0/queues/tx-*/xps_cpus; do echo ffff > "$f"; done
RPS/RFS/XPS richiedono un po' di risorse della CPU, ma sono utili quando mi mancano le code a livello hardware o quando voglio mantenere rigorosamente la località dei socket.
Prestazioni Single-Flow, GRO/TSO e Busy-Polling
Un singolo flusso rimane associato a un core per una buona ragione. Se voglio aumentare la larghezza di banda di un singolo flusso, ricorro agli offload (GRO/TSO), a un’elevata frequenza del core e a un coalescing ben calibrato. Per i percorsi in cui la latenza è un fattore critico, è possibile Sondaggio occupato aiutare:
Impostare un valore basso per # ed effettuare la misurazione
sysctl -w net.core.busy_read=25
sysctl -w net.core.busy_poll=25
Il "busy polling" riduce i cambi di contesto, ma occupa tempo di CPU. Lo attivo solo nei casi in cui le latenze p99 sono rilevanti e ne valuto sempre gli effetti sul carico complessivo e sulla latenza di coda. Di norma mantengo attivo il GRO sui server, mentre utilizzo l’LRO a seconda del ruolo; nei middlebox adotto un approccio conservativo per non interferire con l’elaborazione delle intestazioni e la coerenza degli hash.
Suggerimenti ed esempi: code, affinità, comandi
Come punto di partenza scelgo un numero di code che sia adeguato alla CPU regola, poi osserva il carico per ciascuna coda e modificalo gradualmente. Con 10G spesso bastano 8–16 code, mentre con 25G tendo a impostare un numero più alto, purché ci siano i core disponibili. Per l’affinità utilizzo maschere ben definite per ogni IRQ, in modo da poter analizzare più facilmente i percorsi in un secondo momento. La tabella seguente fornisce valori di riferimento sintetici, che successivamente verifico tramite misurazioni. Solo i risultati delle misurazioni determinano se di più aumenta o riduci.
| Velocità di connessione | Code RX tipiche | Comandi di esempio | Note |
|---|---|---|---|
| 10 Gbit/s | 8-16 | ethtool -l eth0 | ethtool -L eth0 rx 16 | Coalescenza mantenere un livello moderato, verificare la latenza L7 |
| 25 Gbit/s | 16–32+ | grep . /proc/interrupts | Maschere IRQ tramite eco | NUMA tenere presente, verificare le linee PCIe |
| Multi-25G | Per ogni porta separatamente | Attivare la funzione multi-queue della vNIC (ad es. virtio) | Code su core e Carichi di lavoro dividere |
Questi valori di riferimento costituiscono solo il punto di partenza, non l'obiettivo, poiché i carichi di lavoro variano notevolmente. Registro le modifiche, effettuo misurazioni prima e dopo l’adeguamento e, per il resto, mantengo l’ambiente invariato. Non appena il sistema rimane stabile sotto carico di produzione, blocco la configurazione. Successivamente, ripeto le misurazioni in seguito ad aggiornamenti del kernel o dei driver. In questo modo mi attengo a RSS senza deviazioni dalla rotta e con risultati sicuri e riproducibili.
Ostacoli tipici e contromisure
Troppo pochi Spunti I singoli core vengono sovraccaricati, mentre un numero eccessivo aumenta il carico amministrativo e compromette la percentuale di hit della cache. Un’affinità non ottimale sposta gli interrupt su core già sovraccarichi o su nodi NUMA errati. Anche un hash inadeguato fa sì che i flussi dominanti intasino le code. Risolvo il problema passo dopo passo: regolo il numero di code, correggo l’affinità, espando i campi hash e ottimizzo il coalescing. Documento ogni modifica con Metriche, prima di passare alla leva successiva.
Scenari pratici
Un server di archiviazione con 10G raggiunge rapidamente valori compresi tra 8 e 12 Spunti oltre a un coalescing moderato, per garantire il corretto svolgimento dei trasferimenti in blocco. Un server API con un numero elevato di connessioni richiede spesso campi hash più precisi e una minore latenza negli interrupt. Gli host di virtualizzazione traggono notevoli vantaggi quando la funzione vNIC Multi-Queue è attiva sul lato guest e si adatta al layout dell’host. I carichi di lavoro dei container funzionano in modo più fluido quando i pod critici vengono eseguiti vicino alla scheda di rete e alla memoria NUMA. Estendo questi modelli a seconda della situazione, utilizzando PPS, confronta le ritrasmissioni e la distribuzione delle code.
Il vantaggio delle piattaforme efficienti
Configurazioni di hosting con RSS, le schede di rete multi-queue e un’affinità ben gestita garantiscono riserve tangibili in caso di picchi di carico. Chi valuta le offerte di server dovrebbe informarsi in modo mirato sulla funzionalità multi-queue, sul NUMA-pinning e sul monitoraggio. Un fornitore che attua in modo evidente questi aspetti ottiene spesso curve di throughput notevolmente migliori. Per soluzioni di server e hosting ad alte prestazioni, consiglio vivamente webhoster.de. Questo orientamento ripaga in Prestazioni e stabilità, soprattutto in presenza di numerosi flussi paralleli.
Sintesi per la pratica
Attivo Ricevere Side Scaling: imposta un numero ragionevole di code, associa gli IRQ ai core appropriati e verifica la configurazione dell’hash. Successivamente ottimizzo il coalescing in funzione della latenza, prendo in considerazione la vicinanza NUMA e distribuisco i carichi di lavoro in modo coerente. Nella virtualizzazione utilizzo il multi-queue fino ai sistemi guest e mantengo sincronizzati il pinning e l’affinità. Le misurazioni relative a PPS, carico delle code, ritrasmissioni e latenza determinano il passo successivo. Chi procede in questo modo sfrutta appieno le capacità di 10G e 25G, mantenendo Latenza nell'ambito di tale contesto e ottiene in modo affidabile un ritorno dalla rete.


