12,3 juta WebSockets bersamaan

Satu hal tentang WebSockets adalah bahwa Anda memerlukan banyak sumber daya di sisi klien untuk menghasilkan beban yang cukup tinggi bagi server untuk benar-benar memakan semua sumber daya CPU.


Ada beberapa tantangan yang harus Anda atasi karena protokol WebSockets lebih menuntut CPU di sisi klien daripada di sisi server. Pada saat yang sama Anda memerlukan banyak RAM untuk menyimpan informasi tentang koneksi terbuka jika Anda memiliki jutaan dari mereka.


Saya sudah cukup beruntung mendapatkan beberapa server baru untuk jangka waktu terbatas yang saya inginkan untuk pengujian perangkat keras "burnout". Jadi saya memutuskan untuk menggunakan Server Aplikasi Lua - LAppS untuk melakukan kedua pekerjaan: menguji perangkat keras dan melakukan tes beban tinggi LAppS.


Mari kita lihat detailnya


Tantangan


Pertama-tama WebSockets di sisi klien memerlukan RNG yang cukup cepat untuk penutupan lalu lintas kecuali Anda ingin memalsukannya. Saya ingin tesnya realistis, jadi saya membuang ide memalsukan RNG dengan menggantinya dengan konstanta. Ini menyisakan satu-satunya pilihan - banyak daya CPU. Seperti yang saya katakan, saya memiliki dua server: satu dengan dual Intel Gold 6148 CPU (total 40 core) 256GB RAM (DDR4-2666) dan satu dengan quad Intel Platinum 8180 CPU (total 112 core) 384GB RAM (DDR4-2666) . Saya akan merujuk mereka sebagai Server A dan Server B selanjutnya.


Tantangan kedua - tidak ada perpustakaan atau ruang tes untuk WebSockets yang cukup cepat. Itulah sebabnya saya terpaksa mengembangkan modul klien WebSockets untuk LAppS (cws).


Pengaturan tes


Kedua server memiliki kartu dual port 10GbE. Port 1 dari setiap kartu digabungkan ke antarmuka pengikat dan port-port ini pada kedua kartu saling terhubung dalam mode RR. Port 2 dari setiap kartu dikumpulkan ke antarmuka ikatan dalam mode Active-Backup, yang terhubung melalui switch Ethernet. Setiap server perangkat keras menjalankan RHEL 7.6. Saya harus menginstal gcc-8.3 dari sumber untuk memiliki kompiler modern daripada default gcc-4.8.5. Bukan pengaturan terbaik untuk tes kinerja, namun pengemis tidak bisa menjadi pemilih.


CPU di Server A (2xGold 6148) memiliki frekuensi lebih tinggi daripada di Server B (4xPlatinum 8180). Pada saat yang sama Server B memiliki lebih banyak core, jadi saya memutuskan untuk menjalankan server gema di Server A dan klien gema di Server B.


Saya ingin melihat bagaimana LAppS berperilaku di bawah beban tinggi dengan jutaan koneksi dan berapa jumlah maksimum permintaan echo per detik yang dapat dilayani. Ini sebenarnya dua set tes yang berbeda karena dengan pertumbuhan jumlah koneksi server dan klien melakukan pekerjaan yang semakin kompleks.


Sebelum ini saya telah melakukan semua tes pada PC di rumah saya yang saya gunakan untuk pengembangan. Hasil tes ini akan digunakan untuk perbandingan. PC rumah saya memiliki Intel Core i7-7700 CPU 3.6GHz (4.0GHz Turbo) dengan 4 core dan 32GB DDR4-2400 RAM. PC ini menjalankan Gentoo dengan kernel 4.14.72.


Level patch Spectre dan Meltdown
  • PC rumah
    /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 dan 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 

Saya akan membuat catatan tambahan dengan hasil pengujian Server A dan B apakah tambalan ini diaktifkan atau tidak.


Pada tingkat Home PC patch saya tidak pernah berubah.


Menginstal LAppS 0.8.1


Menginstal prasyarat dan LAppS

LAppS bergantung pada luajit-2.0.5 atau lebih tinggi, libcrypto ++ 8.2 dan wolfSSL-3.15.7 perpustakaan dan mereka harus diinstal dari sumber di RHEL 7.6 dan kemungkinan di distribusi linux lainnya.


Awalan untuk instalasi adalah / usr / local. Berikut adalah bagian Dockerfile cukup jelas untuk instalasi 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 

Dan inilah bagian untuk instalasi 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 

Dan 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 

Satu ketergantungan opsional LAppS, yang dapat diabaikan, adalah perpustakaan baru dari Microsoft mimalloc . Perpustakaan ini membuat peningkatan kinerja yang signifikan (sekitar 1%) tetapi membutuhkan cmake-3.4 atau lebih tinggi. Mengingat terbatasnya waktu untuk tes, saya memutuskan untuk mengorbankan peningkatan kinerja yang disebutkan.


Di PC rumah saya, saya tidak akan menonaktifkan mimalloc selama pengujian.


Memungkinkan checkout LAppS dari repositori:


 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 

Sekarang kita perlu menghapus "-lmimalloc" dari semua Makefile di subdirektori nbproject , sehingga kita dapat membangun LAppS (dengan asumsi direktori kita saat ini adalah $ {WORKSPACE} / LAppS)


 # find ./nbproject -type f -name "*.mk" -exec sed -i.bak -e 's/-lmimalloc//g' {} \; # find ./nbproject -type f -name "*.bak" -exec rm {} \; 

Dan sekarang kita bisa membangun LAppS. LAppS menyediakan beberapa konfigurasi bangunan, yang mungkin atau mungkin tidak mengecualikan beberapa fitur di sisi server:


  • dengan dukungan SSL dan dengan dukungan pengumpulan statistik
  • dengan SSL dan tanpa pengumpulan statistik (meskipun pengumpulan statistik minimal akan berlanjut karena digunakan untuk penyetelan LAppS dinamis dalam runtime)
  • tanpa SSL dan tanpa pengumpulan statistik.

Sebelum langkah selanjutnya pastikan Anda adalah pemilik direktori / opt / lapps (atau jalankan make installs dengan sudo)


Mari kita membuat dua jenis biner dengan dukungan SSL dan pengumpulan statistik dan tanpa (dengan asumsi kita berada dalam direktori $ {WORKSPACE} / LAppS):


 # make clean # make CONF=Release.AVX2 install # make CONF=Release.AVX2.NO_STATS.NO_TLS install 

Binari yang dihasilkan adalah:


  • dist / Release.AVX2 / GNU-Linux / lapps.avx2
  • dist / Release.AVX2.NO_STATS.NO_TLS / GNU-Linux / lapps.avx2.nostats.notls

Mereka akan diinstal ke / opt / lapps / bin.


Harap dicatat bahwa modul klien WebSockets untuk Lua selalu dibangun dengan dukungan SSL. Apakah diaktifkan atau tidak tergantung pada URI yang Anda gunakan untuk koneksi (ws: // atau wss: //) di runtime.


Uji 1. Performa terbaik di PC rumah. Konfigurasi untuk baseline.


Saya telah menetapkan bahwa saya mendapatkan kinerja terbaik ketika saya mengkonfigurasi empat instance layanan benchmark dengan masing-masing 100 koneksi. Pada saat yang sama saya hanya membutuhkan tiga IOWorkers dan empat instance layanan gema untuk mencapai kinerja terbaik. Ingat? Saya hanya punya 4 core di sini.


Tujuan dari tes ini adalah hanya untuk menetapkan garis dasar untuk perbandingan lebih lanjut. Tidak ada yang mengasyikkan di sini.


Di bawah ini adalah langkah-langkah yang diperlukan untuk mengkonfigurasi LAppS untuk pengujian.


Sertifikat yang ditandatangani sendiri


skrip certgen.sh
 #!/bin/bash openssl genrsa -out example.org.key 2048 openssl req -new -key example.org.key -out example.org.csr openssl genrsa -out ca.key 2048 openssl req -new -x509 -key ca.key -out ca.crt openssl x509 -req -in example.org.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out example.org.crt cat example.org.crt ca.crt > example.org.bundle.crt 

Menjalankan skrip ini dari dalam direktori / opt / lapps / conf / ssl akan membuat semua file yang diperlukan. Ini adalah output skrip dan apa yang saya ketikkan:


keluaran 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 

Konfigurasi LAppS


Berikut ini adalah file konfigurasi WebSockets yang diatur untuk TLS 1.3, termasuk sertifikat yang dibuat di atas dan dikonfigurasi dengan 3 IOWorkers (lihat deskripsi terperinci tentang variabel dalam wiki 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" } } 

Layanan konfigurasi: server gema dan klien gema (benchmark), masing-masing dengan empat instance.


/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" ] } } } 

Layanan gema cukup sepele:


kode sumber layanan gema
 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 

Layanan benchmark membuat sebanyak benchmark.max_connections ke benchmark.target dan kemudian berjalan sampai Anda menghentikan LAppS. Tidak ada jeda dalam pendirian koneksi atau permintaan pemboman gema. API modul cws menyerupai Web API untuk WebSockets. Setelah semua benchmark.max_connections ditetapkan, benchmark mencetak jumlah Soket yang terhubung. Setelah koneksi terbentuk, benchmark mengirimkan benchmark.message ke server. Setelah server membalas metode onmessage anonim dari objek cws dipanggil, yang hanya mengirim pesan yang sama kembali ke server.


kode sumber layanan patokan
 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 -- poll events once per 10 outgoing connections -- this will improve the connection establishment speed if counter == 10 then cws:eventLoop(); counter=1 else counter = counter + 1; end end print("Sockets connected: "..#array); benchmark.start_time=time.now(); while not must_stop() do cws:eventLoop(); end for i=1,#array do array[i]:close(); cws:eventLoop(); end end return benchmark; 

Instalasi layanan


Sekarang kita perlu menempatkan skrip layanan ke /opt/lapps/apps//.lua:

  • /opt/lapps/apps/benchmark/benchmark.lua
  • /opt/lapps/apps/echo/echo.lua

Kami siap menjalankan tolok ukur kami sekarang. Jalankan saja: rm -f lapps.log; /opt/lapps/bin/lapps.avx2 > log rm -f lapps.log; /opt/lapps/bin/lapps.avx2 > log dari dalam direktori LAppS dan tunggu selama 5 menit, lalu tekan Ctrl-C sekali, untuk menghentikan LAppS (itu tidak akan berhenti segera, itu akan mematikan koneksi terlebih dahulu), atau dua kali (ini akan mengganggu urutan shutdown).


Oke, kami punya file teks dengan sesuatu seperti ini di dalamnya:


output patokan

echo :: onStart
echo :: onStart
echo :: onStart
echo :: onStart
berlari
1 pesan diterima per 3196 ms
1 pesan diterima per 3299 ms
1 pesan diterima per 3299 ms
1 pesan diterima per 3305 ms
Soket terhubung: 100
Soket terhubung: 100
Soket terhubung: 100
Soket terhubung: 100
134597 pesan diterima per 1000ms
139774 pesan diterima per 1000ms
138521 pesan diterima per 1000ms
139404 pesan diterima per 1000ms
140162 pesan diterima per 1000ms
139337 pesan diterima per 1000ms
140088 pesan diterima per 1000ms
139946 pesan diterima per 1000 ms
141.204 pesan diterima per 1000ms
137988 pesan diterima per 1000 ms
141805 pesan diterima per 1000ms
134733 pesan diterima per 1000 ms
...


Mari kita bersihkan file log ini seperti ini:


 echo -e ':%s/ms/^V^M/g\n:%g/^$/d\nGdd1G4/Sockets\n4\ndggZZ' | vi $i 

'^ V ^ M' adalah representasi yang terlihat dari klik kunci berikut: Ctrl-V Ctrl-V Ctrl-V Ctrl-V, jadi akan sangat tidak berguna untuk hanya menyalin tempelkan garis bash ini. Penjelasan singkat:


  • kita harus mengganti simbol 'ms' dengan end of line, karena kita tidak membutuhkannya, mereka akan mengacaukan perhitungan nanti dan 4 tolok ukur yang bekerja secara paralel dapat mencetak hasilnya dalam satu baris.
  • kita perlu menghapus semua baris kosong sesudahnya
  • kami menghapus baris terakhir juga, karena kami menghentikan server
  • dalam file log hanya akan ada garis depan yang terdiri dari Soket yang terhubung: 100 (itu karena kami hanya menjalankan empat layanan benchmark). Jadi kita lewati 4 baris melewati yang terakhir, dan daripada menghapus semuanya ke atas file.
  • menyimpan file.

File disimpan dan Anda kembali ke shell sekarang, dan file log siap untuk langkah selanjutnya:


 # awk -v avg=0 '{rps=($1/$5);avg+=rps;}END{print ((avg*1000)/NR)*4}' log 

Single-liner awk ini menghitung jumlah respons gema per ms, dan mengakumulasikan hasilnya dalam variabel rata-rata . Setelah semua baris dalam file log diproses, ini mengalikan rata - rata hingga 1000 untuk mendapatkan jumlah total respons gema per detik, membaginya ke jumlah baris dan mengalikannya dengan jumlah layanan benchmark. Ini memberi kami jumlah rata-rata respons gema per detik untuk pengujian ini.


Di PC saya nomor ini (ERps) adalah: 563854


Mari kita lakukan hal yang sama tanpa dukungan SSL dan lihat perbedaannya:


  • ubah nilai variabel tls di ws.json menjadi false
  • ubah benchmark.target di benchmark.lua dari wss: // ke ws: //
  • jalankan rm -f lapps.log; /opt/lapps/bin/lapps.avx2.nostats.notls > log rm -f lapps.log; /opt/lapps/bin/lapps.avx2.nostats.notls > log dari dalam direktori LAppS, dan ulangi langkah-langkah di atas.

Saya mendapat: 721236 respons per detik


Perbedaan kinerja dengan SSL dan tanpa SSL adalah sekitar 22%. Mari kita ingat angka-angka ini untuk referensi di masa mendatang.


Dengan pengaturan yang sama pada Server A, saya punya: 421905 ERpS dengan SSL dan 443145 ERpS tanpa SSL. Patch untuk Specter dan Meltdown dinonaktifkan.
Di Server B saya punya 270996 ERpS dengan SSL dan 318522 ERpS tanpa SSL dengan patch diaktifkan. 385726 dan 372126 tanpa dan dengan SSL. Patch untuk Specter dan Meltdown juga dinonaktifkan.


Hasilnya lebih buruk dengan pengaturan yang sama karena CPU pada server ini memiliki frekuensi yang lebih rendah.


Harap waspadai bahwa klien sangat tergantung pada ketersediaan data di / dev / urandom. Mungkin perlu waktu hingga klien benar-benar mulai berjalan, jika Anda sudah menjalankannya sekali. Jadi tunggu saja mereka untuk mulai bekerja, maka mereka cukup cepat pada apa yang mereka lakukan. Pantau saja dengan top jika instance LAppS benar-benar melakukan pekerjaan apa pun. Jika / dev / urandom habis maka LAppS tidak akan memakan CPU Anda sampai ada beberapa data yang tersedia.


Bersiap untuk ujian besar


Pertama-tama kita perlu membuat beberapa perubahan pada parameter kernel dan jangan lupa ulimit untuk file terbuka. Saya menggunakan pengaturan yang hampir sama seperti di artikel ini.


Buat file dengan konten berikut


 : 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" 

Kemudian gunakan source ./filename di kedua server untuk mengubah parameter kernel dan ulimit.


Kali ini tujuan saya adalah membuat beberapa juta klien di satu server dan menghubungkan mereka ke server kedua.


Server A akan berfungsi sebagai sisi server layanan gema WebSockets.
Server B akan berfungsi sebagai sisi klien layanan WebSockets echo.


Ada batasan dalam LAppS yang diberlakukan oleh LuaJIT. Anda hanya dapat menggunakan RAM 2GB per LuaJIT VM. Itu adalah batas RAM yang dapat Anda gunakan dalam instance LAppS tunggal untuk semua layanan, karena layanan adalah utas yang terkait dengan satu instance libluajit. Jika salah satu layanan Anda yang berjalan dalam satu contoh LAppS melebihi batas ini maka semua layanan akan kehabisan memori.


Saya menemukan bahwa per instance LAppS Anda tidak dapat membuat lebih dari 2.464.000 klien WebSockets dengan ukuran pesan 64 byte. Ukuran pesan mungkin sedikit mengubah batas ini, karena LAppS meneruskan pesan ke layanan cws dengan mengalokasikan ruang untuk pesan ini dalam LuaJIT.


Ini menyiratkan bahwa saya harus memulai beberapa instance LAppS dengan konfigurasi yang sama pada Server B untuk membangun lebih dari 2,4 juta WebSockets. Server A (server gema) tidak menggunakan banyak memori di sisi LuaJIT, sehingga satu contoh LAppS akan menangani 12,3 juta WebSockets tanpa masalah.


Mari kita siapkan dua konfigurasi berbeda untuk server A dan B.


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 memiliki dua antarmuka:


  • bond0 - xx203.37
  • bond1 - xx23.10

Satu lebih cepat yang lain lebih lambat tetapi tidak terlalu penting. Server akan berada di bawah beban yang berat.


Mari kita siapkan templat dari /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"); -- force garbage collection on connection failure. end -- poll events once per 10 outgoing connections -- this will improve the connection establishment speed if counter == 10 then cws:eventLoop(); counter=1 else counter = counter + 1; end end print("Sockets connected: "..#array); benchmark.start_time=time.now(); while not must_stop() do cws:eventLoop(); end for i=1,#array do array[i]:close(); cws:eventLoop(); end end return benchmark; 

Mari kita menyimpan alamat IP Server A ke file masing-masing IP1 dan IP2 lalu:


 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 

Sekarang kita memodifikasi /opt/lapps/etc/conf/lapps.json di Server B untuk menggunakan dua layanan benchmark ini:


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

Apakah kita siap? Tidak, tidak. Karena kami bermaksud untuk menghasilkan 2 240.000 soket keluar hanya untuk dua alamat dan kami membutuhkan lebih banyak port di sisi server. Tidak mungkin membuat lebih dari 64k koneksi ke ip yang sama: port pair (sebenarnya sedikit kurang dari 64k).


Pada Server A dalam file LAppS / include / wsServer.h ada fungsi void startListeners (). Dalam fungsi ini kita akan mengganti baris 126


 LAppSConfig::getInstance()->getWSConfig()["port"], 

dengan ini:


 static_cast<int>(LAppSConfig::getInstance()->getWSConfig()["port"])+i, 

Membangun kembali LAppS:


 make CONF=Release.AVX2 install 

Menjalankan tes untuk 2 240.000 klien WebSockets.


Mulai LAppS di Server B, kemudian mulai LAppS di Server B dan arahkan output ke file seperti ini:


 /opt/lapps/bin/lapps.avx2 > log 

Edit, 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. 

Ini mungkin memerlukan waktu hingga semua koneksi terjalin. Mari kita memantau perkembangannya.


Jangan menggunakan netstat untuk menonton koneksi yang sudah ada, itu tidak ada gunanya ketika ia berjalan tanpa batas setelah seperti koneksi 150k yang dibuat dalam beberapa detik. Lihatlah lapps.log di Server A, di direktori tempat Anda berada ketika memulai LAppS. Anda dapat menggunakan one-liner berikut untuk melihat bagaimana koneksi dibuat dan bagaimana mereka didistribusikan di antara 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 

Berikut adalah gagasan tentang seberapa cepat koneksi ini dibuat:


 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 

Di Server B kami dapat memeriksa jumlah tolok ukur yang telah selesai dibuat koneksi mereka:


 # grep Sockets log | wc -l 224 

Setelah Anda melihat bahwa angkanya 224, biarkan server bekerja sebentar, pantau penggunaan CPU dan memori dengan yang teratas. Anda mungkin melihat sesuatu seperti ini (Server B di sebelah kiri dan Server A di sebelah kanan):


gambar


Atau seperti ini (Server B di sebelah kiri dan Server A di sebelah kanan):



Ini jelas Server B, di mana layanan klien patokan berjalan, berada di bawah beban berat. Server A berada di bawah beban berat juga tetapi tidak selalu. Beberapa kali itu menggigil sementara Server B berjuang dengan sejumlah tugas untuk dikerjakan. Penyandian lalu lintas pada TLS sangat intensif menggunakan CPU.


Mari kita hentikan server (tekan Ctrl-C beberapa kali) dan modifikasi log seperti sebelumnya tetapi sehubungan dengan perubahan jumlah layanan 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 

Anda mungkin ingin menghapus beberapa baris terakhir juga, untuk memperhitungkan hasil cetak dari menghentikan layanan benchmark. Anda sudah punya ide tentang cara melakukan ini. Jadi saya akan memposting hasil untuk berbagai skenario pengujian dan melanjutkan dengan masalah yang saya hadapi.


Hasil untuk semua tes lainnya (4,9 juta dan lebih)



uji beban # 4


uji beban # 5


Tes # 5 seimbang untuk berbagi CPU yang sama


tes # 6


tes # 8


tes # 12


tes # 13


Masalah


Ditandai dalam tabel di atas dengan warna merah.


Menjalankan lebih dari 224 instance benchmark pada Server B terbukti merupakan ide yang buruk, karena server di sisi klien tidak mampu mendistribusikan waktu CPU secara merata di antara proses / utas. 224 instance benchmark pertama yang telah membangun semua koneksi mereka mengambil sebagian besar sumber daya CPU dan sisa instance benchmark tertinggal. Bahkan menggunakan renice -20 tidak banyak membantu (448 instance benchmark, 2 instance LAppS):


448 contoh benchmark


Server B (sisi kiri) berada di bawah beban yang sangat berat dan Server A masih memiliki sumber daya CPU gratis.


Jadi saya menggandakan benchmark.max_connections daripada memulai lebih banyak contoh LAppS yang terpisah.


Masih untuk menjalankan 12,3 juta WebSockets, saya sudah memulai instance LAppS ke-5 (tes 5 dan 6) tanpa menghentikan empat yang sudah berjalan. Dan memainkan peran CFQ dengan menangguhkan dan memulai kembali proses yang diprioritaskan secara manual dengan kill -STOP / -CONT atau / dan mengubah prioritas mereka. Anda dapat menggunakan skrip templat berikut untuk ini:


 while [ 1 ]; do kill -STOP <4 fast-processes pids> sleep 10 kill -CONT <4 fast-processes pids> sleep 5 done 

Selamat datang di 2019 RHEL 7.6! Jujur, saya menggunakan perintah renice pertama kali sejak 2009. Yang terburuk, - saya menggunakannya hampir tidak berhasil kali ini.


Saya punya masalah dengan mesin pencar-pencar NIC. Jadi saya menonaktifkannya untuk beberapa tes, tidak benar-benar menandai acara ini ke dalam tabel.


Saya mengalami gangguan tautan parsial di bawah beban berat dan bug driver NIC, jadi saya harus membuang hasil tes terkait.


Akhir cerita


Jujur, tes berjalan lebih lancar dari yang saya perkirakan.


Saya yakin bahwa saya belum berhasil memuat LAppS di Server A hingga potensi penuhnya (tanpa SSL), karena saya tidak memiliki cukup sumber daya CPU untuk sisi klien. Meskipun dengan TLS 1.3 diaktifkan, LAppS di Server A memanfaatkan hampir semua sumber daya CPU yang tersedia.


Saya masih yakin bahwa LAppS adalah server open source WebSockets paling scalable dan tercepat di luar sana dan modul klien cws WebSockets adalah satu-satunya dari jenisnya, menyediakan kerangka kerja untuk pengujian beban tinggi.


Harap verifikasi hasilnya pada perangkat keras Anda sendiri.


Catatan saran: Jangan pernah menggunakan nginx atau apache sebagai penyeimbang beban atau sebagai proxy-pass untuk WebSockets, atau Anda akan berakhir memotong kinerja berdasarkan urutan besarnya. Mereka tidak membangun untuk WebSockets.

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


All Articles