Loadtest Websocket Server


TLDR;

Apache Jmeter — лучший инструмент для создания сложных и полностью индивидуальных планов тестирования для различных сценариев. В этом посте мы углубимся в него и объясним, как его настроить и как создать наш план тестирования для websocket-сервера с самого начала до получения желаемого результата.
Мы создадим 1000 websocket-соединений за 20 секунд и отправим 20 сообщений на каждое соединение на наш эхо-сервер.

Фон

Всякий раз, когда мы разрабатываем сервер для подключения клиентов, мы не можем предугадать детали производительности, и чрезмерная оптимизация во время разработки не является хорошей практикой. Честно говоря, я никогда не думаю об оптимизации и никогда не пишу код для производительности на этапе разработки (хотя до определенной степени).
Недавно мне пришлось разрабатывать сервер websocket, который представляет собой интерфейс для асинхронной связи между клиентом и бэкендом. После завершения первой версии сервера пришло время провести его бенчмаркинг и посмотреть пропускную способность для соединения и операций чтения/записи.
Существует несколько проектов, предоставляющих механизм для нагрузочного тестирования вашего websocket-сервера, но мой первый поиск и изучение их не дали никаких результатов. Я пришел к выводу, что разработка простого инструмента Go для моего случая использования будет лучше, чем использование этих едва поддерживаемых и неясных инструментов. Есть проект Thor, который не поддерживается с 2017 года, и Apache Jmeter, которые казались слишком сложными и не имели встроенной поддержки нагрузочного тестирования вебсокета.
Однако после дополнительных исследований и перед тем, как начать разработку собственного инструмента, я решил дать Jmeter шанс и посмотреть, смогу ли я достичь того, чего хочу, и понравился ли он мне?
Мне понравилось настолько, что я решил написать эту статью в блоге и познакомить вас с Jmeter и процессом его настройки, если вы еще не использовали его, чтобы мы могли наслаждаться вместе.

Настройка окружения

В этом разделе описывается весь процесс установки Jmeter и подготовки всего для тестирования Websocket до того момента, когда мы можем начать создавать наш план тестирования.

Установите Java

Убедитесь, что у вас установлены Java Development Kit и Runtime Environment.

~ java --version
openjdk 18.0.1.1 2022-04-22
OpenJDK Runtime Environment Homebrew (build 18.0.1.1+0)
Войдите в полноэкранный режим Выйдите из полноэкранного режима

Если вы работаете на Mac, используйте Homebrew для установки последней версии JDK или просто перейдите к следующему шагу, поскольку JDK будет установлен Homebrew в качестве зависимости.

~ brew install openjdk
Вход в полноэкранный режим Выход из полноэкранного режима

Установите Apache Jmeter

Вы можете загрузить бинарные файлы или, если вы работаете на Mac, просто используйте Homebrew для установки:

~ brew install jmeter
Войти в полноэкранный режим Выйти из полноэкранного режима

Убедитесь, что jmeter -v выводит версию, или попробуйте добавить каталог jmeter bin в ваш путь.

Плагин Websocket Sampler

Как уже говорилось, Jmeter не поддерживает нагрузочное тестирование Websocket, но, к счастью, есть несколько сэмплеров, разработанных сообществом для websocket. В этом руководстве мы используем сэмплер, разработанный Питером Дорнбошем, который также предоставляет довольно хорошее руководство в файле readme своего репозитория.
Чтобы установить плагин, мы воспользуемся простым подходом, который заключается в использовании менеджера плагинов Jmeter. Обычно вы можете запустить графический интерфейс Jmeter, выполнив jmeter в терминале, после чего откроется графический интерфейс.
В верхнем меню перейдите к пункту Options->Plugins Manager, который является последним в списке.

Если Plugins Manager недоступен, воспользуйтесь сайтом jmeter plugins для его установки. Скачайте файл .jar и скопируйте его в каталог jmeter /lib/ext. Если вы установили Jmeter с помощью Homebrew, это будет /usr/local/Cellar/jmeter/5.5/libexec/lib/ext.
После открытия менеджера плагинов, перейдите на вкладку доступные плагины, найдите websocket и отметьте галочкой WebSocket Samplers by Peter Doornbosch. Нажмите на Apply Changes and Restart Jmeter. Jmeter будет перезапущен, и все готово для настройки нашего плана тестирования.

Настройка плана тестирования WebSocket

Jmeter удивительно богат функциями, что делает его сложным для настройки. Но как только вы немного освоитесь с ним, все начинает обретать смысл.
В этом разделе мы рассмотрим, как настроить простой план тестирования для нагрузочного тестирования нашего сервера вебсокет.
Каждая сессия имеет Test Plan в боковой панели, где вы можете переименовать ее, нажав на нее.

Группа потоков

Каждый план тестирования может иметь одну или несколько групп потоков. Каждая группа потоков декларирует рабочий процесс для выполнения некоторых шагов по нагрузочному тестированию вашего сервера. Поэтому необходимо создать группу потоков.
Вы можете создать новую группу потоков, щелкнув правой кнопкой мыши на плане тестирования и затем add->Threads (Users)->Thread Group.
Переименуйте группу потоков в то, что имеет смысл в вашем случае. Я назвал ее WebsocketCycle.

Действие, которое будет предпринято после ошибки пробы: Здесь вы можете настроить, что произойдет, когда образец столкнется с ошибкой. Опции довольно самоочевидны и не требуют дополнительных пояснений. Я оставлю значение Continue, которое приведет к переходу к следующему шагу группы потоков.

Количество потоков (пользователей): Каждая нить — это новый пользователь. Другими словами, это означает, что повторять шаги, определенные в данной группе потоков X количество времени в разных потоках и, возможно, одновременно с другими потоками. Я установлю значение 500.

Ramp-up period: позволяет вам настроить количество времени, в течение которого Jmeter должен достичь количества потоков, которое вы определили в предыдущем шаге. Например, если количество потоков равно 1000, а период наращивания равен 10 секундам, то Jmeter потребуется 10 секунд, чтобы запустить все эти 1000 потоков. Другими словами, если потокам требуется более 10 секунд для завершения, на 10-й секунде у вас будет 1000 потоков, выполняющих заданные шаги. Я установил значение 20секунд.

Количество циклов: Каждый поток может повторять определенные вами шаги несколько раз. Это не отдельный поток, и каждый цикл в потоке будет выполняться последовательно друг за другом, а не одновременно. Я установил значение 2.

С помощью этой простой настройки я сообщаю своему плану тестирования, что он должен повторяться 1000 раз (500 * 2), где у меня будет не менее 500 запросов в течение 20 секунд.

Заголовок авторизации

Мой Websocket-сервер не находится в открытом доступе, и ему требуется соответствующая авторизация перед обновлением HTTP-соединения. На этом шаге мы устанавливаем заголовок авторизации для наших запросов на соединение.
Для этого мы добавляем первый шаг нашей группы потоков, щелкнув правой кнопкой мыши на нашей группе потоков (WebsocketCycle) и затем add->Config Element->HTTP Header Management. Я переименовал его в AuthorizationHeaderRandomUser.
Нажмите на кнопку Add в нижней части страницы, чтобы добавить пару ключ/значение заголовка. Я добавляю Authorization в качестве имени и Bearer <my-token>.
Чтобы расширить наши знания о возможностях Jmeter, я решил пройти проверку токена, предоставив полезную нагрузку JSON в виде {"id": "<user_id>"} в качестве токена для тестирования. Поэтому я больше не проверяю токен для извлечения идентификатора пользователя.
Однако на моем сервере есть еще одно ограничение, согласно которому каждый пользователь может иметь не более 3 активных соединений. Но если я использую предыдущий токен с фиксированным номером для ID пользователя, это ограничение сбивается, и не может быть установлено более 3 соединений, поэтому нагрузочное тестирование будет бессмысленным.
Но есть ли способ сгенерировать случайное число для этого авторизационного заголовка? Конечно, есть 🙂
Jmeter имеет сотни вспомогательных функций, которые могут быть использованы для различных целей, но функция, которая нам нужна здесь, называется random, а Jmeter использует синтаксис типа ${__<func_name>(inp1, inp2) для вызова функций. В нашем случае это будет ${__random(1, 99999, USERID)}, которая сгенерирует случайное число от 1 до 99999. Оно печатается в месте использования в дополнение к хранению в переменной USERID.
Таким образом, значение моего заголовка авторизации выглядит следующим образом:

{"id": "${__random(1, 99999, USERID)}"}
Войти в полноэкранный режим Выйти из полноэкранного режима

Вы можете изучить другие функции Jmeter на его официальном сайте.

Websocket-соединение

На следующем этапе я хочу, чтобы мой цикл потоков создал веб-сокетное соединение с моим сервером. Сейчас самое время использовать уже установленный плагин websocket sampler.
Щелкните правой кнопкой мыши на группе потоков (WebsocketCycle) и выберите Add->Sampler->Websocket Open Connection.
При таком подходе я создаю соединение в каждом цикле каждого потока и использую это соединение для всех остальных шагов, которые я хочу определить. Другими словами, план тестирования настраивается на открытие 1000 соединений в своем жизненном цикле.
Выберите схему и хост вашего сервера, а также порт и путь к обработчику websocket, если он не прослушивается в корне вашего хоста. Если вы выбрали wss, не забудьте указать порт TLS (например, 443).

Чтение/запись через веб-сокет

Я включил функцию эхо на своем сервере websocket, которая записывает каждое прочитанное сообщение обратно в соединение. Таким образом, мы можем проверить задержку чтения/записи и потерю сообщений, если таковая имеется.
Щелкните правой кнопкой мыши на WebsocketCycle и нажмите на Add->Sampler->Websocket request-response Sampler.

Если у вас нет эхо-сервера, вы можете просто добавить Write Sampler и не ждать ответа сервера.

Как уже говорилось ранее, в разделе Connection я использую use existing connection, чтобы продолжить работу с соединением, которое мы уже создали на предыдущем шаге.
Мой Websocket-сервер ожидает сообщений в текстовом формате. Если ваш ожидает Binary, вы можете выбрать его в разделе Data. Затем укажите пример текстовой полезной нагрузки, которую вы хотите отправить на сервер.
В моем случае ID пользователя должен быть частью полезной нагрузки. Теперь мы можем использовать ${USERID} всякий раз, когда мы хотим использовать наш случайно сгенерированный ID пользователя на этапе авторизации.

Если нет ответа от сервера в течение времени, предусмотренного разделом Response (read) timeout, этот шаг будет засчитан как ошибка в конечном результате.

Многократное чтение/запись на одно соединение

Я думаю про себя, что мы запустили соединение, отправляем запрос и ожидаем ответа, но не лучше ли отправлять более одного запроса на одно открытое соединение? И мой ответ — да.
Теперь пришло время повеселиться с другой функцией Jmeter, где мы можем создать цикл.
Щелкните правой кнопкой мыши на WebsocketCycle и затем щелкните на Add->Logic Controller->Loop Controller. Установите значение Loop Count равным 20, что означает, что каждый шаг, обернутый этим шагом, будет повторяться 20 раз.
Перетащите ранее созданный шаг (request-read sampler) и бросьте его на Loop Controller, чтобы визуально показать, что он вложен.
Теперь наш процесс записи/чтения происходит 20 раз за соединение вместо одного. Ух ты…!

Закрываем соединение

Поскольку процесс записи/чтения происходит асинхронно, я не закрываю соединение на стороне клиента, поскольку это приведет к закрытию соединения, пока мы ждем ответа сервера.
В моем случае сервер закрывает соединение автоматически после таймаута. И почти во всех случаях на сервере и клиенте существует механизм ping/pong, при котором сервер автоматически закрывает соединение, если не получает pong на свой запрос ping.
Если в вашем случае это не так, я настоятельно рекомендую придумать такой механизм. Однако вы можете либо использовать Websocket Write Sampler, не дожидаясь ответа сервера, либо использовать таймер для отсрочки закрытия соединения.

Отчет и результат

Наш тестовый план настроен и полностью готов к выполнению. Однако, прежде чем запустить его, давайте добавим в наш тестовый план некоторые шаги по созданию отчетов.
Вы можете добавить эти элементы отчетности в группу потоков или в сам план тестирования. Если мы добавим их в группу потоков, они покажут результат для этой единственной группы потоков. Но если у вас несколько групп потоков и вы хотите получить агрегированный результат, вам придется создать эти элементы отчетности в плане тестирования.
В данном примере у нас одна группа потоков, но я все равно создам элементы отчетности в плане тестирования.
Щелкните правой кнопкой мыши на Test Plan и добавьте следующие элементы отчетности:

Инициировать нагрузочное тестирование

Теперь, когда все готово, просто нажмите на зеленую кнопку запуска, чтобы запустить план тестирования. Тестовый план создает 1000 соединений и отправляет 20 сообщений на каждое соединение и ожидает 20 сообщений обратно от сервера.
Ниже приведены скриншоты элементов отчетности, которые мы добавили ранее.

Заключение

Это была лишь небольшая картина того, насколько мощным и гибким является Apache Jmeter. Проверьте все доступные элементы тестирования в документации и расширьте свое путешествие по нагрузочному тестированию на другие серверы, кроме WebSocket. Не забудьте также проверить список функций для создания полностью динамического плана тестирования.

До следующего раза…

Оцените статью
devanswers.ru
Добавить комментарий