Когда вы работаете с приложением с высоким трафиком или просто развертываете новое приложение в производственной среде, вам необходимо определить нагрузку, которую готовы принять ваше приложение и инфраструктура.
Также очень важно хорошо протестировать поведение вашего приложения: действительно, если часть его, например, имеет кеш, важно принять это во внимание и попытаться смоделировать «настоящую» нагрузку.
Для тестирования этой нагрузки на рынке существуют различные инструменты, позволяющие вам создавать сценарии сценариев, в зависимости от языка, который вам нравится, среди них: Locust, если вы хотите разработать свои сценарии на Python, или Vegeta, разработанный на Go, но позволяя довольно просто создавать сценарии.
Обзор Gatling
Я решил использовать инструмент Gatling для нагрузочного тестирования, потому что я считаю его достаточно полным как для различных потребностей сценариев, так и для выходных отчетов.
Gatling - это инструмент, написанный на Scala, для выполнения которого требуется JVM, поэтому для его выполнения вам потребуются инструменты Java.
Это, конечно, продукт с открытым исходным кодом, даже если доступно корпоративное (и, следовательно, платное) предложение на случай, если вам нужно быстро и просто получить отчеты о надежном приложении.
Цель этой статьи - технически продемонстрировать вам преимущества, которые Gatling привносит в управление вашими сценариями.
Фактор запроса в секунду / Коэффициент продолжительности
Когда вы запускаете сценарий, вы наверняка захотите иметь возможность быстро и легко адаптировать количество пользователей, которых вы хотите моделировать, а также продолжительность сценария.
Для этого вы можете просто настроить в своем сценарии две переменные, которые позволят вам вычислить множитель. Например:
val rps_factor = Integer.getInteger("rpsFactor", 1).toInt
val time_factor = Integer.getInteger("timeFactor", 1).toInt
Затем назначьте своим тестовым сценариям нужные значения нагрузки, передав коэффициент множителя:
setUp(
scn.inject(
rampUsers(50 * rps_factor) over(5 * time_factor seconds),
rampUsers(100 * rps_factor) over(10 * time_factor seconds),
)
.protocols(httpConf)
)
Таким образом, вы можете передать значение 1 для тестов компиляции сценария (чтобы тесты не выполнялись слишком долго) и более высокие значения для производственных тестов.
Кормушки
Обычно это одна из первых концепций, которые вам понадобятся для запуска ваших тестов, но мы все равно поговорим об этом: фидеры, возможность предоставить вашему тесту набор значений.
Возьмем простой случай: вы хотите протестировать загрузку API в производственной среде и, чтобы избежать кеширования, вы хотите каждый раз выполнять вызовы для получения другого ресурса. В этом случае просто создайте файл CSV со списком допустимых идентификаторов.
Итак, давайте создадим файл user-files / data / my-identifiers.csv:
myIdentifiers
e9fbd24b-31f8-498f-ba03-7d758d4d2a17
2a012137-ec39-4d37-b2b7-0fc3186f78a0
507a036e-a946-4e82-ae52-305306981694
Затем загрузите этот файл в свой код и дайте его своему сценарию для использования следующим образом:
val myIdentifiersFeeder = csv("my-identifiers.csv").random
val scn = scenario("FTPPubSimulation") .feed(feeder) .exec(http("Call to obtain my ressource") .get("/my-ressource/${myIdentifiers}") .queryParam("id_diffusion", "${metaId}"))
А пока ничего сложного и это уже объяснено в документации. Давайте теперь рассмотрим еще несколько советов, которые, я надеюсь, будут вам полезны, если вы не очень знакомы с Gatling и языком Scala.
Состояние на основе предыдущего запроса
Представьте, что вы делаете запрос к API и хотите сделать второй запрос только в том случае, если значение определено в вашем первом запросе.
Это возможно со следующим синтаксисом:
.exec(
http("GET /api/conditionner/{conditionID}")
.get("/api/conditionner/${conditionID}")
.check(
jsonPath("$..purchaseId").findAll.saveAs("purchaseID")
)
.doIf("${purchaseID.exists()}") {
exec(http("GET /api/purchase/{purchaseID}")
.get("/api/purchase/${purchaseID(0)}")
)
}
)
Здесь второй запрос (/ api / Purchase /…) будет выполнен только в том случае, если первым запросом будет возвращено поле «PurchaseId».
Для получения дополнительной информации о различных условиях, которые вы можете ввести, я приглашаю вас перейти на следующую страницу: https://gatling.io/docs/2.3/general/scenario/#conditional-statements.
Опрос: ожидание асинхронной задачи
Если у вас есть рабочие или просто задачи, которые асинхронно обрабатываются в вашей архитектуре, вы поймете, что вам придется ждать их выполнения.
Gatling также позволяет управлять этим делом с помощью функций tryMax и check:
.tryMax(100) {
pause(1)
.exec(http("GET /api/registration/{registrationID}")
.get("/api/registration/${registrationID(0)}")
.check(
jsonPath("$..purchaseId").findAll.saveAs("purchaseID")
)
)
}
...
Здесь мы будем вызывать HTTP-запрос каждую секунду (потому что мы делаем паузу на одну секунду) до 100 раз, если в ответе не будет найдено поле «PurchaseID», и в этом случае проверка будет отмечена как положительная, и сценарий будет продолжен. до следующей казни.
Случайный запрос
Вы можете распределить нагрузку на два разных типа запросов: например, принять или отклонить регистрацию (случайным образом).
Для этого вы сможете сгенерировать случайное число (0 или 1) и использовать doIfEqualsOrElse для вызова соответствующего запроса:
exec(
http("GET /api/contract/{contractID}/users")
.get("/api/contract/${contractID}/users")
.queryParam("limit", "10")
.check(
jsonPath("$.results[*].id").findAll.saveAs("userID")
)
)
.foreach("${userID}", "elementID") {
exec(
http("PUT /api/user/{elementID}")
.put("/api/user/${elementID}")
.header("Cookie", "_token=" + token)
.header("Content-Type", "application/json")
.body(StringBody("""{"status": "status_has_contract"}""")).asJSON
.check(status.not(404), status.not(500))
)
}
В этом примере запрос PUT будет выполняться для всех пользовательских элементов, возвращенных первым запросом.
Играйте в тесты на нескольких узлах
Если вам нужно создать высокую нагрузку, одной машины может быть недостаточно ни с точки зрения доступных ресурсов (ЦП, ОЗУ), ни с точки зрения пропускной способности.
Gatling позволяет агрегировать данные из нескольких отчетов об испытаниях. Таким образом, идея состоит в том, чтобы воспроизводить отчеты на нескольких машинах одновременно и объединять отчеты для получения только одного.
Конечно, этот факт необходимо учитывать в пользовательских значениях и времени ваших сценариев.
Чтобы автоматизировать это, я потратил время на создание следующего сценария bash, который запускает один из моих сценариев и подключается к разным серверам для их одновременного запуска.
Начнем с подготовки различных переменных нашего скрипта:
#!/bin/bash
# Assuming we use this user for all hosts USER_NAME='root'
# Remote hosts list HOSTS=( 1.1.1.1 2.2.2.2 3.3.3.3 4.4.4.4 )
# Assuming all Gatling installation are in the same path (with write permissions) GATLING_HOME=/opt/gatling/my-project GATLING_SIMULATIONS_DIR=$GATLING_HOME/user-files/simulations GATLING_RUNNER=$GATLING_HOME/bin/gatling.sh
# Simulation class name SIMULATION_NAME='mynamespace.MyTestSimulation'
GATLING_REPORT_DIR=$GATLING_HOME/results/ GATHER_REPORTS_DIR=/gatling/reports/
Перед началом мы также очистим любой предыдущий отчет о тестировании, который может остаться локально и на удаленных серверах:
echo "Cleaning previous runs from localhost" rm -rf reports rm -rf $GATHER_REPORTS_DIR mkdir $GATHER_REPORTS_DIR rm -rf $GATLING_REPORT_DIR
for HOST in "${HOSTS[@]}" do echo "Cleaning previous runs from host: $HOST" ssh -n -f $USER_NAME@$HOST "sh -c 'rm -rf $GATLING_REPORT_DIR'" done
Пришло время обновить сценарии на серверах и запустить их:
for HOST in "${HOSTS[@]}" do echo "Copying simulations to host: $HOST" scp -r $GATLING_SIMULATIONS_DIR/* $USER_NAME@$HOST:$GATLING_SIMULATIONS_DIR done
for HOST in "${HOSTS[@]}" do echo "Running simulation on host: $HOST" ssh -n -f $USER_NAME@$HOST "sh -c 'nohup $GATLING_RUNNER -nr -s $SIMULATION_NAME > /gatling/run.log 2>&1 &'" done
После завершения тестов все, что вам нужно сделать, это получить файлы журнала и объединить их:
for HOST in "${HOSTS[@]}" do echo "Gathering result file from host: $HOST" ssh -n -f $USER_NAME@$HOST "sh -c 'ls -t $GATLING_REPORT_DIR | head -n 1 | xargs -I {} mv ${GATLING_REPORT_DIR}{} ${GATLING_REPORT_DIR}report'" scp $USER_NAME@$HOST:${GATLING_REPORT_DIR}report/simulation.log ${GATHER_REPORTS_DIR}simulation-$HOST.log done
mv $GATHER_REPORTS_DIR $GATLING_REPORT_DIR echo "Aggregating simulations" $GATLING_RUNNER -ro reports
Вот и все, ваш отчет теперь доступен. Вы можете объединить все эти команды, чтобы создать сценарий bash, который автоматически выполняет эти шаги.
Вывод
Gatling - это законченный инструмент, позволяющий создавать сценарии тестирования, адаптированные к вашему приложению. В этом случае выучить язык Scala несложно, и вы можете довольно просто написать свои сценарии.
Кроме того, отчеты, предоставляемые Gatling, довольно приятны для чтения, а его работа с файлом журнала позволяет агрегировать журналы из нескольких источников, поэтому его можно запускать на нескольких узлах для имитации высокой нагрузки.