Для тех кто не в курсе: LAppS — Lua Application Server, это почти как nginx или apache, но только для WebSocket протокола, вместо HTTP.
HTTP в нём поддерживается только на уровне Upgrade запроса.
LAppS изначально затачивался на высокую нагрузку и вертикальную масштабируемость, и сегодня достиг пика своих возможностей на моём железе (ну почти, можно и дальше оптимизировать, но это будет долгий и упорный труд).
Самое главное, LAppS по производительности WebSocket стека, превзошёл библиотеку uWebSockets, которая позиционируется как самая быстрая WebSocket имплементация.
Заинтересованных прошу под кат.
С последней моей статьи про LAppS прошло уже пару месяцев, заинтересованности та статья не вызвала. Надеюсь эта статья покажется хабровчанам интересней. LAppS за это время проделал довольно сложный путь к версии 0.7.0, оброс функционалом и вырос в плане производительности (что и было обещано ранее).
Одна из появившихся фич: подгружаемый модуль с реализацией клиентской части протокола WebSocket, — cws.
Благодаря этому модулю, я наконец-то смог выжать все возмозжное из моего домашнего компа, и нагрузить LAppS по настоящему.
Ранее тестирование производилось с помощью echo клиента библиотеки websocketpp (подробнее можно посмотреть на github странице проекта), которая мало того, что медленная, так ещё и трудно параллелизуемая. Тесты выполнялись просто: стартовалась пачка клиентов, результаты от каждого клиента собирались с помощью awk и несложная арифметика выдавала результаты производительности. Результаты были такими:
Сервер | Кол-во клиентов | RPS сервера | RPS на клиента | payload (bytes) |
---|---|---|---|---|
LAppS 0.7.0 | 240 | 84997 | 354.154 | 128 |
uWebSockets (latest) | 240 | 74172.7 | 309.053 | 128 |
LAppS 0.7.0 | 240 | 83627.4 | 348.447 | 512 |
uWebSockets (latest) | 240 | 71024.4 | 295.935 | 512 |
LAppS 0.7.0 | 240 | 79270.1 | 330.292 | 1024 |
uWebSockets (latest) | 240 | 66499.8 | 277.083 | 1024 |
LAppS 0.7.0 | 240 | 51621 | 215.087 | 8192 |
uWebSockets (latest) | 240 | 45341.6 | 188.924 | 8192 |
В данном тесте как и в последующих кол-во пакетов на смом деле вдвое выше, т.к. замер производится на on_message и в методе on_message клиента производится отправка нового пакета того-же размера. Т.е. запрос клиента и ответ сервера имеют одинаковый размер, и если считать объём трафика обработанного сервером, то нужно удваивать результат RPS умножать на payload и пренебрегая заголовками можно получить примерный объём трафика в байтах.
Очевидно, что при одновременной работе 240 процессов клиентов, самому LAppS-у (как и uWebSockets) остаётся не так уж и много ресурсов ЦПУ.
Я просмотрел несколько клиентских реализаций для WebSocket под Lua, и к сожалению не нашёл простого и достаточно производительного модуля, с помощью которого, я-бы мог как следует нагрузить LAppS. Поэтому, как обычно сделал свой велосипед.
Модуль имеет довольно простой интерфейс и иммитирует поведение браузерного WebSocket API
Простой пример того как работать с этим модулем (сервис для получения сделок с BitMEX):
bitmex={}
bitmex.__index=bitmex
bitmex.init=function()
end
-
bitmex.run=function()
-- подключаемся к BitMEX
local websocket,errmsg=cws:new(
"wss://www.bitmex.com/realtime",
{
["onopen"]=function(handler)
-- после установления WebSocket соединения отправляем запрос
local result, errstr=cws:send(handler,[[{"op": "subscribe", "args": ["orderBookL2:XBTUSD"]}]],1);
-- Тип отправляемого сообщения 1 (OpCode 1 - ТЕХТ)
if(not result) -- если отравка сообщения была неудачной, - обрабатываем
then
print("Error on websocket send at handler "..handler..": "..errstr);
end
end,
["onmessage"]=function(handler,message,opcode)
print(message) -- выводим на экран сообщения BitMEX по запрошенному топику.
end,
["onerror"]=function(handler, message) -- обрабатываем ошибки соединения
print(message..". Socket FD: "..handler);
end,
["onclose"]=function(handler) -- реагируем на закрытие сокета
print("WebSocket "..handler.." is closed by peer.");
end
});
if(websocket == nil) -- если не удалось подключиться
then
print(errmsg)
else
while not must_stop()
do
cws:eventLoop(); -- poll событий
end
end
end
return bitmex;
Сразу предупреждаю, модуль появился только сегодня и он слабо оттестирован.
Для тестирования я написал простой сервис для LAppS и назвал его так-же незатейливо benchmark.
Этот сервис на старте создаёт 100 соединений к эхо серверу WebSocket (не важно какому), и при удачном соединении отправляет 1кб сообщение. При приёме сообщения от сервера, он отправляет его назад.
Мой домашний комп: Intel® Core(TM) i7-7700 CPU @ 3.60GHz, microcode 0x5e
Память: DIMM DDR4 Synchronous Unbuffered (Unregistered) 2400 MHz (0,4 ns), Kingston KHX2400C15/16G
Всё тестирование проводилось на этом локалхосте.
Конфигурация эхо сервиса в LAppS:
"echo": {
"auto_start": true,
"instances": 2,
"internal": false,
"max_inbound_message_size": 16777216,
"preload": null,
"protocol": "raw",
"request_target": "/echo"
}
Параметр instances требует от LAppS старта двух параллельных эхо сервисов.
Конигурация бенчмарк-сервиса (клиента):
"benchmark" : {
"auto_start" : true,
"instances": 4,
"internal": true,
"preload" : [ "cws", "time" ]
}
T.e. при старте создаётся 4 экземпляра сервиса-бенчмарка
Результат с включенным TLS
Сервер | Кол-во клиентов | RPS сервера | RPS на клиента | payload (bytes) |
---|---|---|---|---|
LAppS 0.7.0-Upstream | 400 | 257828 | 644.57 | 1024 |
nginx &lua-resty-websocket 4 workers | 400 | 33788 | 84.47 | 1024 |
websocketpp | 400 | 9789.52 | 24.47 | 1024 |
uWebSockets оттестировать пока не удалось, — TLS handshake ругается на SSLv3 (мой клиент использует TLSv1.2 и в используемом мной libreSSL SSLv3 вырезан).
Результат без TLS
Сервер | Кол-во клиентов | RPS сервера | RPS на клиента | payload (bytes) |
---|---|---|---|---|
LAppS 0.7.0-upstream | 400 | 439700 | 1099.25 | 1024 |
uWebSockets-upstream | 400 | 247549 | 618.87 | 1024 |
Почему в заголовке "полмилиона" сообщений, а в тесте 257828? Потому-что сообщений вдвое больше (как и было разъяснено выше).
uWebsockets, показывает незавидные результаты в этом тесте, только потому, что он работает на 1-м ядре, многопоточная версия uWebSockets из репозитория проекта, на самом деле не работает и при включении TLS имеет data-race в OpenSSL стэке.
Если представить, что uWebSockets прекрасно работает на 2-х ядрах (как 2 эхо-сервиса LAppS), то ему условно можно будет зачесть 495098 RPS (просто удвоив результат из таблицы).
Но нужно учитывать, что эхо сервер (uWebSockets) ничего с полученными данными не делает, а сразу отправляет назад. LAppS-же передаёт данные в Lua стек, соответствующему сервису.
Что ещё нового появилось в LAppS
- Модуль аутентификации ч-з PAM: pam_auth
- Модуль очередей сообщений: mqr — для обмена сообщениями между сервисами в рамках одного сервера LAppS (для мултисерверного обмена нужно использовать, что-то уже существующее, например: RabbitMQ, mosquitto, etc)
- ACL сетевых соединений
Со всем этим можно ознакомиться на wiki странице проекта.
Ну и на закуску, для ценителей, чем же собственно LAppS занимается во время этого тестирования.
Без TLS
Очевидный лидер iptables.
4.98% lapps [ip_tables] [k] ipt_do_table
Возврат из системных вызовов
3.80% lapps [kernel.vmlinux] [.] syscall_return_via_sysret
Это передача данных между сервером и Lua сервисами
3.52% lapps libluajit-5.1.so.2.0.5 [.] lj_str_new
Парсинг потока данных WebSocket сервером
1.96% lapps lapps [.] WSStreamProcessing::WSStreamServerParser::parse
Обращения к системным вызовам
1.88% lapps [kernel.vmlinux] [k] copy_user_enhanced_fast_string
1.81% lapps [kernel.vmlinux] [k] __fget
1.61% lapps [kernel.vmlinux] [k] tcp_ack
1.49% lapps [kernel.vmlinux] [k] _raw_spin_lock_irqsave
1.48% lapps [kernel.vmlinux] [k] sys_epoll_ctl
1.45% lapps [xt_tcpudp] [k] tcp_mt
Воркеры LAppS
1.35% lapps lapps [.] LAppS::IOWorker<false, true>::execute
Клиент бенчмарка
1.28% lapps lapps [.] cws_eventloop
...
1.27% lapps [nf_conntrack] [k] __nf_conntrack_find_get.isra.11
1.14% lapps [kernel.vmlinux] [k] __inet_lookup_established
1.14% lapps libluajit-5.1.so.2.0.5 [.] lj_BC_TGETS
Эхо серверы взгляд со стороны C++
1.01% lapps lapps [.] LAppS::Application<false, true, (abstract::Application::Protocol)0>::execute
...
0.98% lapps [kernel.vmlinux] [k] ep_send_events_proc
0.98% lapps [kernel.vmlinux] [k] tcp_recvmsg
0.96% lapps libc-2.26.so [.] __memmove_avx_unaligned_erms
0.93% lapps libc-2.26.so [.] malloc
0.92% lapps [kernel.vmlinux] [k] tcp_transmit_skb
0.88% lapps [kernel.vmlinux] [k] sock_poll
0.85% lapps [nf_conntrack] [k] nf_conntrack_in
0.83% lapps [nf_conntrack] [k] tcp_packet
0.79% lapps [kernel.vmlinux] [k] do_syscall_64
0.78% lapps [kernel.vmlinux] [k] ___slab_alloc
0.78% lapps [kernel.vmlinux] [k] _raw_spin_lock_bh
0.73% lapps libc-2.26.so [.] _int_free
0.69% lapps [kernel.vmlinux] [k] __slab_free
0.66% lapps libcryptopp.so.5.6.5 [.] CryptoPP::Rijndael::Base::UncheckedSetKey
0.66% lapps [kernel.vmlinux] [k] tcp_write_xmit
0.65% lapps [kernel.vmlinux] [k] sock_def_readable
0.65% lapps [kernel.vmlinux] [k] tcp_sendmsg_locked
0.64% lapps libc-2.26.so [.] vfprintf
Собственно отправка сообщений клиентом (сервисом - bemchmark)
0.64% lapps lapps [.] LAppS::ClientWebSocket::send
...
0.64% lapps [kernel.vmlinux] [k] tcp_v4_rcv
0.63% lapps [kernel.vmlinux] [k] __alloc_skb
0.61% lapps lapps [.] std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release
0.61% lapps [kernel.vmlinux] [k] _raw_spin_lock
0.60% lapps libc-2.26.so [.] __memset_avx2_unaligned_erms
0.60% lapps [kernel.vmlinux] [k] kmem_cache_alloc_node
0.59% lapps libluajit-5.1.so.2.0.5 [.] lj_tab_get
0.59% lapps [kernel.vmlinux] [k] __local_bh_enable_ip
0.58% lapps [kernel.vmlinux] [k] __dev_queue_xmit
0.57% lapps [kernel.vmlinux] [k] nf_hook_slow
0.55% lapps [kernel.vmlinux] [k] ep_poll_callback
0.55% lapps [kernel.vmlinux] [k] skb_release_data
0.54% lapps [kernel.vmlinux] [k] native_queued_spin_lock_slowpath
0.54% lapps libc-2.26.so [.] cfree@GLIBC_2.2.5
0.53% lapps [kernel.vmlinux] [k] ip_finish_output2
0.49% lapps libluajit-5.1.so.2.0.5 [.] lj_BC_RET
0.49% lapps libc-2.26.so [.] __strlen_avx2
0.48% lapps [kernel.vmlinux] [k] _raw_spin_unlock_irqrestore
С найдём 10 отличий при работе с TLS
3.73% lapps [kernel.vmlinux] [k] syscall_return_via_sysret
3.49% lapps libcrypto.so.43.0.1 [.] gcm_ghash_clmul
3.42% lapps libcrypto.so.43.0.1 [.] aesni_ctr32_encrypt_blocks
2.74% lapps [ip_tables] [k] ipt_do_table
2.17% lapps libluajit-5.1.so.2.0.5 [.] lj_str_new
1.41% lapps libpthread-2.26.so [.] __pthread_mutex_lock
1.34% lapps libssl.so.45.0.1 [.] tls1_enc
1.32% lapps [kernel.vmlinux] [k] __fget
1.16% lapps libcrypto.so.43.0.1 [.] getrn
1.06% lapps libc-2.26.so [.] __memmove_avx_unaligned_erms
1.06% lapps lapps [.] WSStreamProcessing::WSStreamServerParser::parse
1.05% lapps [kernel.vmlinux] [k] tcp_ack
1.02% lapps [kernel.vmlinux] [k] copy_user_enhanced_fast_string
1.02% lapps [nf_conntrack] [k] __nf_conntrack_find_get.isra.11
0.98% lapps lapps [.] cws_eventloop
0.98% lapps [kernel.vmlinux] [k] native_queued_spin_lock_slowpath
0.93% lapps libcrypto.so.43.0.1 [.] aead_aes_gcm_open
0.92% lapps lapps [.] LAppS::IOWorker<true, true>::execute
0.91% lapps [kernel.vmlinux] [k] tcp_recvmsg
0.89% lapps [kernel.vmlinux] [k] sys_epoll_ctl
0.88% lapps libcrypto.so.43.0.1 [.] aead_aes_gcm_seal
0.84% lapps [kernel.vmlinux] [k] do_syscall_64
0.82% lapps [kernel.vmlinux] [k] __inet_lookup_established
0.82% lapps [kernel.vmlinux] [k] tcp_transmit_skb
0.79% lapps libpthread-2.26.so [.] __pthread_mutex_unlock_usercnt
0.77% lapps [kernel.vmlinux] [k] _raw_spin_lock_irqsave
0.76% lapps [xt_tcpudp] [k] tcp_mt
0.71% lapps libcrypto.so.43.0.1 [.] aesni_encrypt
0.70% lapps [kernel.vmlinux] [k] _raw_spin_lock
0.67% lapps [kernel.vmlinux] [k] ep_send_events_proc
0.66% lapps libcrypto.so.43.0.1 [.] ERR_clear_error
0.63% lapps [kernel.vmlinux] [k] sock_def_readable
0.62% lapps lapps [.] LAppS::Application<true, true, (abstract::Application::Protocol)0>::execute
0.61% lapps libc-2.26.so [.] malloc
0.61% lapps [nf_conntrack] [k] nf_conntrack_in
0.58% lapps libssl.so.45.0.1 [.] ssl3_read_bytes
0.58% lapps libluajit-5.1.so.2.0.5 [.] lj_BC_TGETS
0.57% lapps [kernel.vmlinux] [k] tcp_write_xmit
0.56% lapps libssl.so.45.0.1 [.] do_ssl3_write
0.55% lapps [kernel.vmlinux] [k] __netif_receive_skb_core
0.54% lapps [kernel.vmlinux] [k] ___slab_alloc
0.54% lapps libc-2.26.so [.] __memset_avx2_unaligned_erms
0.51% lapps [kernel.vmlinux] [k] _raw_spin_lock_bh
0.51% lapps libcrypto.so.43.0.1 [.] gcm_gmult_clmul
0.51% lapps [kernel.vmlinux] [k] sock_poll
0.48% lapps [nf_conntrack] [k] tcp_packet
0.48% lapps libc-2.26.so [.] cfree@GLIBC_2.2.5
0.48% lapps libssl.so.45.0.1 [.] SSL_read
0.46% lapps [kernel.vmlinux] [k] copy_user_generic_unrolled
0.45% lapps [kernel.vmlinux] [k] tcp_sendmsg_locked
0.45% lapps lapps [.] LAppS::ClientWebSocket::send
0.44% lapps libc-2.26.so [.] _int_free
0.44% lapps libssl.so.45.0.1 [.] ssl3_read_internal
0.43% lapps [kernel.vmlinux] [k] futex_wake
0.42% lapps libluajit-5.1.so.2.0.5 [.] lj_tab_get
0.42% lapps libc-2.26.so [.] vfprintf
0.41% lapps [kernel.vmlinux] [k] tcp_v4_rcv
Комментарии (8)
trapwalker
27.08.2018 18:56Вы бы на этом своём сервере игрушку какую-нибудь многопользовательскую простейшую замутили бы, вот тогда народ оценил бы. Даже если простейшая какая-то вроде змейки, но не падающая от огромного количества сообщений в реальном времени.
thatsme Автор
27.08.2018 20:56Я честно не могу представить, что «змейка» должна делать в онлайне. Давайте так: вы делаете фронтенд, и говорите мне что нужно от бэкенда, думаю осилим вдвоём. Пишите в личку.
trapwalker
28.08.2018 10:18Змеек может быть много и каждой управляет отдельный человек. Что-то вроде slither.io. Такие задачи действительно часто упираются в логистику сообщений. Если делать в лоб, то там квадратичная зависимость.
На счет скооперироваться=) так я ж тоже бэкендщик=)
tgz
28.08.2018 08:58На одном CPU — это сколько потоков?
thatsme Автор
28.08.2018 12:40Вообще на этом CPU 4-e ядра, если s Hyperthreading то 8.
Параллельно работающих IOWorkers в тестах — 4
Параллельных инстансов echo сервиса: 2
Параллельныйх инстансов нагрузки: 4
Load при 5-мин исполнении: 3.97
Использование CPU LAppS-ом (это и клиентом и сервером) 6.37 CPU
Involuntary context switches: 0
Voluntary context switches: 14
Из железа выжато всё.
Кстати на ноуте пробовал запустить (AMD A8) — в 10-15 раз медленнее, но там и частота вдвое ниже и память тормозная (Хотя 4-е честных ядра).
Moxa
интересная штука, а можно подробную инструкцию как запускать бенчмарки? просто
./benchmark/runBenchmark.sh
у меня не работаетthatsme Автор
Это старые бенчмарки, они с websocketpp сделаны. По идее ./runBenchmark.sh должен скомпилить из исходников 2 файла ./benchmark_tls и benchmark_nontls. Они по сути и запускаются.
Что-бы runBenchmark.sh работал, вы должны находиться в том-же каталоге, в котором расположены 3 файла: runBenchmark.sh, echo_client_tls.cpp и echo_client.cpp
При запуске нужно указывать кол-во процессов, размер payload и uri сервера WebSocket.
Запускать так:
./runBenchmark.sh 240 1024 wss://localhost:5083/target
Естественно вместо wss:// мозно использовать ws://, вместо localhost можно подставить IP конкретного сервера WebSocket с запущенным echo-сервисом. /target требуется только если сервер знает что делать с таргетом.
— Если хотите использовать LAppS для тестов, то нужно поднять эхо сервис:
В файле /opt/lapps/etc/conf/lapps.json добавить ехо сервис:
Потом запустить lapps в режиме демона: /opt/lapps/bin/lapps -d
Потом перейти в директорию где лежит ./runBenchmark.sh и запустить его. В URI обязательно указывать таргет /echo — т.к сервис поднят именно для этого таргета (см конфиг сервиса выше)
Новый бенчмарк работает как сервис LAppS, т.е. его нужно задеплоить. И да, это только после сборки из гита.
P.S: демон можно остановить с помощью kill -15 pid-LappS. Тесты прерывать по Ctrl-C бесполезно, — процессы в фоне будут крутиться. После стопа LappS-a клиентские процессы отвалятся.