Eine Sache bei WebSockets ist, dass Sie viele Ressourcen auf der Clientseite benötigen, um eine ausreichend hohe Last zu generieren, damit der Server tatsächlich alle CPU-Ressourcen verbraucht.
Es gibt mehrere Herausforderungen, die Sie bewältigen müssen, da das WebSockets-Protokoll auf Clientseite mehr CPU-Anforderungen stellt als auf Serverseite. Gleichzeitig benötigen Sie viel RAM, um Informationen über offene Verbindungen zu speichern, wenn Sie über Millionen davon verfügen.
Ich hatte das Glück, für einen begrenzten Zeitraum ein paar neue Server für die Hardware-Burnout-Tests zur Verfügung zu haben. Deshalb habe ich mich entschlossen, meinen Lua Application Server - LAppS für beide Aufgaben zu verwenden: Testen Sie die Hardware und führen Sie die LAppS-Hochlasttests durch.
Lassen Sie uns die Details sehen
Herausforderungen
Zunächst benötigen WebSockets auf der Client-Seite ein ziemlich schnelles RNG für die Verkehrsmaskierung, es sei denn, Sie möchten es fälschen. Ich wollte, dass die Tests realistisch sind, also verwarf ich die Idee, das RNG zu fälschen, indem ich es durch eine Konstante ersetzte. Dies lässt die einzige Option - viel CPU-Leistung. Wie gesagt, ich habe zwei Server: einen mit zwei Intel Gold 6148-CPUs (insgesamt 40 Kerne) mit 256 GB RAM (DDR4-2666) und einen mit vier Intel Platinum 8180-CPUs (insgesamt 112 Kerne) mit 384 GB RAM (DDR4-2666) . Ich werde sie im Folgenden als Server A und Server B bezeichnen.
Die zweite Herausforderung: Es gibt keine Bibliotheken oder Testsuiten für WebSockets, die schnell genug sind. Aus diesem Grund musste ich ein WebSockets-Clientmodul für LAppS ( cws ) entwickeln.
Der Testaufbau
Beide Server verfügen über 10-GbE-Karten mit zwei Ports. Port 1 jeder Karte ist zu einer Verbindungsschnittstelle zusammengefasst, und diese Ports auf beiden Karten sind im RR-Modus miteinander verbunden. Port 2 jeder Karte wird in einem Active-Backup-Modus, der über einen Ethernet-Switch verbunden ist, zu einer Verbindungsschnittstelle zusammengefasst. Auf jedem Hardwareserver wurde RHEL 7.6 ausgeführt. Ich musste gcc-8.3 von den Quellen installieren, um einen modernen Compiler anstelle des Standard-gcc-4.8.5 zu haben. Nicht das beste Setup für die Leistungstests, aber Bettler können keine Wahl sein.
CPUs auf Server A (2xGold 6148) haben eine höhere Frequenz als auf Server B (4xPlatinum 8180). Gleichzeitig hat Server B mehr Kerne, daher habe ich beschlossen, einen Echoserver auf Server A und die Echoclients auf Server B auszuführen.
Ich wollte sehen, wie sich LAppS unter hoher Last mit Millionen von Verbindungen verhält und welche maximale Anzahl von Echoanforderungen pro Sekunde es bedienen kann. Dies sind eigentlich zwei verschiedene Testreihen, da Server und Clients mit dem Wachstum der Verbindungen immer komplexer werden.
Vorher habe ich alle Tests auf meinem Heim-PC durchgeführt, die ich für die Entwicklung verwende. Die Ergebnisse dieser Tests werden zum Vergleich herangezogen. Mein Heim-PC verfügt über eine Intel Core i7-7700-CPU mit 3,6 GHz (4,0 GHz Turbo), 4 Kernen und 32 GB DDR4-2400-RAM. Auf diesem PC läuft Gentoo mit dem Kernel 4.14.72.
Spectre- und Meltdown-Patch-Levels- Home PC
/sys/devices/system/cpu/vulnerabilities/l1tf:Mitigation: PTE Inversion; VMX: conditional cache flushes, SMT vulnerable /sys/devices/system/cpu/vulnerabilities/meltdown:Mitigation: PTI /sys/devices/system/cpu/vulnerabilities/spec_store_bypass:Mitigation: Speculative Store Bypass disabled via prctl and seccomp /sys/devices/system/cpu/vulnerabilities/spectre_v1:Mitigation: __user pointer sanitization /sys/devices/system/cpu/vulnerabilities/spectre_v2:Vulnerable: Minimal generic ASM retpoline, IBPB, IBRS_FW
- Server A und B.
/sys/kernel/debug/x86/ibpb_enabled : 1 /sys/kernel/debug/x86/pti_enabled : 1 /sys/kernel/debug/x86/ssbd_enabled : 1 /sys/kernel/debug/x86/ibrs_enabled : 1 /sys/kernel/debug/x86/retp_enabled : 3
Ich werde mit den Ergebnissen der Tests der Server A und B eine zusätzliche Notiz machen, ob diese Patches aktiviert sind oder nicht.
Auf meinem Heim-PC wird der Patch-Level nie geändert.
Installieren von LAppS 0.8.1
Installationsvoraussetzungen und LAppSLAppS hängt von den Bibliotheken luajit-2.0.5 oder höher, libcrypto ++ 8.2 und wolfSSL-3.15.7 ab und muss aus Quellen in RHEL 7.6 und wahrscheinlich in jeder anderen Linux-Distribution installiert werden.
Das Präfix für die Installation lautet / usr / local. Hier ist der ziemlich selbsterklärende Dockerfile-Teil für die wolfSSL-Installation
ADD https://github.com/wolfSSL/wolfssl/archive/v3.15.7-stable.tar.gz ${WORKSPACE} RUN tar xzvf v3.15.7-stable.tar.gz WORKDIR ${WORKSPACE}/wolfssl-3.15.7-stable RUN ./autogen.sh RUN ./configure CFLAGS="-pipe -O2 -march=native -mtune=native -fomit-frame-pointer -fstack-check -fstack-protector-strong -mfpmath=sse -msse2avx -mavx2 -ftree-vectorize -funroll-loops -DTFM_TIMING_RESISTANT -DECC_TIMING_RESISTANT -DWC_RSA_BLINDING" --prefix=/usr/local --enable-tls13 --enable-openssh --enable-aesni --enable-intelasm --enable-keygen --enable-certgen --enable-certreq --enable-curve25519 --enable-ed25519 --enable-intelasm --enable-harden RUN make -j40 all RUN make install
Und hier ist der Teil für die Installation von libcrypto ++
ADD https://github.com/weidai11/cryptopp/archive/CRYPTOPP_8_2_0.tar.gz ${WORKSPACE} RUN rm -rf ${WORKSPACE}/cryptopp-CRYPTOPP_8_2_0 RUN tar xzvf ${WORKSPACE}/CRYPTOPP_8_2_0.tar.gz WORKDIR ${WORKSPACE}/cryptopp-CRYPTOPP_8_2_0 RUN make CFLAGS="-pipe -O2 -march=native -mtune=native -fPIC -fomit-frame-pointer -fstack-check -fstack-protector-strong -mfpmath=sse -msse2avx -mavx2 -ftree-vectorize -funroll-loops" CXXFLAGS="-pipe -O2 -march=native -mtune=native -fPIC -fomit-frame-pointer -fstack-check -fstack-protector-strong -mfpmath=sse -msse2avx -mavx2 -ftree-vectorize -funroll-loops" -j40 libcryptopp.a libcryptopp.so RUN make install
Und der Luajit
ADD http://luajit.org/download/LuaJIT-2.0.5.tar.gz ${WORKSPACE} WORKDIR ${WORKSPACE} RUN tar xzvf LuaJIT-2.0.5.tar.gz WORKDIR ${WORKSPACE}/LuaJIT-2.0.5 RUN env CFLAGS="-pipe -Wall -pthread -O2 -fPIC -march=native -mtune=native -mfpmath=sse -msse2avx -mavx2 -ftree-vectorize -funroll-loops -fstack-check -fstack-protector-strong -fno-omit-frame-pointer" make -j40 all RUN make install
Eine optionale Abhängigkeit von LAppS, die möglicherweise ignoriert wird, ist die neue Bibliothek von Microsoft mimalloc . Diese Bibliothek verbessert die Leistung erheblich (ca. 1%), erfordert jedoch cmake-3.4 oder höher. Angesichts der begrenzten Zeit für die Tests habe ich beschlossen, die erwähnte Leistungsverbesserung zu opfern.
Auf meinem Heim-PC werde ich mimalloc während der Tests nicht deaktivieren.
Ermöglicht das Auschecken von LAppS aus dem Repository:
WORKDIR ${WORKSPACE} RUN rm -rf ITCLib ITCFramework lar utils LAppS RUN git clone https://github.com/ITpC/ITCLib.git RUN git clone https://github.com/ITpC/utils.git RUN git clone https://github.com/ITpC/ITCFramework.git RUN git clone https://github.com/ITpC/LAppS.git RUN git clone https://github.com/ITpC/lar.git WORKDIR ${WORKSPACE}/LAppS
Jetzt müssen wir "-lmimalloc" aus allen Makefiles im Unterverzeichnis nbproject entfernen , damit wir LAppS erstellen können (vorausgesetzt, unser aktuelles Verzeichnis ist $ {WORKSPACE} / LAppS).
Und jetzt können wir LAppS bauen. LAppS bietet verschiedene Build-Konfigurationen, die einige Funktionen auf der Serverseite ausschließen können oder nicht:
- mit SSL-Unterstützung und mit Unterstützung beim Sammeln von Statistiken
- mit SSL und ohne Statistiksammlung (obwohl die minimale Statistiksammlung fortgesetzt wird, da sie für die dynamische LAppS-Optimierung zur Laufzeit verwendet wird)
- ohne SSL und ohne statistische Erfassung.
Stellen Sie vor dem nächsten Schritt sicher, dass Sie der Eigentümer des Verzeichnisses / opt / lapps sind (oder führen Sie make installs mit sudo aus).
Lassen Sie uns zwei Arten von Binärdateien mit und ohne SSL-Unterstützung und Statistikerfassung erstellen (vorausgesetzt, wir befinden uns im Verzeichnis $ {WORKSPACE} / LAppS):
Die resultierenden Binärdateien sind:
- dist / Release.AVX2 / GNU-Linux / lapps.avx2
- dist / Release.AVX2.NO_STATS.NO_TLS / GNU-Linux / lapps.avx2.nostats.notls
Sie werden in / opt / lapps / bin installiert.
Bitte beachten Sie, dass das WebSockets-Clientmodul für Lua immer mit SSL-Unterstützung erstellt wird. Ob es aktiviert ist oder nicht, hängt von der URI ab, die Sie zur Laufzeit für die Verbindung (ws: // oder wss: //) verwenden.
Ich habe bereits festgestellt, dass ich die beste Leistung erhalte, wenn ich vier Benchmark-Dienstinstanzen mit jeweils 100 Verbindungen konfiguriere. Gleichzeitig benötige ich nur drei IOWorker und vier Echo-Service-Instanzen, um die beste Leistung zu erzielen. Erinnerst du dich? Ich habe hier nur 4 Kerne.
Der Zweck dieses Tests besteht lediglich darin, eine Basislinie für den weiteren Vergleich festzulegen. Hier ist wirklich nichts Aufregendes.
Nachfolgend sind die Schritte aufgeführt, die zum Konfigurieren von LAppS für die Tests erforderlich sind.
Selbstsignierte Zertifikate
Wenn Sie dieses Skript im Verzeichnis / opt / lapps / conf / ssl ausführen, werden alle erforderlichen Dateien erstellt. Hier ist die Ausgabe des Skripts und was ich eingegeben habe:
Ausgabe von certgen.sh # certgen.sh Generating RSA private key, 2048 bit long modulus .................................................................................................................................................................+++++ .....................................+++++ e is 65537 (0x010001) You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [AU]:KZ State or Province Name (full name) [Some-State]:none Locality Name (eg, city) []:Almaty Organization Name (eg, company) [Internet Widgits Pty Ltd]:NOORG.DO.NOT.FORGET.TO.REMOVE.FROM.BROWSER Organizational Unit Name (eg, section) []: Common Name (eg server FQDN or YOUR name) []: Email Address []: Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []: An optional company name []: Generating RSA private key, 2048 bit long modulus ...+++++ ............................................................................+++++ e is 65537 (0x010001) You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [AU]:KZ State or Province Name (full name) [Some-State]:none Locality Name (eg, city) []:Almaty Organization Name (eg, company) [Internet Widgits Pty Ltd]:none Organizational Unit Name (eg, section) []: Common Name (eg server FQDN or YOUR name) []:*.example.org Email Address []: Signature ok subject=C = KZ, ST = none, L = Almaty, O = NOORG.DO.NOT.FORGET.TO.REMOVE.FROM.BROWSER Getting CA Private Key
LAppS-Konfiguration
Hier ist die WebSockets-Konfigurationsdatei, die für TLS 1.3 festgelegt ist, die oben generierten Zertifikate enthält und mit 3 IOWorkern konfiguriert ist (siehe detaillierte Beschreibung der Variablen im LAppS-Wiki).
/opt/lapps/etc/conf/ws.json { "listeners" : 2, "connection_weight": 1.0, "ip" : "0.0.0.0", "port" : 5083, "lapps_config_auto_save" : true , "workers" : { "workers": 3, "max_connections" : 40000, "auto_fragment" : false, "max_poll_events" : 256, "max_poll_wait_ms" : 10, "max_inbounds_skip" : 50, "input_buffer_size" : 2048 }, "acl" : { "policy" : "allow", "exclude" : [] }, "tls":true, "tls_server_version" : 4, "tls_client_version" : 4, "tls_certificates":{ "ca":"/opt/lapps/conf/ssl/ca.crt", "cert": "/opt/lapps/conf/ssl/example.org.bundle.crt", "key": "/opt/lapps/conf/ssl/example.org.key" } }
Konfigurieren von Diensten: Der Echoserver und der Echoclient (Benchmark) mit jeweils vier Instanzen.
/opt/lapps/etc/conf/lapps.json { "directories": { "app_conf_dir": "etc", "applications": "apps", "deploy": "deploy", "tmp": "tmp", "workdir": "workdir" }, "services": { "echo": { "auto_start": true, "instances": 4, "internal": false, "max_inbound_message_size": 16777216, "protocol": "raw", "request_target": "/echo", "acl" : { "policy" : "allow", "exclude" : [] } }, "benchmark": { "auto_start": true, "instances" : 4, "internal": true, "preload": [ "nap", "cws", "time" ], "depends" : [ "echo" ] } } }
Der Echo-Service ist ziemlich trivial:
Echo-Service-Quellcode echo = {} echo.__index = echo; echo.onStart=function() print("echo::onStart"); end echo.onDisconnect=function() end echo.onShutdown=function() print("echo::onShutdown()"); end echo.onMessage=function(handler,opcode, message) local result, errmsg=ws:send(handler,opcode,message); if(not result) then print("echo::OnMessage(): "..errmsg); end return result; end return echo
Der Benchmark-Service erstellt so viele wie Benchmark.max_connections zu Benchmark.Target und wird dann nur ausgeführt, bis Sie das LAppS stoppen. Es gibt keine Pause beim Aufbau der Verbindungen oder beim Bombardement mit Echoanfragen. Die cws- Modul-API ähnelt der Web-API für WebSockets. Sobald alle Benchmark.max_connections eingerichtet sind, gibt der Benchmark die Anzahl der verbundenen Sockets aus. Sobald die Verbindung hergestellt ist, sendet der Benchmark eine Benchmark-Nachricht an den Server. Nachdem der Server geantwortet hat , wird die anonyme onmessage- Methode des cws- Objekts aufgerufen, die nur dieselbe Nachricht an den Server zurücksendet .
Benchmark-Service-Quellcode benchmark={} benchmark.__index=benchmark benchmark.init=function() end benchmark.messages_counter=0; benchmark.start_time=time.now(); benchmark.max_connections=100; benchmark.target="wss://127.0.0.1:5083/echo"; benchmark.message="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; benchmark.meter=function() benchmark.messages_counter=benchmark.messages_counter+1; local slice=time.now() - benchmark.start_time; if( slice >= 1000) then print(benchmark.messages_counter.." messages received per "..slice.."ms") benchmark.messages_counter=0; benchmark.start_time=time.now(); end end benchmark.run=function() local n=nap:new(); local counter=1; n:sleep(1); local array={}; local start=time.now(); while(#array < benchmark.max_connections) and (not must_stop()) do local sock, err_msg=cws:new(benchmark.target, { onopen=function(handler) local result, errstr=cws:send(handler,benchmark.message,2); if(not result) then print("Error on websocket send at handler "..handler..": "..errstr); end end, onmessage=function(handler,message,opcode) benchmark.meter(); cws:send(handler,message,opcode); end, onerror=function(handler, message) print("Client WebSocket connection is failed for socketfd "..handler..". Error: "..message); end, onclose=function(handler) print("Connection is closed for socketfd "..handler); end }); if(sock ~= nil) then table.insert(array,sock); else print(err_msg); err_msg=nil; collectgarbage("collect"); end
Installation der Dienste
Jetzt müssen wir die Service-Skripte in /opt/lapps/apps//.lua platzieren:
- /opt/lapps/apps/benchmark/benchmark.lua
- /opt/lapps/apps/echo/echo.lua
Wir sind jetzt bereit, unseren Benchmark durchzuführen. Führen Sie einfach rm -f lapps.log; /opt/lapps/bin/lapps.avx2 > log
: rm -f lapps.log; /opt/lapps/bin/lapps.avx2 > log
rm -f lapps.log; /opt/lapps/bin/lapps.avx2 > log
aus dem LAppS-Verzeichnis aus und warten Sie 5 Minuten. Drücken Sie dann einmal Strg-C, um das LAppS zu stoppen (es wird nicht sofort gestoppt, es werden zuerst die Verbindungen beendet) oder zweimal (dies unterbricht die Abschaltsequenz).
Ok, wir haben eine Textdatei mit so etwas:
Benchmark-Ausgabeecho :: onStart
echo :: onStart
echo :: onStart
echo :: onStart
Laufen
1 empfangene Nachrichten pro 3196 ms
1 empfangene Nachrichten pro 3299 ms
1 empfangene Nachrichten pro 3299 ms
1 empfangene Nachrichten pro 3305 ms
Anschlüsse angeschlossen: 100
Anschlüsse angeschlossen: 100
Anschlüsse angeschlossen: 100
Anschlüsse angeschlossen: 100
Pro 1000 ms empfangene 134597 Nachrichten
139774 pro 1000 ms empfangene Nachrichten
Pro 1000 ms empfangene 138521 Nachrichten
139404 pro 1000 ms empfangene Nachrichten
140162 empfangene Nachrichten pro 1000 ms
139337 Nachrichten pro 1000 ms empfangen
Pro 1000 ms empfangene 140088 Nachrichten
139946 empfangene Nachrichten pro 1000 ms
141204 pro 1000 ms empfangene Nachrichten
Pro 1000 ms empfangene 137988 Nachrichten
141805 Nachrichten pro 1000 ms empfangen
Pro 1000 ms empfangene 134733 Nachrichten
...
Lassen Sie uns diese Protokolldatei wie folgt bereinigen:
echo -e ':%s/ms/^V^M/g\n:%g/^$/d\nGdd1G4/Sockets\n4\ndggZZ' | vi $i
'^ V ^ M' ist eine sichtbare Darstellung der folgenden Schlüsseltreffer: Strg-V Strg-V Strg-V Strg-M, daher ist es ziemlich nutzlos, nur diese Bash-Zeile zu kopieren und einzufügen. Kurze Erklärung:
- Wir müssen 'ms'-Symbole durch das Zeilenende ersetzen, da wir sie nicht benötigen. Sie werden die Berechnungen später durcheinander bringen und 4 parallel arbeitende Benchmarks können ihre Ergebnisse in einer Zeile ausdrucken.
- wir müssen danach alle leeren Zeilen entfernen
- Wir entfernen auch die letzte Zeile, weil wir den Server stoppen
- In der Protokolldatei gibt es nur vordere Zeilen, die aus verbundenen Sockets bestehen : 100 (weil wir nur vier Benchmark-Dienste ausführen). Wir überspringen also 4 Zeilen nach der letzten und entfernen dann alles oben in der Datei.
- Speichern der Datei.
Die Datei wird gespeichert und Sie befinden sich wieder in der Shell. Die Protokolldatei ist für den nächsten Schritt bereit:
Dieser awk-Einzeiler berechnet die Menge der Echoantworten pro ms und akkumuliert das Ergebnis in einer durchschnittlichen Variablen. Nachdem alle Zeilen in der Protokolldatei verarbeitet wurden, wird der Durchschnitt mit 1000 multipliziert, um die Gesamtmenge der Echoantworten pro Sekunde zu erhalten, wobei die Anzahl der Zeilen dividiert und die Anzahl der Benchmark-Dienste multipliziert wird. Dies gibt uns die durchschnittliche Anzahl von Echoantworten pro Sekunde für diesen Testlauf.
Auf meinem PC lautet diese Nummer (ERps): 563854
Machen wir dasselbe ohne SSL-Unterstützung und sehen den Unterschied:
- Ändern Sie den Wert der Variablen tls in ws.json in false
- ändere Benchmark.Target in Benchmark.Lua von wss: // in ws: //
- Führen Sie
rm -f lapps.log; /opt/lapps/bin/lapps.avx2.nostats.notls > log
rm -f lapps.log; /opt/lapps/bin/lapps.avx2.nostats.notls > log
aus dem LAppS-Verzeichnis aus und wiederholen Sie die obigen Schritte.
Ich habe: 721236 Antworten pro Sekunde
Der Leistungsunterschied mit SSL und ohne SSL beträgt ca. 22%. Denken wir an diese Zahlen, um später darauf zurückgreifen zu können.
Mit dem gleichen Setup auf Server A habe ich: 421905 ERpS mit SSL und 443145 ERpS ohne SSL. Die Patches für Spectre und Meltdown wurden deaktiviert.
Auf dem Server B habe ich 270996 ERpS mit SSL und 318522 ERpS ohne SSL mit aktivierten Patches. 385726 und 372126 ohne und mit SSL. Die Patches für Spectre und Meltdown wurden ebenfalls deaktiviert.
Die Ergebnisse sind bei gleichem Setup schlechter, da die CPUs auf diesen Servern weniger häufig sind.
Bitte beachten Sie, dass die Clients stark von der Datenverfügbarkeit in / dev / urandom abhängig sind. Es kann eine Weile dauern, bis die Clients tatsächlich ausgeführt werden, wenn Sie sie bereits einmal ausgeführt haben. Warten Sie also, bis sie anfangen zu arbeiten, dann sind sie ziemlich schnell bei dem, was sie tun. Überwachen Sie einfach mit top, ob die LAppS-Instanzen überhaupt einen Job ausführen. Wenn / dev / urandom erschöpft ist, verbraucht das LAppS Ihre CPUs erst, wenn einige Daten verfügbar sind.
Vorbereitung auf große Tests
Zunächst müssen wir einige Änderungen an den Kernel-Parametern vornehmen und das ulimit für nr der geöffneten Dateien nicht vergessen. Ich habe fast das gleiche Setup wie in diesem Artikel verwendet .
Erstellen Sie eine Datei mit dem folgenden Inhalt
: sysctl -w fs.file-max=14000000 sysctl -w fs.nr_open=14000000 ulimit -n 14000000 sysctl -w net.ipv4.tcp_mem="100000000 100000000 100000000" sysctl -w net.core.somaxconn=20000 sysctl -w net.ipv4.tcp_max_syn_backlog=20000 sysctl -w net.ipv4.ip_local_port_range="1025 65535"
Verwenden Sie dann auf beiden Servern die Quelle ./Dateiname , um die Kernelparameter und das Ulimit zu ändern.
Dieses Mal möchte ich mehrere Millionen Clients auf einem Server erstellen und mit dem zweiten verbinden.
Server A dient als Server-Seite für den WebSockets-Echo-Service.
Server B dient als Client-Seite für den WebSockets-Echo-Service.
Es gibt eine von LuaJIT auferlegte Einschränkung in LAppS. Sie können nur 2 GB RAM pro LuaJIT-VM verwenden. Dies ist die RAM-Grenze, die Sie innerhalb einer einzelnen LAppS-Instanz für alle Dienste verwenden können, da die Dienste die Threads sind, die mit einer Libluajit-Instanz verknüpft sind. Wenn einer Ihrer Dienste, der unter der einzelnen LAppS-Instanz ausgeführt wird, diese Grenze überschreitet, haben alle Dienste nicht genügend Speicher.
Ich habe herausgefunden, dass Sie pro einzelner LAppS-Instanz nicht mehr als 2 464 000 Client-WebSockets mit einer Nachrichtengröße von 64 Byte einrichten können. Die Nachrichtengröße kann diese Grenze geringfügig ändern, da LAppS die Nachricht an den cws- Dienst weiterleitet , indem der Speicherplatz für diese Nachricht im LuaJIT zugewiesen wird.
Dies bedeutet, dass ich mehrere LAppS-Instanzen mit derselben Konfiguration auf Server B starten muss, um mehr als 2,4 Millionen WebSockets einzurichten. Der Server A (der Echoserver) verwendet auf LuaJIT-Seite nicht so viel Speicher, sodass die eine Instanz des LAppS problemlos 12,3 Millionen WebSockets verwaltet.
Bereiten wir zwei verschiedene Konfigurationen für die Server A und B vor.
Server A ws.json { "listeners" : 224, "connection_weight": 1.0, "ip" : "0.0.0.0", "port" : 5084, "lapps_config_auto_save" : true , "workers" : { "workers": 40, "max_connections" : 2000000, "auto_fragment" : false, "max_poll_events" : 256, "max_poll_wait_ms" : 10, "max_inbounds_skip" : 50, "input_buffer_size" : 2048 }, "acl" : { "policy" : "allow", "exclude" : [] }, "tls": true, "tls_server_version" : 4, "tls_client_version" : 4, "tls_certificates":{ "ca":"/opt/lapps/conf/ssl/ca.crt", "cert": "/opt/lapps/conf/ssl/example.org.bundle.crt", "key": "/opt/lapps/conf/ssl/example.org.key" } }
Server A lapps.json { "directories": { "app_conf_dir": "etc", "applications": "apps", "deploy": "deploy", "tmp": "tmp", "workdir": "workdir" }, "services": { "echo": { "auto_start": true, "instances": 40, "internal": false, "max_inbound_message_size": 16777216, "protocol": "raw", "request_target": "/echo", "acl" : { "policy" : "allow", "exclude" : [] } } } }
Server B ws.json { "listeners" : 0, "connection_weight": 1.0, "ip" : "0.0.0.0", "port" : 5083, "lapps_config_auto_save" : true , "workers" : { "workers": 0, "max_connections" : 0, "auto_fragment" : false, "max_poll_events" : 2048, "max_poll_wait_ms" : 10, "max_inbounds_skip" : 50, "input_buffer_size" : 2048 }, "acl" : { "policy" : "deny", "exclude" : [] }, "tls": true, "tls_server_version" : 4, "tls_client_version" : 4, "tls_certificates":{ "ca":"/opt/lapps/conf/ssl/ca.crt", "cert": "/opt/lapps/conf/ssl/example.org.bundle.crt", "key": "/opt/lapps/conf/ssl/example.org.key" } }
Server A verfügt über zwei Schnittstellen:
- bond0 - xx203.37
- bond1 - xx23.10
Einer ist schneller, ein anderer ist langsamer, aber das spielt keine Rolle. Der Server wird sowieso stark ausgelastet sein.
Bereiten wir eine Vorlage aus unserer Datei /opt/lapps/benchmark/benchmark.lua vor
Benchmark.lua benchmark={} benchmark.__index=benchmark benchmark.init=function() end benchmark.messages_counter=0; benchmark.start_time=time.now(); benchmark.max_connections=10000; benchmark.target_port=0; benchmark.target_prefix="wss://IPADDR:"; benchmark.target_postfix="/echo"; benchmark.message="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; benchmark.meter=function() benchmark.messages_counter=benchmark.messages_counter+1; local slice=time.now() - benchmark.start_time; if( slice >= 1000) then print(benchmark.messages_counter.." messages received per "..slice.."ms") benchmark.messages_counter=0; benchmark.start_time=time.now(); end end benchmark.run=function() local n=nap:new(); local counter=1; n:sleep(1); local array={}; local start=time.now(); while(#array < benchmark.max_connections) and (not must_stop()) do benchmark.target_port=math.random(5084,5307); local sock, err_msg=cws:new(benchmark.target_prefix..benchmark.target_port..benchmark.target_postfix, { onopen=function(handler) local result, errstr=cws:send(handler,benchmark.message,2); if(not result) then print("Error on websocket send at handler "..handler..": "..errstr); end end, onmessage=function(handler,message,opcode) benchmark.meter(); cws:send(handler,message,opcode); end, onerror=function(handler, message) print("Client WebSocket connection is failed for socketfd "..handler..". Error: "..message); end, onclose=function(handler) print("Connection is closed for socketfd "..handler); end }); if(sock ~= nil) then table.insert(array,sock); else print(err_msg); err_msg=nil; collectgarbage("collect");
Speichern wir die IP-Adressen von Server A in den Dateien IP1 bzw. IP2 und dann:
for i in 1 2 do mkdir -p /opt/lapps/apps/benchmark${i} sed -e "s/IPADDR/$(cat IP1)/g" /opt/lapps/apps/benchmark/benchmark.lua > /opt/lapps/apps/benchmark${i}/benchmark${i}.lua done
Jetzt ändern wir /opt/lapps/etc/conf/lapps.json auf Server B, um diese beiden Benchmark-Dienste zu verwenden:
Server B lapps.json { "directories": { "app_conf_dir": "etc", "applications": "apps", "deploy": "deploy", "tmp": "tmp", "workdir": "workdir" }, "services": { "benchmark1": { "auto_start": true, "instances" : 112, "internal": true, "preload": [ "nap", "cws", "time" ] }, "benchmark2": { "auto_start": true, "instances" : 112, "internal": true, "preload": [ "nap", "cws", "time" ] } } }
Sind wir bereit Nein, sind wir nicht. Da wir beabsichtigen, 2 240 000 ausgehende Sockets an nur zwei Adressen zu generieren, benötigen wir mehr Ports auf der Serverseite. Es ist unmöglich, mehr als 64.000 Verbindungen zu demselben IP: Port-Paar herzustellen (tatsächlich etwas weniger als 64.000).
Auf dem Server A in der Datei LAppS / include / wsServer.h befindet sich eine Funktion void startListeners (). In dieser Funktion ersetzen wir Zeile 126
LAppSConfig::getInstance()->getWSConfig()["port"],
damit:
static_cast<int>(LAppSConfig::getInstance()->getWSConfig()["port"])+i,
LAppS neu erstellen:
make CONF=Release.AVX2 install
Ausführen der Tests für 2 240 000 Client-WebSockets.
Starten Sie das LAppS auf Server B, starten Sie dann das LAppS auf Server B und leiten Sie die Ausgabe in eine Datei wie die folgende um:
/opt/lapps/bin/lapps.avx2 > log
Bearbeiten, NB:
if you want to run several LAppS instances, then create separate directory for each instnace (like run1,run2, etc) and run each instance from within these directories. This is required for lapps.log file and of course for resulting standard output, for not to be overlapped/overwritten by concurrent LAppS instances.
Dies kann eine Weile dauern, bis alle Verbindungen hergestellt sind. Lassen Sie uns den Fortschritt überwachen.
Verwenden Sie netstat nicht, um hergestellte Verbindungen zu überwachen. Es ist sinnlos, wenn es nach etwa 150.000 Verbindungen, die in einigen Sekunden hergestellt werden, auf unbestimmte Zeit ausgeführt wird. Sehen Sie sich das lapps.log auf Server A in dem Verzeichnis an, in dem Sie sich beim Starten von LAppS befanden. Sie können den folgenden Einzeiler verwenden, um zu sehen, wie die Verbindungen hergestellt und wie sie unter IOWorkern verteilt werden:
date;awk '/ will be added to connection pool of worker/{a[$22]++}END{for(i in a){ print i, a[i]; sum+=a[i]} print sum}' lapps.log | sort -n;date
Hier ist eine Idee, wie schnell diese Verbindungen hergestellt werden:
375874 Sun Jul 21 20:33:07 +06 2019 650874 Sun Jul 21 20:34:42 +06 2019 2894 connections per second 1001874 Sun Jul 21 20:36:45 +06 2019 2974 connections per second 1182874 Sun Jul 21 20:37:50 +06 2019 2784 connections per second 1843874 Sun Jul 21 20:41:44 +06 2019 2824 connections per second 2207874 Sun Jul 21 20:45:43 +06 2019 3058 connections per second
Auf dem Server B können wir überprüfen, wie viele Benchmarks ihre Verbindungen hergestellt haben:
Nachdem Sie gesehen haben, dass die Nummer 224 ist, lassen Sie die Server eine Weile arbeiten, überwachen Sie die CPU- und Speichernutzung mit top. Möglicherweise sehen Sie so etwas (Server B links und Server A rechts):

Oder so (Server B links und Server A rechts):

Es ist klar, dass der Server B, auf dem die Benchmark-Clientdienste ausgeführt werden, stark ausgelastet ist. Der Server A ist ebenfalls stark ausgelastet, jedoch nicht immer. Manchmal kühlt es ab, während der Server B mit der Anzahl der zu erledigenden Aufgaben zu kämpfen hat. Die Verkehrsverschlüsselung auf TLS ist sehr CPU-intensiv.
Stoppen wir die Server (drücken Sie mehrmals Strg-C) und ändern Sie das Protokoll wie zuvor, jedoch in Bezug auf die geänderte Anzahl von Benchmark-Diensten (224):
echo -e ':%s/ms/^V^M/g\n:%g/^$/d\nGdd1G224/Sockets\n224\ndggZZ' | vi $i awk -v avg=0 '{rps=($1/$5);avg+=rps;}END{print ((avg*1000)/NR)*224}' log
Möglicherweise möchten Sie auch mehrere letzte Zeilen löschen, um zu berücksichtigen, dass Ausdrucke das Beenden von Benchmark-Diensten verhindern. Sie haben bereits eine Idee, wie das geht. Also werde ich die Ergebnisse für verschiedene Testszenarien veröffentlichen und mit Problemen fortfahren, mit denen ich konfrontiert bin.
Ergebnisse für alle anderen Tests (4,9 Millionen und mehr)

Test Nr. 5 für gleiche CPU-Freigabe ausgeglichen Probleme.
In der obigen Tabelle rot markiert.
Das Ausführen von mehr als 224 Benchmark-Instanzen auf Server B erwies sich als schlechte Idee, da der Server auf der Clientseite nicht in der Lage war, die CPU-Zeit gleichmäßig auf Prozesse / Threads zu verteilen. Die ersten 224 Benchmark-Instanzen, die alle Verbindungen hergestellt haben, beanspruchten den größten Teil der CPU-Ressourcen, und die übrigen Benchmark-Instanzen bleiben zurück. Selbst die Verwendung von renice -20 hilft nicht viel (448 Benchmark-Instanzen, 2 LAppS-Instanzen):
448 Benchmark-Instanzen
Der Server B (linke Seite) ist sehr stark ausgelastet und der Server A verfügt weiterhin über freie CPU-Ressourcen.
Also habe ich die Benchmark.max_connections verdoppelt, anstatt mehr separate LAppS-Instanzen zu starten.
Damit 12,3 Millionen WebSockets ausgeführt werden können, habe ich die fünfte LAppS-Instanz (Test 5 und 6) gestartet, ohne vier bereits ausgeführte zu stoppen. Und spielte die Rolle von CFQ, indem priorisierte Prozesse mit kill -STOP / -CONT oder / manuell angehalten und neu gestartet wurden und deren Prioritäten neu festgelegt wurden. Sie können hierfür das folgende Vorlagenskript verwenden:
while [ 1 ]; do kill -STOP <4 fast-processes pids> sleep 10 kill -CONT <4 fast-processes pids> sleep 5 done
Willkommen zu 2019 RHEL 7.6! Ehrlich gesagt habe ich den Befehl renice zum ersten Mal seit 2009 verwendet. Was am schlimmsten ist - ich habe ihn diesmal fast erfolglos verwendet.
Ich hatte ein Problem mit der Scatter-Gather-Engine der NICs. Deshalb habe ich es für einige Tests deaktiviert und dieses Ereignis nicht in der Tabelle markiert.
Ich hatte teilweise Verbindungsunterbrechungen unter hoher Last und den NIC-Treiberfehler, daher musste ich verwandte Testergebnisse verwerfen.
Ende der Geschichte
Ehrlich gesagt verliefen die Tests viel reibungsloser als ich erwartet hatte.
Ich bin überzeugt, dass es mir nicht gelungen ist, LAppS auf Server A in vollem Umfang (ohne SSL) zu laden, da ich nicht genügend CPU-Ressourcen für die Clientseite hatte. Obwohl TLS 1.3 aktiviert war, nutzte das LAppS auf Server A fast alle verfügbaren CPU-Ressourcen.
Ich bin immer noch davon überzeugt, dass LAppS der skalierbarste und schnellste WebSockets-Open-Source-Server auf dem Markt ist und das cws- WebSockets-Clientmodul das einzige seiner Art ist, das den Rahmen für Hochlasttests bietet.
Bitte überprüfen Sie die Ergebnisse auf Ihrer eigenen Hardware.
Hinweis: Verwenden Sie niemals Nginx oder Apache als Load Balancer oder als Proxy-Pass für WebSockets, da sonst die Leistung in der Größenordnung sinkt. Sie sind nicht für WebSockets erstellt.