Пользователь
0,0
рейтинг
19 февраля 2010 в 06:38

Разработка → Простой нагрузочный тест с Apache JMeter

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

Почему бы не избежать этих неприятностей, прогнав нагрузочный тест?

Наверное, кого-то останавливает неверное представление о том, что нагрузочное тестирование — это очень сложное дело, требующее специальных знаний. Однако не боги горшки обжигают. Если выбор — тестировать не слишком профессионально, или не тестировать вовсе, я бы выбрал первое. Тем более, что организовать примитивный тест производительности очень даже просто. Можно воспользоваться онлайн-средствами (см., например, Нагрузочное тестирование по-быстренькому), а можно замутить все своими руками, это ненамного сложнее.

Под катом рассказываю, как с нуля организовать незамысловатый нагрузочный тест сайта при помощи Apache JMeter.

Сразу хочу предупредить, что описанный подход (Log Replay) хорошо работает именно для сайтов, и не годится для веб-приложений, активно использующих POST, а также, по своей простоте, игнорирует существование cookie-based сессий. Кроме того, нежелательно тестировать проект, развернутый по адресу 127.0.0.1, результаты довольно сильно искажаюся из-за того, что JMeter и сайт тормозят друг друга (с другой стороны, плохо, когда сервер далеко — мешают задержки).

Нам понадобится:
  1. JMeter
  2. Установленная Жаба, в наше время она водится почти на любой машине
  3. Access log нашего сайта. Если access log у нас пустой, нам ничто не мешает его слегка пополнить, взяв в руки браузер и полазив по сайту. Можно пройти сайт попавшимся под руку краулером, например HTTrack или Xenu. Если веб-сервер — IIS, то предварительно нужно переключить формат лога в NCSA, понимаемый парсером JMeter-а. Брать лог из-под работающего сервера (когда он туда пишет) не стоит, лучше взять уже закрытый, скажем, вчерашний, или приостановить веб-сервер на время выемки лога. Лог стоит посмотреть текстовым редактором на предмет корректности.

Есть еще неплохой способ генерации файла, который для JMeter'а сойдет за лог, причем без захода в файловую систему сервера. Добываем откуда-нибудь список URL сайта. Приемлемый список делает Xenu в отчете о сканировании. Вставляем этот список в текстовый файл. Получится что-то вроде
http://test.local/index.php
http://test.local/news/event-12.php
...

Делаем глобальный реплейс «http://test.local» на «"GET » (с кавычкой и пробелом), получаем
"GET /index.php
"GET /news/event-12.php
...

Этот формат парсер хорошо кушает, принимая за чистую монету (закрывать кавычки в конце строки не надо).

Итак, скачали JMeter (http://jakarta.apache.org/site/downloads/downloads_jmeter.cgi, разворачиваем архив, идем в директорию bin и запускаем jmeter.bat (делаю пример под Виндой). После небольшой паузы стартует GUI традиционного жабьего вида.
Слева наблюдаем дерево из 2 узлов: TestPlan и Workbench (про второй сразу забываем, он нам не понадобится). На Test Plan кликаем правым кликом и говорим Add->Thread Group (в интерфейсе можно увидеть много фишек разной степени полезности, но мы сейчас не отвлекаемся, а кратчайшим путем идем к нашему тесту, потом, если захотим — будем изучать обширные возможности JMeter подробнее).

image

Группа потоков добавилась:

thread group

Менять мы тут пока ничего не будем. Цифры все стоят по 1, что хорошо. Это один виртуальный пользователь, поторый один раз выполнит сценарий (в случае используемого нами Access Log Sampler'а — выполнит один запрос, соответствующий первой строчке лога). А нам для отладки теста больше и не надо.
Переименовывать Test Plan и Thread Group тоже не будем, эти названия у нас в рамках теста уникальны.
Правым кликом на Thread Group добавляем Access Log Sampler (Thread Group->Add->Sampler->Access Log Sampler)

add access log sampler

Вбиваем адрес сервера и локальный путь к аксесс-логу (мы его утащили с сервера и положили к себе на диск):

access log sampler

Теперь добавляем в тест средства отображения:
  • Thread Group->Add->Listener->View Results in Table
  • Thread Group->Add->Listener->Graph Results
  • Thread Group->Add->Listener->Aggregate Report
Во View Results in Table надо заполнить поле Filename (если не указывать путь, лог-файл образуется рядом с jmeter.bat). Создавать лог необходимо для отладки, так как JMeter в своем GUI толковой информации об ошибках не выводит.

Тест-план готов, переходим к его тестированию :) и отладке (ничего-ничего, он может и с первого раза заработать).
File->Save, и так каждый раз после внесения изменений в тест-план. Это важно, JMeter другой раз виснет, и тест приходится восстанавливать по памяти.
Run->Clear All (на первый раз можно не делать, но потом все равно понадобится).
Run->Start.
И идем смотреть во View Results in Table. Если нам повезло, там будет одна строчка, с зеленой галочкой в колонке Status.

success

Если что-то пошло не так, в статусе будет ошибка:

image

Если такое дело, идем читать наш TestPlan.log. Как правило, по сообщениям в нем можно догадаться, что именно сломалось. Например, если тестируемый сервер не отвечает, в логе оказывается такая ругань: rc="Non HTTP response code: java.net.ConnectException" rm="Non HTTP response message: Connection refused: connect". Такой текст rc="Non HTTP response code: java.net.ProtocolException" rm="Non HTTP response message: Invalid HTTP method: null" скорее всего свидетельствует о том, что строка акцесс-лога неправильно распарсилась.
Положим, разобрались, или все сразу прошло чисто. Идем в свойства Thread Group и ставим Loop Count: Forever

Forever

Запускаем (File->Save, Run->Clear All, Run->Start). Идем смотреть во View Results in Table. Должно получиться как-то так:

View Results in Table

В последней строчке ошибка, это JMeter испытывает расстройство оттого, что файл закончился (видно, привык работать с бесконечными файлами). К сожалению, по окончании файла сценарий останавливается, игнорируя настройку Action to be taken after a Sampler error = Continue (мне это кажется багом, а разработчикам наверняка фичей). Чтобы это не исказило результаты теста, лучше брать достаточно длинные аксесс-логи. Длинный файл несложно организовать из короткого с помощью copy в командной строке или Ctrl+C, Ctrl+V в текстовом редакторе. Для наших опытов больше 1000 строк в логе вряд ли понадобится.

Еще, прежде чем начать тест, добавим в начало сценария случайную задержку (Uniform Random Timer) 0-1000 миллисекунд, она обычно помогает несколько сгладить графики. Сценарий в результате работает так: ждет случайное количество миллисекунд, читает строку из лога, делает HTTP запрос, передает результаты листенерам, снова ждет, читает следующую строчку, и так далее.

Делаем первый, пристрелочный, тест. В свойствах группы потоков поставим: Number of Threads (users): 100, Ramp-Up Period (in seconds): 100. Мы собираемся натравить на сайт 100 виртуальных юзеров, вводя их в бой по одному в течении 100 секунд, то есть по юзеру в секунду. Цифры 100 и 100 я взял откуда-то с потолка, но надо же с чего-то начать.

Еще раз напомним себе, что мы имеем хорошие шансы притормозить или даже завалить сайт (что может быть нехорошо, если речь идет об уже работающем проекте). ОК, будучи в здравом уме и трезвой памяти, осознавая ответственность за свои действия, начинаем.
File->Save, Run->Clear All, Run->Start и идем смотреть Graph Results. Видим, скажем, такую картинку:

100-100

В правом верхнем углу можно наблюдать текущее количество виртуальных пользователей.
О чем говорит нам этот график? Среднее время отклика (Average) растет, а скорость обработки (Throughput) не меняется. Это значит, что где-то на сервере операции становятся в очередь, и производительности не хватает, чтобы обслужить все запросы. Зайдя браузером на сайт, убедимся, что он еле ворочается или вообще не респондит. Зачем зря мучить несчастного? Run->Stop. Ну вот, сайт снова ожил. Неудачная идея во время такого теста — отвлечься ненадолго и, вернувшись через несколько часов (как это бывает), обнаружить, что сайт полдня лежал.

В качестве содержательного результата мы получили одно число — максимальное значение Throughput (183 запроса в минуту). Можно считать его пределом производительности. Для начала этого числа может быть достаточно, например, уже ясно, что 100 000 хостов в сутки наш сайт не потянет.

Внимательно посмотрев на график времени отклика, можно увидеть полочку в его начале. В это время нагрузка росла, а реакция сервера не менялась, то есть ему было хорошо. Попробуем более подробно изучить этот диапазон нагрузок. Уменьшив Number of Threads и увеличив Ramp-Up Period, получаем такую картинку:

gentle test

Видим, что сайту поплохело после 3 виртуальных юзеров и 150 запросов в минуту.

Для уверенности теперь есть смысл провести тест со статической нагрузкой. Ставим Number of Threads = 3, Ramp-Up Period= 0 (вводим потоки сразу) и смотрим, что получилось. Вроде все нормально, сайт реагирует живенько. Если хотим, снимаем несколько таких точек и на бумажке строим график. Эти цифры сильно достовернее, чем наблюдения по графику с динамической нагрузкой.

Заглянем теперь в Aggregate Report. Там для нас приготовлена статистика по URL-ам
(лучше всего смотреть после теста с большой, но не чрезмерной статической нагрузкой). Нас в первую очередь интересует колонка Average, среднее время отклика. Часто оказывется что есть несколько тяжелых страниц, которые в первую очередь и создают нагрузку на систему, и если их оттюнить, общая производительность многократно увеличивается (лучше всего начинать оптимизацию со страниц, которые по статистике вызываются часто, а отрабатывают долго). Справедливости ради надо отметить, что не всегда самые долгоиграющие страницы дают наибольший вклад в нагрузку, но чаще это так.

Пара слов об интерпретации полученных чисел: 3 виртуальных юзера, 150 запросов в минуту. Как эти величины соотносятся с реальными пользователями и, скажем, запросами страниц в сутки? Практически никак, мы не ставили себе цель смоделировать реального юзера. То, что мы имеем — относительная величина, на которую можно ориентироваться в процессе тюнинга. В данном случае 3 юзера получены при тестировании по списку урлов сайта, и «лог» не содержит картинок, css и прочих ресурсов. Так что 150 per minute как раз соответствуют настоящим запросам страниц в минуту. Если мы использовали реальный лог, то можно взять Aggregate Report, экспортировать его в csv (внизу есть кнопочка Save Table Data), повыкидывать из него все обращения к ресурсам, посчитать оставшиеся хиты и разделить на продолжительность теста.

В заключение хочется предупредить об одном недостатке описанного способа. Поскольку все виртуальные пользователи выполняют запросы в одном и том же порядке, эффективность кэширования на всех уровнях будет очень высокой. В реальности эффективность будет меньше, и это вносит в результаты наших исследований с трудом оцениваемую погрешность (кстати, надо будет написать сэмплер, который дергает строчки из лога в случайном порядке… если руки дойдут).

Но зато такой тест делается легко и быстро и обладает хорошей производительностью, так что для начала, имхо, в самый раз.
@msyu
карма
57,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

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

  • 0
    В качестве ознакомительной статьи на конкретном примере очень хорошо написано, но неплохо бы еще парочку возможностей для затравки упомянуть. К примеру более изощренные сценарии можно быстро набросать записав свои действия как макрос, а снизить нагрузку на тестирующую машину(скорей всего это далеко не серверные мощности, да и в зависимости от сложности сценариев ~1000 запросов могут быть уже трудногенерируемы) можно установкой копий на дополнительные компьютеры(один будет выступать в роли сервера) — и все это опять же средствами jmeter.
    • 0
      Наверно напишу еще о других возможностях, как руки дойдут. А тут хотелось быть лаконичным, а то никто не поверит, что вся эта кухня не слишком сложная (все-таки, имхо, получилось длиннее, чем нужно).

      Спасибо за коммент, думал уже, никто не выскажется…
  • 0
    Нормально, в качестве введения в сабж. Теперь можно более развернуто написать о создании сценариев с помощью HTTP Proxy, мониторинге загрузки памяти и процессора веб сервером с помощью Monitor Results, получении параметров из респонза и передача в последующий реквест и прочих плюшках для более сложных сценариев. Кстати, для иммитации более реальных пользователей стоит между стэпами сценария ставить, например, пятисекундные таймеры, иначе возникает лавинообразная нагрузка на сервер, что очень искажает результат и впринципе не соответствует реальности.
  • 0
    Спасибо, это лучший вводный материал по теме в рунете)
  • 0
    Присоединяюсь, к комментариям. Не занудно, и вполне можно развить тему.
  • 0
    Не пойму одного. Если я задаю 100 пользователей в 100 секунд, то счетчик в правом верхнем углу у меня никогда не показывает больше 1, т.к. 1 юзер вводится, тут же выводится, и тут же водится следующий, поэтому всегда показывает 0-1/100, а как у вас на скрине получилось 55/100?
    Второй вопрос, график у меня получается совсем кривой, он доходит до конца при прорисовке а потом начинает затирать начальные данные. Как у вас так получилось, что так много инфы поместилось на таком маленьком участке графика (масштаб?)
    • 0
      Также, не понятно следующее: «прежде чем начать тест, добавим в начало сценария случайную задержку (Uniform Random Timer) 0-1000 миллисекунд»
      Где это добавлять?
      • 0
        Правой кнопкой на Thread Group, Add->Timer->Uniform Random Timer
        Если добавился не в начало — перетащить в начало, хотя большой разницы нет.
        • 0
          Спасибо, вроде похоже стало на нормальный график. Только не очень понятно, график пропускной способности растет растет, а потом резко начинает идти в нуле)
          • 0
            Чаще всего такое бывает, если тестируемый сервер лег и начал на все запросы отвечать ошибкой 500.
            ЗЫ Вы бы картинки публиковали — было бы яснее. Кстати, есть habrastorage.org, чтобы не через дропбокс (а то сами же удалите через месяц, а может кому будет интересно)
    • 0
      Это значит, сценарий очень короткий. Скажем, длиной в один реквест. А если за 100 секунд первый пользователь не успеет закончить сценарий, то и будет 100 одновременных. Еще такая ситуация наблюдается, если сценарий каждый раз завершается аварийно (например, не найден файл, из которого читать)
      • 0
        Спасибо за инфу. Я делал как вы написали — собрал урлы (30 штук) и вбил их в JMeter. Какой тут может быть длинный сценарий, если сценарий — это просто заход на страницу (как у вас в примере и описано)?
        А что насчет графика — почему он у меня такой кривой?
        dl.dropbox.com/u/167393/jmeter.png
  • 0
    Если вставить случайный таймер, то и график сгладится, и сценарий не будет успевать закончиться. На картинке таймера нету.
    Еще можно добавить View Results Tree, там видно ошибки. См. тж. habrahabr.ru/post/88714/
  • 0
    " Менять мы тут пока ничего не будем. Цифры все стоят по 1, что хорошо. Это один виртуальный пользователь, поторый один раз выполнит сценарий" Автор, опечатка:)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.