Direktes Routing und Balancing mit NFT vs Nginx

Bei der Entwicklung hoch ausgelasteter Netzwerkanwendungen ist ein Lastausgleich erforderlich.

Ein beliebtes L7-Auswuchtwerkzeug ist Nginx. Sie können Antworten zwischenspeichern, verschiedene Strategien auswählen und sogar Skripte für LUA erstellen.

Trotz aller Reize von Nginx, wenn:

  1. Keine Notwendigkeit, mit HTTP (s) zu arbeiten.
  2. Sie müssen das Maximum aus dem Netzwerk herausholen.
  3. Sie müssen nichts zwischenspeichern - der Balancer verfügt über saubere API-Server mit Dynamik.

Es kann sich die Frage stellen: Warum brauchen Sie Nginx? Warum sollten Sie den Ressourcenausgleich für L7 ausgeben? Ist es nicht einfacher, das SYN-Paket einfach weiterzuleiten? (L4 Direktes Routing).

Layer 4 Balancing oder wie man in der Antike balanciert


Ein beliebtes Paketweiterleitungstool war IPVS. Er führte Ausgleichsaufgaben durch den Tunnel und Direct Routing durch.

Im ersten Fall wurde für jede Verbindung ein TCP-Kanal eingerichtet, und das Paket des Benutzers ging an den Balancer, dann an den Minion und dann in umgekehrter Reihenfolge.



In diesem Schema ist das Hauptproblem sichtbar: In der entgegengesetzten Richtung gehen die Daten zuerst zum Balancer und dann zum Benutzer (Nginx funktioniert auf die gleiche Weise). Unnötige Arbeiten werden ausgeführt, da normalerweise mehr Daten an den Benutzer gesendet werden. Dieses Verhalten führt zu Leistungseinbußen.

Ein solcher Nachteil wird einer Ausgleichsmethode namens Direct Routing beraubt (aber mit neuen ausgestattet). Schematisch sieht es so aus:



Beim direkten Routing gehen die Rückgabepakete unter Umgehung des Balancers direkt an den Benutzer. Offensichtlich werden sowohl Balancer-Ressourcen als auch das Netzwerk gespeichert. Durch das Einsparen von Netzwerkressourcen wird nicht so viel Datenverkehr eingespart, da die Server normalerweise an ein separates Grid angeschlossen werden und der Datenverkehr nicht berücksichtigt wird. Die Tatsache, dass selbst die Übertragung über den Balancer einen Millisekundenverlust bedeutet.

Diese Methode unterliegt bestimmten Einschränkungen:

  1. Das Rechenzentrum, in dem sich die Infrastruktur befindet, sollte das Spoofing lokaler Adressen ermöglichen. In der obigen Abbildung muss jeder Minion im Auftrag von IP 10.10.0.1, die dem Balancer zugewiesen ist, Pakete zurücksenden.
  2. Der Balancer weiß nichts über den Zustand der Schergen. Folglich sind Least Conn- und Least Time-Strategien nicht sofort realisierbar. In einem der folgenden Artikel werde ich versuchen, sie zu implementieren und das Ergebnis zu zeigen.

Hier kommt NFTables


Vor einigen Jahren begann Linux, NFTables als Ersatz für IPTables, ArpTables, EBTables und alle anderen [az] {1,} Tables aktiv zu bewerben. In dem Moment, in dem wir in Adram jede Millisekunde der Antwort aus dem Netzwerk herausholen mussten, entschied ich mich, den Checker herauszuziehen und zu suchen - oder vielleicht hat ipTables gelernt, Iphash-Weiterleitung durchzuführen, und Sie können es beschleunigen, um es auszugleichen. Dann bin ich auf nftables gestoßen, die das können und nicht nur das, aber iptables können das noch nicht einmal.
Nach mehreren Testtagen konnte ich im Testlabor endlich Direct Routing und Channel Routing durch die NFTables erhalten und diese im Vergleich zu Nginx vergleichen.

Also das Testlabor. Wir haben 5 Autos:

  1. nft-router - Ein Router, der die Verbindung zwischen dem Client und dem AppServer-Subnetz ausführt. Es befinden sich 2 Netzwerkkarten: 192.168.56.254 - zeigt das Netzwerk des App-Servers an, 192.168.97.254 - betrachtet die Clients. Ip_forward ist aktiviert und alle Routen sind registriert.
  2. nft-client: Client, von dem ab, ip 192.168.97.2 gejagt wird
  3. nft-balancer: balancer. Es hat zwei IPs: 192.168.56.4, auf die Clients zugreifen, und 192.168.13.1 aus dem Minion-Subnetz.
  4. nft-minion-a und nft-minion-b: minions ipy: 192.168.56.2, 192.168.56.3 und 192.168.13.2 und 192.168.13.3 (Ich habe versucht, sowohl das gleiche als auch das unterschiedliche Netzwerk zum Ausgleich zu verwenden). In Tests hörte ich bei der Tatsache auf, dass Schergen "externe" Typen haben - im Subnetz 192.168.56.0/24

Alle MTU 1500-Schnittstellen.

Direktes Routing


NFTables-Einstellungen auf dem Balancer:

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 } } } 

Am Haken wird eine Rohkette mit einer Priorität von -300 erstellt.

Wenn ein Paket mit der Zieladresse http ankommt, wird abhängig vom Quellport (zum Testen von einem Computer aus benötigen Sie tatsächlich ip saddr) entweder 56.2 oder 56.3 ausgewählt und als Zieladresse im Paket festgelegt und dann weiter entlang der Routen gesendet. Grob gesagt für gerade Ports 56.2 bzw. für ungerade Ports 56.3 (in der Tat nein, weil für gerade / ungerade Hashes, aber es ist einfacher, genau das zu verstehen). Nach dem Festlegen der Ziel-IP geht das Paket zurück zum Netzwerk. Es tritt kein NAT auf, das Paket kommt mit der Quell-IP des Clients zu den Minions und nicht mit dem Balancer, der für das direkte Routing wichtig ist.

Minion NFT-Einstellungen:

 table ip raw { chain output { type filter hook output priority -300; policy accept; tcp sport http ip saddr set 192.168.56.4 } } 

Ein Raw-Ausgabe-Hook wird mit einer Priorität von -300 erstellt (Priorität ist hier sehr wichtig, auf höheren Ebenen funktioniert das erforderliche Mengling für Antwortpakete nicht).

Der gesamte ausgehende Datenverkehr vom http-Port wird von 56.4 (IP-Balancer) signiert und unter Umgehung des Balancers direkt an den Client gesendet.

Um zu überprüfen, ob alles korrekt funktioniert, habe ich den Client in ein anderes Netzwerk gebracht und über den Router übertragen.

Ich habe auch arp_filter, rp_filter deaktiviert (damit das Spoofing funktioniert) und ip_forward sowohl auf dem Balancer als auch auf dem Router aktiviert.

Bei Bänken wird im Fall von NFT Nginx + php7.2-FPM über eine Unix-Buchse an jedem Minion verwendet. Es war nichts auf dem Balancer.

Im Fall von Nginx haben wir verwendet: Nginx auf dem Balancer und php7.2-FPM über TCP auf Minions. Infolgedessen habe ich nicht den Webserver hinter dem Balancer ausgeglichen, sondern sofort FPM (was mit Nginx ehrlicher und mit dem wirklichen Leben konsistenter ist).

Für NFT wurde nur die Hash-Strategie verwendet ( nft dr in der Tabelle), für nginx: hash ( ngx eq ) und Least conn ( ngx lc )

Es wurden mehrere Tests durchgeführt.

  1. Kleines schnelles Skript (klein) .

     <?php system('hostname'); 

  2. Das Skript mit einer zufälligen Verzögerung (Rand) .

     <?php usleep(mt_rand(100000,200000)); echo "ok"; 
  3. Ein Skript mit dem Senden einer großen Datenmenge (Größe) .

     <?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')); 

    Folgende Größen wurden verwendet:
    512.1440.1460.1480.1500.2048.65535.655350 Bytes.
    Vor den Tests habe ich die Statikdateien für jeden Diener aufgewärmt.

Ab dreimal pro Test getestet:

 #!/bin/bash function do_test() { rep=$3 for i in $(seq $rep) do echo "testing $2 # $i" echo "$2 pass $i" >> $2 ab $1 >> $2 echo "--------------------------" >> $2 done } do_test " -n 5000 -c 100 http://192.168.56.4:80/rand.php" "ngx_eq_test_rand" 3 do_test " -n 10000 -c 100 http://192.168.56.4:80/" "ngx_eq_test_small" 3 size=512 do_test " -n 10000 -c 100 http://192.168.56.4:80/size.php?size=$size" "ngx_eq_test_size_$size" 3 size=1440 do_test " -n 10000 -c 100 http://192.168.56.4:80/size.php?size=$size" "ngx_eq_test_size_$size" 3 size=1460 do_test " -n 10000 -c 100 http://192.168.56.4:80/size.php?size=$size" "ngx_eq_test_size_$size" 3 size=1480 do_test " -n 10000 -c 100 http://192.168.56.4:80/size.php?size=$size" "ngx_eq_test_size_$size" 3 size=1500 do_test " -n 10000 -c 100 http://192.168.56.4:80/size.php?size=$size" "ngx_eq_test_size_$size" 3 size=2048 do_test " -n 10000 -c 100 http://192.168.56.4:80/size.php?size=$size" "ngx_eq_test_size_$size" 3 size=65535 do_test " -n 10000 -c 100 http://192.168.56.4:80/size.php?size=$size" "ngx_eq_test_size_$size" 3 size=655350 do_test " -n 10000 -c 100 http://192.168.56.4:80/size.php?size=$size" "ngx_eq_test_size_$size" 3 

Ursprünglich wollte ich die Testzeit, Millisekunden und den Rest einbringen. Als Ergebnis habe ich mich für RPS entschieden - sie sind repräsentativ und korrelieren mit Zeitindikatoren.

Erhielt die folgenden Ergebnisse:

Größentest - Spalten - die Größe der angegebenen Daten.



Wie Sie sehen können, gewinnt nft direct routing mit großem Abstand.

Ich habe mit einigen anderen Ergebnissen in Bezug auf die Größe des Ethernet-Frames gerechnet, aber es wurde keine Korrelation gefunden. Vielleicht passt 512 Körper nicht in 1500 MTU, obwohl ich bezweifle, dass der kleine Test indikativ sein wird.

Mir ist aufgefallen, dass Nginx bei großen Volumina (650k) die Trennung reduziert. Vielleicht hat dies etwas mit Puffern und der TCP-Windows-Größe zu tun.

Das Ergebnis des Rand-Tests. Zeigt an, wie wenig Conn unter Bedingungen unterschiedlicher Geschwindigkeit der Skriptausführung auf verschiedenen Minions funktioniert.



Überraschenderweise funktionierte Nginx-Hash schneller als der geringste Conn, und erst im letzten Durchgang kam der geringste Conn ein wenig voran, was nicht vorgibt, signifikant zu sein.
Die Anzahl der Durchgänge ist sehr unterschiedlich, da 100 Threads gleichzeitig ablaufen und das FPM-ok von Anfang an etwa 10 lädt. Beim dritten Durchgang hatten sie Zeit, sich daran zu gewöhnen - was die Anwendbarkeit von Strategien für Bursts zeigt.

NFT hat diesen Test voraussichtlich verloren. Nginx optimiert in solchen Situationen die Interaktion mit FPMs.

kleiner Test



nft gewinnt geringfügig auf RPS, am wenigsten ist conn wieder ein Außenseiter.

Übrigens ist in diesem Test zu sehen, dass 400-500 U / min ausgegeben werden, obwohl es beim Test mit 512 Bytes 1500 waren - es scheint, dass das System diese tausend frisst.

Schlussfolgerungen


NFT hat sich in einer Situation der Optimierung gleichmäßiger Lasten gut bewährt: Wenn viele Daten angegeben werden und die Betriebszeit der Anwendung bestimmt wird und die Ressourcen des Clusters ausreichen, um den eingehenden Stream zu verarbeiten, ohne in einen Tailspin zu geraten.

In einer Situation, in der die Last bei jeder Anforderung chaotisch ist und es unmöglich ist, die Serverlast gleichmäßig mit dem primitiven Rest der Hash-Division auszugleichen, verliert die NFT.

Source: https://habr.com/ru/post/de441348/


All Articles