Una cosa acerca de WebSockets es que necesita muchos recursos del lado del cliente para generar una carga lo suficientemente alta como para que el servidor realmente consuma todos los recursos de la CPU.
Hay varios desafíos que debe superar porque el protocolo WebSockets requiere más CPU del lado del cliente que del lado del servidor. Al mismo tiempo, necesita mucha RAM para almacenar información sobre conexiones abiertas si tiene millones de ellas.
He tenido la suerte de obtener un par de servidores nuevos por un período limitado de tiempo a mi disposición para las pruebas de "desgaste" del hardware. Así que decidí usar mi Servidor de aplicaciones Lua - LAppS para hacer ambos trabajos: probar el hardware y realizar las pruebas de carga alta de LAppS.
Vamos a ver los detalles
Desafíos
En primer lugar, los WebSockets del lado del cliente requieren un RNG bastante rápido para enmascarar el tráfico, a menos que desee simularlo. Quería que las pruebas fueran realistas, así que descarté la idea de falsificar el RNG reemplazándolo por una constante. Esto deja la única opción: mucha potencia de CPU. Como dije, tengo dos servidores: uno con dos CPU Intel Gold 6148 (40 núcleos en total) 256 GB de RAM (DDR4-2666) y uno con cuatro CPU Intel Platinum 8180 (112 núcleos en total) 384 GB de RAM (DDR4-2666) . Me referiré a ellos como Servidor A y Servidor B en adelante.
El segundo desafío: no hay bibliotecas ni conjuntos de pruebas para WebSockets que sean lo suficientemente rápidos. Es por eso que me vi obligado a desarrollar un módulo de cliente WebSockets para LAppS ( cws ).
La configuración de las pruebas
Ambos servidores tienen tarjetas de 10 GbE de doble puerto. El puerto 1 de cada tarjeta se agrega a una interfaz de enlace y estos puertos en cualquiera de las tarjetas están conectados entre sí en modo RR. El puerto 2 de cada tarjeta se agrega a una interfaz de enlace en un modo Active-Backup, conectado a través de un conmutador Ethernet. Cada servidor de hardware ejecutaba el RHEL 7.6. Tuve que instalar gcc-8.3 desde las fuentes para tener un compilador moderno en lugar del gcc-4.8.5 predeterminado. No es la mejor configuración para las pruebas de rendimiento, sin embargo, los mendigos no pueden elegir.
Las CPU en el servidor A (2xGold 6148) tienen mayor frecuencia que en el servidor B (4xPlatinum 8180). Al mismo tiempo, el Servidor B tiene más núcleos, por lo que decidí ejecutar un servidor de eco en el Servidor A y los clientes de eco en el Servidor B.
Quería ver cómo LAppS se comporta bajo una gran carga con millones de conexiones y qué cantidad máxima de solicitudes de eco por segundo puede servir. Estos son en realidad dos conjuntos diferentes de pruebas porque con el crecimiento de la cantidad de conexiones, el servidor y los clientes están haciendo un trabajo cada vez más complejo.
Antes de esto, hice todas las pruebas en la PC de mi casa que utilizo para el desarrollo. Los resultados de estas pruebas se utilizarán para comparación. La PC de mi casa tiene una CPU Intel Core i7-7700 3.6GHz (Turbo 4.0GHz) con 4 núcleos y 32GB de RAM DDR4-2400. Esta PC ejecuta Gentoo con el kernel 4.14.72.
Niveles de parche Spectre y Meltdown- PC casera
/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
- Servidores A y 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
Haré una nota adicional con los resultados de las pruebas de los Servidores A y B si estos parches están habilitados o no.
En mi PC, el nivel de parche nunca cambia.
Instalación de LAppS 0.8.1
Instalación de requisitos previos y LAppSLAppS depende de las bibliotecas luajit-2.0.5 o superior, libcrypto ++ 8.2 y wolfSSL-3.15.7 y deben instalarse desde las fuentes en RHEL 7.6 y probablemente en cualquier otra distribución de Linux.
El prefijo para la instalación es / usr / local. Aquí está la parte Dockerfile bastante explicativa para la instalación de wolfSSL
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
Y aquí está la parte para la instalación de 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
Y el 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
Una dependencia opcional de LAppS, que puede ignorarse, es la nueva biblioteca de Microsoft mimalloc . Esta biblioteca mejora significativamente el rendimiento (alrededor del 1%) pero requiere cmake-3.4 o superior. Dado el tiempo limitado para las pruebas, decidí sacrificar la mejora de rendimiento mencionada.
En la PC de mi casa, no deshabilitaré mimalloc durante las pruebas.
Vamos a pagar LAppS desde el repositorio:
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
Ahora necesitamos eliminar "-lmimalloc" de todos los Makefiles en el subdirectorio nbproject , para poder construir LAppS (suponiendo que nuestro directorio actual sea $ {WORKSPACE} / LAppS)
Y ahora podemos construir LAppS. LAppS proporciona varias configuraciones de compilación, que pueden excluir o no algunas características en el lado del servidor:
- con soporte SSL y con soporte de recopilación de estadísticas
- con SSL y sin recopilación de estadísticas (aunque la recopilación mínima de estadísticas continuará, ya que se utiliza para el ajuste dinámico de LAppS en tiempo de ejecución)
- sin SSL y sin recopilación de estadísticas.
Antes del siguiente paso, asegúrese de ser el propietario del directorio / opt / lapps (o ejecute make installs con sudo)
Hagamos dos tipos de binarios con soporte SSL y recopilación de estadísticas y sin ellos (suponiendo que estemos dentro del directorio $ {WORKSPACE} / LAppS):
Los binarios resultantes son:
- dist / Release.AVX2 / GNU-Linux / lapps.avx2
- dist / Release.AVX2.NO_STATS.NO_TLS / GNU-Linux / lapps.avx2.nostats.notls
Se instalarán en / opt / lapps / bin.
Tenga en cuenta que el módulo de cliente WebSockets para Lua siempre se crea con soporte SSL. Si está habilitado o no depende del URI que esté utilizando para la conexión (ws: // o wss: //) en tiempo de ejecución.
Ya he establecido que obtengo el mejor rendimiento cuando configuro cuatro instancias de servicio de referencia con 100 conexiones cada una. Al mismo tiempo, solo necesito tres IOWorkers y cuatro instancias de servicio de eco para lograr el mejor rendimiento. Recuerda? Tengo solo 4 núcleos aquí.
El propósito de esta prueba es solo establecer una línea de base para una comparación adicional. Nada emocionante aquí realmente.
A continuación se detallan los pasos necesarios para configurar LAppS para las pruebas.
Certificados autofirmados
La ejecución de este script desde el directorio / opt / lapps / conf / ssl creará todos los archivos necesarios. Aquí está la salida del script y lo que he escrito:
salida 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
Configuración de LAppS
Aquí está el archivo de configuración de WebSockets que está configurado para TLS 1.3, incluye los certificados generados anteriormente y está configurado con 3 IOWorkers (consulte la descripción detallada de las variables en el wiki de LAppS).
/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" } }
Configuración de servicios: el servidor de eco y el cliente de eco (punto de referencia), cada uno con cuatro instancias.
/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" ] } } }
El servicio de eco es bastante trivial:
código fuente del servicio echo 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
El servicio de benchmark crea tantos como benchmark.max_ connections a benchmark.target y luego se ejecuta hasta que detenga LAppS. No hay pausa en el establecimiento de las conexiones o bombardeo de solicitudes de eco. La API del módulo cws se parece a la API web para WebSockets. Una vez que todas las conexiones benchmark.max_ están establecidas, el benchmark imprime la cantidad de Sockets conectados. Una vez que se establece la conexión, el punto de referencia envía un mensaje de referencia al servidor. Después de que el servidor responde, se invoca el método de mensaje anónimo del objeto cws , que simplemente envía el mismo mensaje al servidor.
código fuente del servicio de referencia 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
Instalacion de servicios
Ahora necesitamos colocar los scripts de servicio en /opt/lapps/apps//.lua:
- /opt/lapps/apps/benchmark/benchmark.lua
- /opt/lapps/apps/echo/echo.lua
Estamos listos para ejecutar nuestro punto de referencia ahora. Simplemente ejecute: rm -f lapps.log; /opt/lapps/bin/lapps.avx2 > log
rm -f lapps.log; /opt/lapps/bin/lapps.avx2 > log
desde el directorio LAppS y espere 5 minutos, luego presione Ctrl-C una vez, para detener LAppS (no se detendrá inmediatamente, cerrará las conexiones primero), o dos veces (esto interrumpirá la secuencia de apagado).
Ok, tenemos un archivo de texto con algo como esto adentro:
salida de referenciaecho :: onStart
echo :: onStart
echo :: onStart
echo :: onStart
corriendo
1 mensajes recibidos por 3196 ms
1 mensajes recibidos por 3299 ms
1 mensajes recibidos por 3299 ms
1 mensajes recibidos por 3305 ms
Zócalos conectados: 100
Zócalos conectados: 100
Zócalos conectados: 100
Zócalos conectados: 100
134597 mensajes recibidos por 1000ms
139774 mensajes recibidos por 1000ms
138521 mensajes recibidos por 1000ms
139404 mensajes recibidos por 1000ms
140162 mensajes recibidos por 1000ms
139337 mensajes recibidos por 1000 ms
140088 mensajes recibidos por 1000ms
139946 mensajes recibidos por 1000 ms
141204 mensajes recibidos por 1000ms
137988 mensajes recibidos por 1000ms
141805 mensajes recibidos por 1000ms
134733 mensajes recibidos por 1000 ms
...
Limpiemos este archivo de registro así:
echo -e ':%s/ms/^V^M/g\n:%g/^$/d\nGdd1G4/Sockets\n4\ndggZZ' | vi $i
'^ V ^ M' es una representación visible de las siguientes teclas: Ctrl-V Ctrl-V Ctrl-V Ctrl-M, por lo que será bastante inútil copiar y pegar esta línea bash. Breve explicación:
- tenemos que reemplazar los símbolos 'ms' con el final de la línea, ya que no los necesitamos, desordenarán los cálculos más adelante y 4 puntos de referencia que funcionen en paralelo pueden imprimir sus resultados en una línea.
- necesitamos eliminar todas las líneas vacías después
- también eliminamos la última línea, porque detenemos el servidor
- en el archivo de registro solo habrá líneas anteriores que consisten en Sockets conectados: 100 (es porque ejecutamos solo cuatro servicios de referencia). Por lo tanto, omitimos 4 líneas más allá del último de ellos, y luego eliminamos todo al principio del archivo.
- guardando el archivo.
El archivo se guarda y ahora está de vuelta en el shell, y el archivo de registro está listo para el siguiente paso:
Este awk single-liner calcula la cantidad de respuestas de eco por ms y acumula el resultado en una variable promedio . Después de que se procesan todas las líneas en el archivo de registro, se multiplica avg a 1000 para obtener la cantidad total de las respuestas de eco por segundo, dividiendo el número de líneas y multiplicando por la cantidad de servicios de referencia. Esto nos da el número promedio de respuestas de eco por segundo para esta ejecución de prueba.
En mi PC este número (ERps) es: 563854
Hagamos lo mismo sin soporte SSL y veamos la diferencia:
- cambiar el valor de la variable tls en ws.json a falso
- cambie benchmark.target en benchmark.lua de wss: // a ws: //
- ejecute
rm -f lapps.log; /opt/lapps/bin/lapps.avx2.nostats.notls > log
rm -f lapps.log; /opt/lapps/bin/lapps.avx2.nostats.notls > log
desde el directorio LAppS y repita los pasos anteriores.
Tengo 721236 respuestas por segundo
La diferencia en el rendimiento con SSL y sin SSL es aproximadamente del 22%. Tengamos en cuenta estos números para futuras referencias.
Con la misma configuración en el Servidor A, tengo: 421905 ERpS con SSL y 443145 ERpS sin SSL. Los parches para Spectre y Meltdown fueron desactivados.
En el Servidor B, tengo 270996 ERpS con SSL y 318522 ERpS sin SSL con los parches habilitados. 385726 y 372126 sin y con SSL. Los parches para Spectre y Meltdown también fueron desactivados.
Los resultados son peores con la misma configuración porque las CPU en estos servidores tienen menor frecuencia.
Tenga en cuenta que los clientes dependen mucho de la disponibilidad de datos en / dev / urandom. Puede llevar un tiempo hasta que los clientes realmente comiencen a ejecutarse, si ya lo ejecutó una vez. Entonces, solo espere a que comiencen a trabajar, luego son bastante rápidos en lo que hacen. Simplemente supervise con la parte superior si las instancias de LAppS realmente hacen algún trabajo. Si / dev / urandom está agotado, LAppS no consumirá sus CPU hasta que haya algunos datos disponibles.
Preparándose para grandes pruebas
En primer lugar, debemos hacer algunos cambios en los parámetros del kernel y no olvidar el ulimit para nr de archivos abiertos. Usé casi la misma configuración que en este artículo .
Crea un archivo con el siguiente contenido
: 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"
Luego use source ./filename en ambos servidores para cambiar los parámetros del kernel y el ulimit.
Esta vez mi propósito es crear varios millones de clientes en un servidor y conectarlos al segundo.
El servidor A servirá como el servidor del servicio de eco WebSockets.
El servidor B servirá como el lado del cliente del servicio de eco de WebSockets.
Hay una limitación en LAppS impuesta por LuaJIT. Puede usar solo 2GB de RAM por LuaJIT VM. Ese es el límite de RAM que puede usar dentro de una sola instancia de LAppS para todos los servicios, ya que los servicios son los hilos vinculados contra una instancia de libluajit. Si alguno de sus servicios que se ejecuta bajo la única instancia de LAppS excede este límite, entonces todos los servicios estarán sin memoria.
Descubrí que por cada instancia de LAppS no puede establecer más de 2 464 000 clientes WebSockets con un tamaño de mensaje de 64 bytes. El tamaño del mensaje puede cambiar ligeramente este límite, porque LAppS pasa el mensaje al servicio cws asignando el espacio para este mensaje dentro de LuaJIT.
Esto implica que tengo que iniciar varias instancias de LAppS con la misma configuración en el Servidor B para establecer más de 2.4 millones de WebSockets. El Servidor A (el servidor de eco) no usa tanta memoria en el lado de LuaJIT, por lo que la única instancia de LAppS se encargará de 12,3 millones de WebSockets sin ningún problema.
Vamos a preparar dos configuraciones diferentes para los servidores A y B.
Servidor 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" } }
Servidor 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" : [] } } } }
Servidor 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" } }
El servidor A tiene dos interfaces:
- bond0 - xx203.37
- bond1 - xx23.10
Uno es más rápido, otro es más lento, pero en realidad no importa. El servidor estará bajo una gran carga de todos modos.
Vamos a preparar una plantilla de nuestro /opt/lapps/benchmark/benchmark.lua
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");
Guardemos las direcciones IP del Servidor A en los archivos IP1 e IP2 respectivamente y luego:
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
Ahora modificamos el /opt/lapps/etc/conf/lapps.json en el Servidor B para usar estos dos servicios de referencia:
Servidor 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" ] } } }
Estamos listos? No, no lo somos. Porque tenemos la intención de generar 2 240 000 sockets salientes a solo dos direcciones y necesitamos más puertos en el lado del servidor. Es imposible crear más de 64k conexiones a la misma ip: par de puertos (en realidad, un poco menos de 64k).
En el Servidor A en el archivo LAppS / include / wsServer.h hay una función void startListeners (). En esta función reemplazaremos la línea 126
LAppSConfig::getInstance()->getWSConfig()["port"],
con esto:
static_cast<int>(LAppSConfig::getInstance()->getWSConfig()["port"])+i,
Reconstruir aplicaciones:
make CONF=Release.AVX2 install
Ejecución de las pruebas para 2 240 000 clientes WebSockets.
Inicie LAppS en el Servidor B, luego inicie LAppS en el Servidor B y redirija la salida a un archivo como este:
/opt/lapps/bin/lapps.avx2 > log
Editar, 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.
Esto puede demorar hasta que se establezcan todas las conexiones. Vamos a monitorear el progreso.
No use netstat para mirar conexiones establecidas, no tiene sentido mientras se ejecuta indefinidamente después de 150k conexiones que se establecen en algunos segundos. Mire el lapps.log en el Servidor A, en el directorio en el que estaba cuando inició LAppS. Puede usar la siguiente línea para ver cómo se establecen las conexiones y cómo se distribuyen entre los trabajadores de IOW:
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
Aquí hay una idea de qué tan rápido se establecen estas conexiones:
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
En el Servidor B podemos verificar la cantidad de puntos de referencia terminados de establecer sus conexiones:
Después de que vea que el número es 224, deje que los servidores funcionen por un tiempo, controle el uso de CPU y memoria con la parte superior. Es posible que vea algo como esto (Servidor B a la izquierda y Servidor A en el lado derecho):

O así (Servidor B a la izquierda y Servidor A en el lado derecho):

Claramente, el Servidor B, donde se ejecutan los servicios de cliente de referencia, está bajo una gran carga. El servidor A también está bajo una carga pesada, pero no siempre. Algunas veces se enfría mientras el Servidor B lucha con la cantidad de tareas para trabajar. El cifrado de tráfico en TLS requiere mucha CPU.
Detengamos los servidores (presione Ctrl-C varias veces) y modifiquemos el registro como antes pero con respecto a la cantidad cambiada de servicios de referencia (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
Es posible que desee eliminar varias últimas líneas también, para tener en cuenta las impresiones de detener los servicios de referencia. Ya tienes una idea de cómo hacer esto. Así que publicaré los resultados para diferentes escenarios de prueba y procederé con los problemas que he enfrentado.
Resultados para todas las otras pruebas (4.9 millones y más)

prueba # 5 equilibrada para compartir la CPU por igual Problemas
Marcado en la tabla de arriba con rojo.
Ejecutar más de 224 instancias de referencia en el Servidor B demostró ser una mala idea, ya que el servidor en el lado del cliente era incapaz de distribuir uniformemente el tiempo de CPU entre procesos / subprocesos. Las primeras 224 instancias de referencia que han establecido todas sus conexiones tomaron la mayoría de los recursos de la CPU y el resto de las instancias de referencia están rezagadas. Incluso usar renice -20 no ayuda mucho (448 instancias de referencia, 2 instancias de LAppS):
448 instancias de referencia
El servidor B (lado izquierdo) está bajo una carga muy pesada y el servidor A todavía tiene recursos de CPU libres.
Así que dupliqué benchmark.max_ connections en lugar de iniciar más instancias de LAppS separadas.
Aún para ejecutar 12,3 millones de WebSockets, he comenzado la quinta instancia de LAppS (pruebas 5 y 6) sin detener cuatro que ya están en ejecución. Y jugó el papel de CFQ suspendiendo y reiniciando manualmente los procesos priorizados con kill -STOP / -CONT o / y renice sus prioridades. Puede usar el siguiente script de plantilla para esto:
while [ 1 ]; do kill -STOP <4 fast-processes pids> sleep 10 kill -CONT <4 fast-processes pids> sleep 5 done
¡Bienvenido a 2019 RHEL 7.6! Honestamente, usé el comando renice por primera vez desde 2009. Lo que es peor, esta vez lo usé casi sin éxito.
Tuve un problema con el motor de dispersión-reunión de las NIC. Así que lo desactivé para algunas pruebas, sin marcar este evento en la tabla.
Tuve interrupciones de enlace parciales bajo una gran carga y el error del controlador NIC, por lo que tuve que descartar los resultados de las pruebas relacionadas.
Fin de la historia
Honestamente, las pruebas fueron mucho más suaves de lo que esperaba.
Estoy convencido de que no he logrado cargar LAppS en el servidor A en todo su potencial (sin SSL), porque no tenía suficientes recursos de CPU para el lado del cliente. Aunque con TLS 1.3 habilitado, LAppS en el Servidor A estaba utilizando casi todos los recursos de CPU disponibles.
Todavía estoy convencido de que LAppS es el servidor de código abierto WebSockets más escalable y rápido que existe y que el módulo de cliente cws WebSockets es el único de su tipo, que proporciona el marco para pruebas de alta carga.
Verifique los resultados en su propio hardware.
Nota de consejo: nunca use nginx o apache como equilibrador de carga o como paso de proxy para WebSockets, o terminará reduciendo el rendimiento en orden de magnitud. No están diseñados para WebSockets.