Uma coisa sobre o WebSockets é que você precisa de muitos recursos no lado do cliente para gerar carga alta o suficiente para o servidor realmente consumir todos os recursos da CPU.
Há vários desafios que você precisa superar, porque o protocolo WebSockets exige mais CPU no lado do cliente do que no lado do servidor. Ao mesmo tempo, você precisa de muita RAM para armazenar informações sobre conexões abertas, se você tiver milhões delas.
Tive a sorte de obter alguns servidores novos por um período limitado de tempo para os testes de "esgotamento" de hardware. Então, decidi usar o Lua Application Server - LAppS para realizar as duas tarefas: testar o hardware e realizar os testes de alta carga do LAppS.
Vamos ver os detalhes
Desafios
Antes de tudo, os WebSockets do lado do cliente exigem um RNG bastante rápido para mascarar o tráfego, a menos que você queira fingir. Queria que os testes fossem realistas, então descartei a idéia de falsificar o RNG, substituindo-o por uma constante. Isso deixa a única opção - muita energia da CPU. Como eu disse, tenho dois servidores: um com dois processadores Intel Gold 6148 (40 núcleos no total) 256 GB de RAM (DDR4-2666) e outro com quatro processadores Intel Platinum 8180 (112 núcleos no total) 384 GB de RAM (DDR4-2666) . Vou me referir a eles como Servidor A e Servidor B a seguir.
O segundo desafio - não há bibliotecas ou conjuntos de testes para WebSockets que sejam rápidos o suficiente. Por isso, fui forçado a desenvolver um módulo cliente WebSockets para LAppS ( cws ).
A configuração dos testes
Ambos os servidores possuem placas de 10GbE de porta dupla. A porta 1 de cada placa é agregada a uma interface de ligação e essas portas nas duas placas são conectadas uma à outra no modo RR. A porta 2 de cada placa é agregada a uma interface de ligação no modo de backup ativo, conectado por um comutador Ethernet. Cada servidor de hardware estava executando o RHEL 7.6. Eu tive que instalar o gcc-8.3 a partir das fontes para ter um compilador moderno em vez do padrão gcc-4.8.5. Não é a melhor configuração para os testes de desempenho, mas os mendigos não podem escolher.
As CPUs no servidor A (2xGold 6148) têm maior frequência do que no servidor B (4xPlatinum 8180). Ao mesmo tempo, o servidor B tem mais núcleos, por isso decidi executar um servidor de eco no servidor A e os clientes de eco no servidor B.
Eu queria ver como o LAppS se comporta sob alta carga com milhões de conexões e qual a quantidade máxima de solicitações de eco por segundo que ele pode atender. Na verdade, são dois conjuntos diferentes de testes porque, com o crescimento das conexões, o servidor e os clientes estão realizando um trabalho cada vez mais complexo.
Antes disso, fiz todos os testes no meu PC doméstico que utilizo para o desenvolvimento. Os resultados desses testes serão usados para comparação. Meu PC doméstico possui uma CPU Intel Core i7-7700 de 3,6 GHz (4,0 GHz Turbo) com 4 núcleos e 32 GB de RAM DDR4-2400. Este PC roda o Gentoo com o kernel 4.14.72.
Níveis de patch Spectre e Meltdown- Pc em casa
/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 e 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
Farei uma anotação extra com os resultados dos testes dos Servidores A e B, independentemente de esses patches estarem ativados ou não.
No meu PC em casa, o nível do patch nunca é alterado.
Instalando o LAppS 0.8.1
Instalando pré-requisitos e o LAppSO LAppS depende das bibliotecas luajit-2.0.5 ou superior, libcrypto ++ 8.2 e wolfSSL-3.15.7 e elas devem ser instaladas a partir de fontes no RHEL 7.6 e provavelmente em qualquer outra distribuição linux.
O prefixo para instalação é / usr / local. Aqui está a parte do Dockerfile praticamente auto-explicativa para a instalação do 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
E aqui está a parte para a instalação da 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
E o 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
Uma dependência opcional do LAppS, que pode ser ignorada, é a nova biblioteca do Microsoft mimalloc . Essa biblioteca melhora significativamente o desempenho (cerca de 1%), mas requer o cmake-3.4 ou superior. Dada a quantidade limitada de tempo para os testes, decidi sacrificar a melhoria de desempenho mencionada.
No meu PC doméstico, não desabilitarei o mimalloc durante os testes.
Permite fazer checkout do LAppS no repositório:
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
Agora precisamos remover "-lmimalloc" de todos os Makefiles no subdiretório nbproject , para que possamos construir o LAppS (assumindo que o diretório atual seja $ {WORKSPACE} / LAppS)
E agora podemos construir o LAppS. O LAppS fornece várias configurações de compilação, que podem ou não excluir alguns recursos no lado do servidor:
- com suporte SSL e com suporte a coleta de estatísticas
- com SSL e sem coleta de estatísticas (embora a coleta mínima de estatísticas continue enquanto é usada para o ajuste dinâmico do LAppS em tempo de execução)
- sem SSL e sem coleta de estatísticas.
Antes da próxima etapa, verifique se você é o proprietário do diretório / opt / lapps (ou execute make installs com sudo)
Vamos fazer dois tipos de binários com suporte a SSL e coleta de estatísticas e sem (supondo que estejamos dentro do diretório $ {WORKSPACE} / LAppS):
Os binários resultantes são:
- dist / Release.AVX2 / GNU-Linux / lapps.avx2
- dist / Release.AVX2.NO_STATS.NO_TLS / GNU-Linux / lapps.avx2.nostats.notls
Eles serão instalados em / opt / lapps / bin.
Observe que o módulo do cliente WebSockets para Lua é sempre construído com suporte a SSL. Se está habilitado ou não, depende do URI que você está usando para conexão (ws: // ou wss: //) em tempo de execução.
Já estabeleci que obtenho o melhor desempenho ao configurar quatro instâncias de serviço de benchmark com 100 conexões cada. Ao mesmo tempo, preciso de apenas três IOWorkers e quatro instâncias de serviço de eco para obter o melhor desempenho. Lembra? Eu tenho apenas 4 núcleos aqui.
O objetivo deste teste é apenas estabelecer uma linha de base para comparação adicional. Nada emocionante aqui realmente.
Abaixo estão as etapas necessárias para configurar o LAppS para os testes.
Certificados autoassinados
A execução desse script no diretório / opt / lapps / conf / ssl criará todos os arquivos necessários. Aqui está a saída do script e o que eu digitei:
saída 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
Configuração LAppS
Aqui está o arquivo de configuração do WebSockets definido para o TLS 1.3, inclui os certificados gerados acima e configurado com 3 IOWorkers (consulte a descrição detalhada das variáveis no wiki do 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" } }
Configurando serviços: o servidor de eco e o cliente de eco (benchmark), cada um com quatro instâncias.
/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" ] } } }
O serviço de eco é bastante trivial:
código fonte do serviço de eco 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
O serviço de benchmark cria até benchmark.max_connections em benchmark.target e depois é executado até você parar o LAppS. Não há pausa no estabelecimento das conexões ou solicitações de eco de bombardeio. A API do módulo cws se assemelha à API da Web para WebSockets. Depois que todas as benchmark.max_connections forem estabelecidas, a benchmark imprime a quantidade de soquetes conectados. Depois que a conexão é estabelecida, o benchmark envia um benchmark.message ao servidor. Após o servidor responder, o método onmessage anônimo do objeto cws é chamado, o que apenas envia a mesma mensagem de volta ao servidor.
código fonte do serviço de referência 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
Instalação de serviços
Agora precisamos colocar os scripts de serviço em /opt/lapps/apps//.lua:
- /opt/lapps/apps/benchmark/benchmark.lua
- /opt/lapps/apps/echo/echo.lua
Estamos prontos para executar nossa referência agora. Basta executar: rm -f lapps.log; /opt/lapps/bin/lapps.avx2 > log
rm -f lapps.log; /opt/lapps/bin/lapps.avx2 > log
no diretório LAppS, aguarde 5 minutos e pressione Ctrl-C uma vez para interromper o LAppS (ele não para imediatamente, interrompe as conexões primeiro) ou duas vezes (isso interromperá a sequência de desligamento).
Ok, temos um arquivo de texto com algo assim dentro:
saída de referênciaecho :: onStart
echo :: onStart
echo :: onStart
echo :: onStart
correndo
1 mensagens recebidas por 3196ms
1 mensagens recebidas por 3299ms
1 mensagens recebidas por 3299ms
1 mensagens recebidas por 3305ms
Soquetes conectados: 100
Soquetes conectados: 100
Soquetes conectados: 100
Soquetes conectados: 100
134597 mensagens recebidas por 1000ms
139774 mensagens recebidas por 1000 ms
138521 mensagens recebidas por 1000 ms
139404 mensagens recebidas por 1000ms
140162 mensagens recebidas por 1000ms
139337 mensagens recebidas por 1000 ms
140088 mensagens recebidas por 1000ms
139946 mensagens recebidas por 1000ms
141204 mensagens recebidas por 1000ms
137988 mensagens recebidas por 1000ms
141805 mensagens recebidas por 1000ms
134733 mensagens recebidas por 1000ms
...
Vamos limpar esse arquivo de log assim:
echo -e ':%s/ms/^V^M/g\n:%g/^$/d\nGdd1G4/Sockets\n4\ndggZZ' | vi $i
'^ V ^ M' é uma representação visível das seguintes ocorrências de teclas: Ctrl-V Ctrl-V Ctrl-V Ctrl-M, portanto, será bastante inútil copiar e colar esta linha do bash. Breve explicação:
- temos que substituir os símbolos 'ms' pelo final da linha, porque não precisamos deles, eles irão atrapalhar os cálculos mais tarde e 4 benchmarks trabalhando em paralelo podem imprimir seus resultados em uma linha.
- precisamos remover todas as linhas vazias depois
- removemos a última linha também, porque paramos o servidor
- no arquivo de log , haverá apenas linhas anteriores que consistem em soquetes conectados: 100 (é porque executamos apenas quatro serviços de benchmark). Então, pulamos 4 linhas além da última delas e depois removemos tudo para a parte superior do arquivo.
- salvando o arquivo.
O arquivo é salvo e você está de volta ao shell agora, e o arquivo de log está pronto para a próxima etapa:
Esse liner único do awk calcula a quantidade de respostas de eco por ms e acumula o resultado na variável avg . Depois que todas as linhas do arquivo de log são processadas, multiplica-se a média de 1000 para obter a quantidade total de respostas de eco por segundo, dividindo-se pelo número de linhas e multiplicando-a pela quantidade de serviços de referência. Isso nos dá o número médio de respostas de eco por segundo para esta execução de teste.
No meu PC, esse número (ERps) é: 563854
Vamos fazer o mesmo sem suporte a SSL e ver a diferença:
- altere o valor da variável tls em ws.json para false
- altere benchmark.target em benchmark.lua de wss: // para ws: //
- execute
rm -f lapps.log; /opt/lapps/bin/lapps.avx2.nostats.notls > log
rm -f lapps.log; /opt/lapps/bin/lapps.avx2.nostats.notls > log
no diretório LAppS e repita as etapas acima.
Eu tenho: 721236 respostas por segundo
A diferença de desempenho com SSL e sem SSL é de cerca de 22%. Vamos manter esses números em mente para referência futura.
Com a mesma configuração no servidor A, tenho: 421905 ERpS com SSL e 443145 ERpS sem SSL. Os patches para Spectre e Meltdown foram desativados.
No servidor B, tenho 270996 ERpS com SSL e 318522 ERpS sem SSL com os patches ativados. 385726 e 372126 sem e com SSL. Os patches para Spectre e Meltdown também foram desativados.
Os resultados são piores com a mesma configuração, porque as CPUs nesses servidores têm menor frequência.
Por favor, tenha cuidado que os clientes são muito dependentes da disponibilidade de dados em / dev / urandom. Pode demorar um pouco até os clientes começarem a executar, se você já o executou uma vez. Então, basta esperar que eles comecem a trabalhar, para que sejam bem rápidos no que fazem. Apenas monitore com o topo se as instâncias do LAppS realmente estão fazendo algum trabalho. Se / dev / urandom estiver esgotado, o LAppS não consumirá suas CPUs até que haja alguns dados disponíveis.
Preparando-se para grandes testes
Antes de tudo, precisamos fazer algumas alterações nos parâmetros do kernel e não esquecer o ulimit para o número de arquivos abertos. Eu usei quase a mesma configuração, como neste artigo .
Crie um arquivo com o seguinte conteúdo
: 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"
Em seguida, use source ./filename nos dois servidores para alterar os parâmetros do kernel e o ulimit.
Desta vez, meu objetivo é criar vários milhões de clientes em um servidor e conectá-los ao segundo.
O servidor A servirá como o lado do servidor do serviço de eco WebSockets.
O servidor B servirá como o lado do cliente do serviço de eco WebSockets.
Há uma limitação no LAppS imposta pelo LuaJIT. Você pode usar apenas 2 GB de RAM por VM LuaJIT. Esse é o limite de RAM que você pode usar em uma única instância do LAppS para todos os serviços, pois os serviços são os encadeamentos vinculados a uma instância do libluajit. Se algum de seus serviços que estiver executando sob a instância única do LAppS exceder esse limite, todos os serviços ficarão sem memória.
Descobri que, por uma única instância do LAppS, não é possível estabelecer mais de 2 464 000 WebSockets clientes com o tamanho da mensagem de 64 bytes. O tamanho da mensagem pode alterar levemente esse limite, porque o LAppS passa a mensagem para o serviço cws , alocando o espaço para essa mensagem no LuaJIT.
Isso implica que eu tenho que iniciar várias instâncias do LAppS com a mesma configuração no servidor B para estabelecer mais de 2,4 milhões de WebSockets. O servidor A (servidor de eco) não usa tanta memória no lado LuaJIT; portanto, uma instância do LAppS cuidará de 12,3 milhões de WebSockets sem nenhum problema.
Vamos preparar duas configurações diferentes para os servidores A e 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" } }
O servidor A possui duas interfaces:
- bond0 - xx203.37
- bond1 - xx23.10
Um é mais rápido, o outro é mais lento, mas isso realmente não importa. O servidor estará sob carga pesada de qualquer maneira.
Vamos preparar um modelo em nosso /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");
Vamos armazenar os endereços IP do servidor A nos arquivos IP1 e IP2, respectivamente, e depois:
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
Agora modificamos o /opt/lapps/etc/conf/lapps.json no servidor B para usar estes dois serviços de benchmark:
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 prontos? Não, não somos. Como pretendemos gerar 2 240 000 soquetes de saída em apenas dois endereços e precisamos de mais portas no lado do servidor. É impossível criar mais de 64k conexões com o mesmo par ip: port (na verdade, um pouco menos que 64k).
No servidor A, no arquivo LAppS / include / wsServer.h, existe uma função void startListeners (). Nesta função, substituiremos a linha 126
LAppSConfig::getInstance()->getWSConfig()["port"],
com isso:
static_cast<int>(LAppSConfig::getInstance()->getWSConfig()["port"])+i,
Reconstruir LAppS:
make CONF=Release.AVX2 install
Executando os testes para 2 240 000 WebSockets do cliente.
Inicie o LAppS no servidor B, inicie o LAppS no servidor B e redirecione a saída para um arquivo 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.
Isso pode demorar um pouco até que todas as conexões sejam estabelecidas. Vamos monitorar o progresso.
Não use o netstat para assistir a conexões estabelecidas; é inútil enquanto é executado indefinidamente após conexões de 150k, que são estabelecidas em alguns segundos. Veja o lapps.log no servidor A, no diretório em que você estava quando iniciou o LAppS. Você pode usar a seguinte linha única para ver como as conexões são estabelecidas e como elas são distribuídas entre os IOWorkers:
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
Aqui está uma idéia sobre a rapidez com que essas conexões são estabelecidas:
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
No servidor B, podemos verificar a quantidade de benchmarks concluídos, estabelecendo suas conexões:
Depois de ver que o número é 224, deixe os servidores trabalharem por um tempo, monitore o uso da CPU e da memória com o máximo. Você pode ver algo assim (Servidor B à esquerda e Servidor A à direita):

Ou assim (Servidor B à esquerda e Servidor A à direita):

É claramente o servidor B, onde os serviços do cliente de referência estão em execução, está sob carga pesada. O servidor A também está sob carga pesada, mas nem sempre. Algumas vezes, esfria enquanto o servidor B luta com a quantidade de tarefas para trabalhar. A codificação de tráfego no TLS consome muita CPU.
Vamos parar os servidores (pressione Ctrl-C várias vezes) e modifique o log como antes, mas com relação à quantidade alterada de serviços de benchmark (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
Você também pode excluir várias últimas linhas, para dar conta das impressões de interromper os serviços de benchmark. Você já tem uma idéia de como fazer isso. Então, publicarei os resultados para diferentes cenários de teste e continuarei com os problemas que enfrentei.
Resultados para todos os outros testes (4,9 milhões e além)

Teste 5 equilibrado para compartilhamento igual de CPU Problemas.
Marcado na tabela acima com vermelho.
A execução de mais de 224 instâncias de benchmark no servidor B provou ser uma péssima idéia, pois o servidor do lado do cliente era incapaz de distribuir uniformemente o tempo da CPU entre processos / threads. As primeiras 224 instâncias de benchmark que estabeleceram todas as suas conexões consumiram a maioria dos recursos da CPU e as demais instâncias de benchmark estão atrasadas. Mesmo o uso de renice -20 não ajuda muito (448 instâncias de benchmark, 2 instâncias LAppS):
448 instâncias de benchmark
O servidor B (lado esquerdo) está sob carga muito pesada e o servidor A ainda possui recursos livres de CPU.
Então, eu dobrei o benchmark.max_connections em vez de iniciar mais instâncias separadas do LAppS.
Ainda para a execução de 12,3 milhões de WebSockets, iniciei a 5ª instância do LAppS (testes 5 e 6) sem interromper quatro que já estão em execução. E desempenhou o papel de CFQ suspendendo e reiniciando manualmente os processos priorizados com kill -STOP / -CONT ou / e renize suas prioridades. Você pode usar o seguinte script de modelo para isso:
while [ 1 ]; do kill -STOP <4 fast-processes pids> sleep 10 kill -CONT <4 fast-processes pids> sleep 5 done
Bem-vindo ao RHEL 7.6! Honestamente, usei o comando renice pela primeira vez desde 2009. O pior: usei quase sem sucesso dessa vez.
Eu tive um problema com o mecanismo de dispersão e coleta das NICs. Por isso, desativei-o para alguns testes, sem marcar esse evento na tabela.
Eu tive interrupções parciais no link sob carga pesada e o bug do driver da NIC, então tive que descartar os resultados dos testes relacionados.
Fim da história
Honestamente, os testes foram muito mais suaves do que eu esperava.
Estou convencido de que não consegui carregar o LAppS no servidor A em todo o seu potencial (sem SSL), porque eu não tinha recursos de CPU suficientes para o lado do cliente. Embora com o TLS 1.3 ativado, o LAppS no servidor A estava utilizando quase todos os recursos de CPU disponíveis.
Ainda estou convencido de que o LAppS é o servidor de código aberto WebSockets mais escalável e mais rápido disponível no mercado e o módulo cliente WebSockets da cws é o único do gênero, fornecendo a estrutura para testes de alta carga.
Verifique os resultados em seu próprio hardware.
Nota do conselho: nunca use nginx ou apache como balanceador de carga ou como um proxy-pass para WebSockets, ou você acabará reduzindo o desempenho em ordem de magnitude. Eles não são criados para WebSockets.