Привет, меня зовут Рамиль, я middle java backend программист в ЮMoney. В этой статье расскажу о своём эксперименте с нагрузочным тестированием с помощью JMeter, Grafana и Prometheus, а также покажу, как тестировал три сценария: с двумя, 10 и 100 запросами в секунду.

Что такое JMeter

JMeter (Apache JMeter) — это инструмент для тестирования производительности веб-приложений. JMeter позволяет проверить, как приложение будет работать при различных нагрузках, а также ­­помогает создавать тестовые сценарии, имитирующие действия пользователей на веб-сайте или в веб-приложении.

Что можно с JMeter:

  • Создавать разные тестовые сценарии для проверки функциональности и производительности приложений через протоколы HTTP, HTTPS, FTP, JDBC и многие другие.

  • Генерировать нагрузку на серверы, чтобы оценить, могут ли они обрабатывать большое количество запросов.

  • Проводить мониторинг и анализ результатов тестирования с помощью графиков и отчётов из коробки.

JMeter — опенсорс-проект, написанный на Java. Приложение состоит из ядра, которое позволяет запускать тестовые сценарии и формировать по ним отчёт, и интерфейса для наглядной настройки сценариев. При первом запуске JMeter рекомендует не использовать графический интерфейс для запуска больших тестовых сценариев, требующих системных ресурсов.

Первый запуск программы через консоль
Первый запуск программы через консоль
Графический интерфейс JMeter
Графический интерфейс JMeter

План тестирования

Чтобы продемонстрировать возможности системы мониторинга вместе с JMeter, проведём пару тестов для конечной точки создания урока POST /dep_1/work_plan/data пет-проект, фиксируя показатели.

Потестируем три сценария:

1.     Два запроса в секунду на протяжении 10 секунд.

2.     100 запросов в секунду на протяжении 10 секунд.

3.     10 запросов, отправленные одновременно.

Далее покажу процесс и результаты тестирования. Но, прежде чем перейти к созданию тестового сценария, нужно настроить систему мониторинга — в интернете есть много инструкций. Если хотите отдельную статью об этом, напишите в комментариях, поделюсь своими наработками.

Итоговый setup:

Создание тестового сценария

Первое, что нужно добавить, это настройки Thread Group для сценария, они будут определять план запросов в сервис.

Добавление Thread Group
Добавление Thread Group

Перечислю основные настройки Thread Group. Именно они используются для описания тестового сценария.

1. Number of Threads (users) — количество пользователей. Определяет количество виртуальных пользователей (потоков), которые будут одновременно отправлять запросы на сервер. Иными словами, параметр указывает на общее количество потоков, которые будут использоваться в тесте.

2. Ramp-Up Period (in seconds) — период нарастания (в секундах). Устанавливает время, за которое JMeter будет поочередно запускать все потоки, указанные в Number of Threads. Проще говоря, это время, в течение которого нужно исполнить указанное число запросов. Например, если установлено 10 потоков с периодом нарастания пять секунд, то каждый новый поток будет запускаться раз в 0,5 секунды.

3. Loop Count — количество итераций. Определяет, сколько раз каждый поток будет повторять выполнение тестового плана. Если установлено значение «-1», то потоки будут повторять выполнение теста бесконечно.

Укажем параметры для Теста 1 в Thread Group:

·       Number of Threads 20;

·       Ramp-Up Period 10;

·       Loop Count 1.

То есть 20 пользователей один за другим с промежутком в 10/20=0,5 секунды отправят по одному запросу. Тот же тестовый сценарий можно получить иначе: если один пользователь в течение 10 секунд должен будет отправить 20 запросов.

·       Number of Threads 1;

·       Ramp-Up Period 10;

·       Loop Count 20.

Настроим параметры запроса. Для этого в правом меню выберем Add-> Sampler-> HTTP Request, укажем адрес и тело запроса.

Добавление параметров вызова
Добавление параметров вызова

После чего укажем в заголовке Authorization неистёкший JWT-токен, добавив его через правую боковую панель HTTP Header Manager. Тут же добавим заголовок запроса Content-type с указанием, что мы планируем отправлять в теле запроса JSON.

Добавление HTTP Header Manager
Добавление HTTP Header Manager
Меню HTTP Header Manager
Меню HTTP Header Manager

Осталось настроить способ представления результатов теста. Для этого добавляем сущности Listener — View Results Tree и Summary Report. Система готова к первому тесту.

Добавление Listeners
Добавление Listeners

Тест 1. Цель: отправлять два запроса в секунду на протяжении 10 секунд

Запускаем тест из интерфейса приложения, нажав на зелёную кнопку RUN в верхней панели, по завершении сценария наблюдаем результаты. Grafana показывает, что испытуемое веб-приложение отправило информацию о 20 вызовах в период с 23:37:00 до 23:39:00.

Grafana: Пользовательская метрика о кол-ве вызовов
Grafana: Пользовательская метрика о кол-ве вызовов

Расход выделенного процессу ресурса CPU процессора увеличился с 2,5% в простое до 5,5%. Рост обоснован, поскольку приложение простаивало.

Grafana: Метрика CPU_USAGE
Grafana: Метрика CPU_USAGE

В JMeter во вкладке View Result Tree можно посмотреть результаты каждого запроса. Во вкладках Response headers видим, что статус запросов — 200, то есть запросы успешны.

JMeter: View Result Tree
JMeter: View Result Tree

Тест 2. Цель: отправлять 100 запросов в секунду на протяжении 10 секунд

Внесём изменения в тестовый конфиг к Тесту 2. Во-первых, поменяем параметры в Thread Group:

·       Number of Threads — 1000;

·       Ramp-Up Period — 10;

·       Loop Count — 1.

Во-вторых, в Grafana настроим более частый импорт данных из Prometheus, чтобы точнее видеть изменение пользовательской метрики.

Grafana: Query Options.Правим параметр Interval
Grafana: Query Options.Правим параметр Interval
Grafana: Пользовательский график до изменений Query Options
Grafana: Пользовательский график до изменений Query Options
Grafana: Пользовательский график после изменений Query Options
Grafana: Пользовательский график после изменений Query Options

В-третьих, поскольку ожидается появление ответов с ошибками из-за высокой частоты запросов (RPS, Requests per Second), с помощью JMeter начнём размечать запросы с кодом ответа 200 как успешные, а другие — как неудачные. Для этого добавим в конфигурацию теста сущность Response Assertion через меню Add-> Assertions -> Response Assertion, затем попросим в нём искать строку «200» в любом поле ответа. Это позволит быстро увидеть неудачные запросы.

JMeter: Response Assertion
JMeter: Response Assertion
JMeter: Response Assertion (2)
JMeter: Response Assertion (2)

Поскольку нагрузка большая, запускать такой тест надо из консоли. Можно предположить, что лучше запускать большие тесты не из графического интерфейса, потому что иначе ресурсов одного процесса операционной системы может не хватить. Чтобы продемонстрировать разницу, попробуем запустить тест не из консоли, а из графического интерфейса. В первом случае тест заканчивает работу не за 10 секунд, как запланировано, а за 30.

Чтобы исполнить тест из консоли, сначала сохраним сценарий в JMeter. Для этого нажмём на иконку дискеты, откроем сохранённый файл со сценарием тестирования LoadTest1000.jmx как текстовый документ и обнаружим, что этот сценарий тестирования имеет разметку xml. Приведу его часть, открыв его как .xml документ.

Обращаю внимание, что сопоставить теги xml с настройками тестового сценария несложно, поэтому иногда для редактирования сценария легче править LoadTest1000.jmx как текстовый файл, не используя графический интерфейс:

<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group">
        <intProp name="ThreadGroup.num_threads">1000</intProp>
        <intProp name="ThreadGroup.ramp_time">10</intProp>
        <boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller">
          <stringProp name="LoopController.loops">1</stringProp>
          <boolProp name="LoopController.continue_forever">false</boolProp>
        </elementProp>
      </ThreadGroup>
      <hashTree>
        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="AddLesson">
          <stringProp name="HTTPSampler.domain">localhost</stringProp>
          <stringProp name="HTTPSampler.port">8082</stringProp>
          <stringProp name="HTTPSampler.path">/dep_1/work_plan/data</stringProp>

Запустим LoadTest1000.jmx. Для запуска теста на ОС Windows исполним в консоли jmeter.bat из директории JMeter, передав ему файл LoadTest1000.jmx как параметр и указав «.\result-1000\» как директорию для хранения результатов:

«jmeter.bat -n -t LoadTest1.jmx -l log-10.jtl -e -o .\result-1000\ ».

Результаты запуска теста в консоли
Результаты запуска теста в консоли

Время исполнения теста составило около 10 секунд, как и было запланировано.

Результаты теста 2. CPU USAGE
Результаты теста 2. CPU USAGE

Рассмотрим результаты в Grafana:

  • Расход ресурсов процессора значительно возрос, потому что необходимо обрабатывать множество приходящих запросов.

  • Максимальное время обработки запроса возросло вчетверо.

  • Пользовательская метрика зарегистрировала 1000 новых запросов с успешным ответом.

Результаты теста 2. Response Time
Результаты теста 2. Response Time
Результаты теста 2. Пользовательская метрика
Результаты теста 2. Пользовательская метрика

Обратимся к отчёту JMeter по итогам тестирования:

  • В директории result-1000 появился отчёт о тесте в формате html.

  • Указано, что 1000 запросов были обработаны корректно,  и получили статус 200 в ответе, что соответствует информации из пользовательской метрики Grafana.

  • В логе веб-приложения тоже не вижу ошибок, поэтому статистика верная.

  • Также в отчете представлен график с временем выполнения запросов:

50 перцентиль — 23 ms

90 перцентиль — 36 ms

95 перцентиль — 43 ms

99 перцентиль — 84 ms

Результаты теста 2. Общий отчёт JMeter
Результаты теста 2. Общий отчёт JMeter
Результаты теста 2. Отчёт JMeter. Время ответа
Результаты теста 2. Отчёт JMeter. Время ответа

Согласно этим данным, запросы 99% пользователей обработаны менее чем за 85ms, что удовлетворяет требованиям к скорости работы современных систем. Но учитывайте, что клиент (JMeter) и сервер (веб-приложения) находятся на одном компьютере, поэтому не стоит ориентироваться на это время ответа: если клиент и сервер будут на разных машинах, время увеличится.

Для 99-го перцентиля в Отчёте JMeter со временем ответа видим увеличение времени ответа более чем в четыре раза в связи с большой нагрузкой. Это объясняется тем, что у Spring-приложения, основанного на Tomcat-сервере, есть определённое количество потоков (пул-потоков), которые могут обрабатывать запросы. Поток освобождается лишь тогда, когда сервер обработал запрос и вернул http-ответ — в остальное время поток либо работает, либо находится в состоянии WAIT (ожидание). Мы в процессе обработки каждого запроса сохраняем записи в БД, а это блокирующая операция, то есть при сохранении занятия в БД поток переходит в WAIT-состояние и бездействует, пока БД не вернёт результат. Пока часть потоков ожидает ответа от БД, часть запросов ждёт свободный поток на обработку, из-за чего растёт время выполнения запросов.

Чтобы избавиться от этого недостатка, можно применить реактивный подход при проектировании системы. Это позволит избежать блокировки потоков. В контексте этого сценария поток реактивного приложения, отправив запрос в БД, вместо перехода в WAIT-состояние идёт принимать другой запрос или получать ответ от БД и формировать ответ пользователю. Обработкой ответа БД и формированием http-ответа займётся другой свободный поток.

Распределение нагрузки процессора при обработке запроса при использовании пула потоков.
Распределение нагрузки процессора при обработке запроса при использовании пула потоков.

Тест 3. Цель: отправить 10 запросов одновременно

Отправим сразу 10 запросов на добавление уроков, чтобы проверить, как веб-приложение генерирует уникальный идентификатор, сохраняя информацию о занятии. Поменяем параметры в Thread Group:

·       Number of Threads — 10;

·       Ramp-Up Period — 0;

·       Loop Count — 1.

То есть 10 пользователей одновременно отправят один запрос. Запустим сценарий.

Результаты теста 3. JMeter
Результаты теста 3. JMeter

Согласно сценарию на скриншоте, запросы были отправлены с максимальной временной разницей в 0,006 секунды. Все ответы получили статус 200, а для новых уроков были сгенерированы уникальные последовательные идентификаторы. Можно сделать вывод, что в этом сценарии последовательность для генерации идентификаторов записей в БД отработала без нареканий. Результаты Теста 3.CPU Usage доказывают, что показания нагрузки процессора нормальные – 16%.

Результаты теста 3. Пользовательский график
Результаты теста 3. Пользовательский график
Результаты теста 3. CPU Usage
Результаты теста 3. CPU Usage

Вывод

Мы установили, что сочетание инструментов JMeter, Prometheus и Grafana позволяет эффективно проводить нагрузочное тестирование веб-приложений. JMeter даёт возможность генерировать нагрузку на приложение, а Prometheus и Grafana помогают отслеживать расход ресурсов приложения, успешность выполнения запросов и другие важные характеристики. Для корректного измерения нагрузки нужно устанавливать тестируемый сервис (сервер) на отдельной машине в окружении, приближенном к реальному.

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

Комментарии (6)


  1. RealLazyCat
    31.07.2024 12:14
    +4

    Прекрасный образец, КАК ДЕЛАТЬ НЕ НАДО.
    Автор плохо понимает как работает Jmeter, как стартуют потоки, почему нагрузку делать надо не "10 сразу стартанем по 1 разу и все узнаем", а есть типы тестов стабильности, поиска максимума и др., что такое интенсивность запросов и как ей управлять, в частности, в jmeter.
    Оставьте эту работу профессионалам или научитесь, прежде чем писать статьи на хабр и давать вредные советы.


    1. Ramrush Автор
      31.07.2024 12:14
      +1

      Добрый день, консультировался с преподавателем ВУЗа перед публикацией, видимо мы оба не знаем (не ирония).

      В соседней статье https://habr.com/ru/companies/ozontech/articles/662800/ вижу разные типы нагрузочного тестирования, почитаю. Когда дополню статью ремарками, уведомлю.


    1. Iknwpwd
      31.07.2024 12:14

      Читаю выводы и напрашивается вопрос.

      А с чего бы Jmeter не генерить нагрузку, а Графане с Прометеусом не обрабатывать метрики? Они же для этого и созданы, есть документация.

      Статья выглядит как статья на тест-драйв автомобиля, при котором репортёр садится в тачку, говорит "Тут есть руль и педали - погнали" врезается в стену, выходит и говорит "Таким образом мы убедились, что наш автомобиль может ездить" - занавес.


  1. pvzh
    31.07.2024 12:14

    JMeter тут слегка чужеродно смотрится. У Графаны есть же свой родной инструмент k6. Я бы его применил.


    1. hexbat
      31.07.2024 12:14

      Если нет понимания того, что нужно сделать, то инструмент значения не имеет - можно и на k6 сделать не правильно


  1. lHumaNl
    31.07.2024 12:14
    +2

    Как говорится: "это настолько плохо, что даже хорошо". Отдайте уже проведение нагрузочной экспертизы профильным специалистам. Это отдельная область IT со своей теорией, подходами и прикладными навыками. За 3,5 года опыта в НТ уже не раз наблюдал, как на разрабов или на Manual\Auto QA пытаются повесить проведение НТ и каждый раз из этого выходил, как максимум бенчмарк (а бенчмарк != НТ) каких-то компонентов системы (а не полное ее покрытие), а как минимум "я что-то сделал, какие-то данные получил и вроде бы все хорошо". Начинаешь проводить аудит и волосы начинают шевелиться от всего этого.

    Мониторинг бизнес\аппаратных\программных\БД метрик? Да зачем? Мне и так все ясно. Я на CPU посмотрел, он на 100% не утилизируется - значит все ок.

    Управление интенсивностью? Да не, я пользаков как-то наделаю и норм.

    Профиль НТ? Зачем? Я просто постреляю в API'ху сервиса абы как и гляну что будет.

    Требования? Ой, это что-то для аналитиков.

    Анализ поведения системы и поиск "узких" мест? Пусть DevOps'ы разбираются. Они там в конфигах что-то не так указали.

    В общем, если это статья на манер "мне руководство сказало что-то написать и я написал" - то может это и не так страшно. Но вот если она сделана под предлогом того, что "я хочу поделиться с Вами своим опытом" - то это плохой опыт и вредные советы для других. Не просто же так сеньеры НТники в финтехе получают по 300-400к, работая с одной командой разрабов в фуллтайме...