Pull to refresh

LAppS: Пол миллиона 1KB-WebSocket сообщений в секунду с TLS на одном CPU

Reading time8 min
Views4.7K

Для тех кто не в курсе: 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
Tags:
Hubs:
Total votes 21: ↑19 and ↓2+17
Comments8

Articles