Что такое Gatling?
Gatling — это высокопроизводительный open-source инструмент нагрузочного тестирования, предназначенный для непрерывной интеграции и рабочих процессов, ориентированных на разработчиков. Построенный на Scala с асинхронной неблокирующей архитектурой (Akka + Netty), Gatling может симулировать тысячи конкурентных пользователей со значительно меньшим потреблением памяти, чем инструменты на основе потоков, такие как JMeter.
Gatling создаёт детальные интерактивные HTML-отчёты «из коробки» — часто считающиеся лучшими отчётами о производительности в индустрии. Эти отчёты включают распределение времён ответа, перцентили, количество запросов во времени и анализ ошибок.
Когда выбирать Gatling
У каждого инструмента нагрузочного тестирования есть свои сильные стороны. Вот сравнение Gatling.
| Характеристика | Gatling | JMeter | k6 |
|---|---|---|---|
| Язык | DSL Scala/Java/Kotlin | GUI + XML | JavaScript |
| Архитектура | Async неблокирующая | Поток на пользователя | Горутины Go |
| Отчёты | Красивый HTML встроенный | Базовые, нужны плагины | Терминал + JSON/CSV |
| CI/CD | Плагин Maven/Gradle/sbt | Режим CLI | CLI нативный |
| Протокол | HTTP, WebSocket, JMS, MQTT | HTTP, JDBC, FTP, LDAP, JMS | HTTP, WebSocket, gRPC |
| Потребление ресурсов | Очень низкое | Высокое | Низкое |
| Кривая обучения | Крутая (Scala) | Средняя | Лёгкая (JS) |
Выбирайте Gatling, когда: Нужны отличные отчёты, команда использует JVM-языки, нужно низкое потребление ресурсов при масштабировании или нативная интеграция с Maven/Gradle для CI/CD.
Структура симуляции Gatling
Симуляция Gatling — это класс Scala, расширяющий Simulation. Он содержит три основные части:
- Конфигурация протокола — настройки HTTP, базовый URL, заголовки
- Определение сценария — путь пользователя (последовательность запросов)
- Профиль инъекции — как пользователи добавляются во времени
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._
class BasicSimulation extends Simulation {
// 1. Конфигурация протокола
val httpProtocol = http
.baseUrl("https://api.example.com")
.acceptHeader("application/json")
.contentTypeHeader("application/json")
// 2. Определение сценария
val scn = scenario("Просмотр товаров")
.exec(
http("Список товаров")
.get("/api/products")
.check(status.is(200))
.check(jsonPath("$.products").exists)
)
.pause(2)
.exec(
http("Детали товара")
.get("/api/products/1")
.check(status.is(200))
.check(jsonPath("$.name").saveAs("productName"))
)
.pause(1)
// 3. Setup с профилем инъекции
setUp(
scn.inject(
rampUsers(100).during(60.seconds)
)
).protocols(httpProtocol)
.assertions(
global.responseTime.percentile3.lt(1000),
global.successfulRequests.percent.gt(99)
)
}
Конфигурация протокола
Билдер HTTP-протокола определяет общие настройки для всех запросов:
val httpProtocol = http
.baseUrl("https://api.example.com")
.acceptHeader("application/json")
.acceptEncodingHeader("gzip, deflate")
.userAgentHeader("Gatling/3.9")
.shareConnections
Определение сценария
Сценарий — это последовательность действий виртуального пользователя:
val scn = scenario("Путь пользователя")
.exec(http("Главная").get("/"))
.pause(1, 3) // случайная пауза 1-3 секунды
.exec(http("Логин").post("/login")
.body(StringBody("""{"user":"test","pass":"123"}"""))
.check(jsonPath("$.token").saveAs("authToken"))
)
.exec(http("Дашборд").get("/dashboard")
.header("Authorization", "Bearer ${authToken}")
)
Ключевые действия:
exec()— Выполнить HTTP-запросpause()— Ждать между запросами (think time)repeat()— Повторить последовательность N разduring()— Повторять в течение указанной длительностиdoIf()/doIfOrElse()— Условное выполнениеfeed()— Внедрить данные из фидера (CSV, JSON, JDBC)
Feeders (Параметризация данных)
Feeders поставляют тестовые данные виртуальным пользователям:
// CSV feeder
val csvFeeder = csv("users.csv").circular // переработка при исчерпании
// JSON feeder
val jsonFeeder = jsonFile("products.json").random
// Inline feeder
val inlineFeeder = Iterator.continually(Map(
"username" -> s"user_${scala.util.Random.nextInt(1000)}",
"email" -> s"user${scala.util.Random.nextInt(1000)}@test.com"
))
val scn = scenario("Параметризованный тест")
.feed(csvFeeder)
.exec(http("Логин")
.post("/login")
.body(StringBody("""{"username":"${username}","password":"${password}"}"""))
)
Checks и Assertions
Checks валидируют отдельные ответы:
http("Получить пользователя")
.get("/api/users/1")
.check(status.is(200))
.check(jsonPath("$.name").is("John"))
.check(responseTimeInMillis.lt(500))
.check(jsonPath("$.id").saveAs("userId"))
.check(header("Content-Type").is("application/json"))
Assertions определяют критерии прохождения/провала для всей симуляции:
setUp(scn.inject(...))
.assertions(
global.responseTime.max.lt(5000),
global.responseTime.percentile3.lt(1000),
global.successfulRequests.percent.gt(99),
details("Login").responseTime.mean.lt(500)
)
Профили инъекции
Профили инъекции определяют, как виртуальные пользователи вводятся в симуляцию. Gatling предлагает несколько стратегий:
setUp(
scn.inject(
nothingFor(5.seconds),
atOnceUsers(10),
rampUsers(100).during(60.seconds),
constantUsersPerSec(20).during(120.seconds),
rampUsersPerSec(1).to(50).during(60.seconds)
)
)
Эти профили можно комбинировать для создания сложных паттернов нагрузки.
Запуск Gatling
# Используя бандл
./bin/gatling.sh
# Используя Maven
mvn gatling:test
# Используя Gradle
gradle gatlingRun
# Используя sbt
sbt Gatling/test
Gatling генерирует HTML-отчёт в target/gatling/ с графиками времён ответа, пропускной способности и активных пользователей во времени.
Упражнение: Проектирование симуляции Gatling
Создайте симуляцию Gatling для платформы онлайн-аукционов.
Сценарий
Платформа аукционов имеет следующие эндпоинты:
| Эндпоинт | Метод | Описание |
|---|---|---|
/api/auth/login | POST | Авторизация пользователя |
/api/auctions | GET | Список активных аукционов |
/api/auctions/{id} | GET | Детали аукциона |
/api/auctions/{id}/bid | POST | Сделать ставку |
Требования
- Используйте CSV feeder для учётных данных
- Цепочка запросов: логин, просмотр аукционов, просмотр деталей, ставка
- Извлеките токен из логина и ID аукциона из списка
- Добавьте паузы между действиями (1-3 секунды)
- Профиль инъекции: 50 пользователей за 2 минуты ramp-up, удержание 3 минуты, снижение
- Assertions: p95 < 1000мс, процент успеха > 98%
Подсказка: Структура симуляции
class AuctionSimulation extends Simulation {
val httpProtocol = http.baseUrl("https://auction-api.example.com")
val csvFeeder = csv("users.csv").circular
val scn = scenario("Ставки на аукционе")
.feed(csvFeeder)
.exec(/* логин */)
.pause(1, 3)
.exec(/* список аукционов, сохранить auctionId */)
.pause(1, 2)
.exec(/* детали аукциона */)
.pause(1, 2)
.exec(/* сделать ставку */)
setUp(
scn.inject(
rampUsers(50).during(120.seconds),
nothingFor(180.seconds)
)
).protocols(httpProtocol)
.assertions(/* ваши assertions */)
}
Решение: Полная симуляция Gatling
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._
class AuctionSimulation extends Simulation {
val httpProtocol = http
.baseUrl("https://auction-api.example.com")
.acceptHeader("application/json")
.contentTypeHeader("application/json")
val csvFeeder = csv("users.csv").circular
val scn = scenario("Поток ставок на аукционе")
.feed(csvFeeder)
.exec(
http("Логин")
.post("/api/auth/login")
.body(StringBody("""{"username":"${username}","password":"${password}"}"""))
.check(status.is(200))
.check(jsonPath("$.token").saveAs("authToken"))
)
.pause(1, 3)
.exec(
http("Список аукционов")
.get("/api/auctions")
.header("Authorization", "Bearer ${authToken}")
.check(status.is(200))
.check(jsonPath("$.auctions[0].id").saveAs("auctionId"))
.check(jsonPath("$.auctions[0].currentBid").saveAs("currentBid"))
)
.pause(1, 2)
.exec(
http("Детали аукциона")
.get("/api/auctions/${auctionId}")
.header("Authorization", "Bearer ${authToken}")
.check(status.is(200))
.check(jsonPath("$.title").exists)
)
.pause(1, 2)
.exec(session => {
val currentBid = session("currentBid").as[String].toDouble
val newBid = currentBid + 10.0
session.set("newBid", newBid.toString)
})
.exec(
http("Сделать ставку")
.post("/api/auctions/${auctionId}/bid")
.header("Authorization", "Bearer ${authToken}")
.body(StringBody("""{"amount":${newBid}}"""))
.check(status.in(200, 201))
)
setUp(
scn.inject(
rampUsers(50).during(120.seconds),
nothingFor(180.seconds)
)
).protocols(httpProtocol)
.assertions(
global.responseTime.percentile(95).lt(1000),
global.successfulRequests.percent.gt(98)
)
}
Анализ HTML-отчёта:
- Распределение времён ответа: Проверьте, что гистограмма смещена влево (быстрые ответы)
- Активные пользователи во времени: Убедитесь, что паттерн ramp-up соответствует профилю инъекции
- Запросов в секунду: Ищите стабильность throughput во время устойчивой нагрузки
- Перцентили времени ответа: p95 должен оставаться ниже 1000мс на протяжении всего теста
- Вкладка ошибок: Определите, какие запросы падают и при каком числе пользователей
Профессиональные советы
- DSL Java/Kotlin: Если команда не знает Scala, Gatling 3.7+ поддерживает DSL на Java и Kotlin, не уступающие по мощности. Java DSL — рекомендуемая отправная точка для большинства команд.
- Закрытая vs Открытая модель: Gatling поддерживает как закрытую модель (фиксированное число конкурентных пользователей), так и открытую (пользователи приходят с определённой частотой). Используйте открытую модель для веб-приложений, где новые пользователи приходят постоянно, независимо от того, сколько уже активно.
- Gatling Recorder: Аналогично рекордеру JMeter, Gatling включает конвертер HAR и прокси-рекордер HTTP для захвата трафика браузера и преобразования его в скрипты Gatling.
- Maven Archetype: Начинайте новые проекты с
mvn archetype:generate -DarchetypeGroupId=io.gatling.highcharts -DarchetypeArtifactId=gatling-highcharts-maven-archetypeдля предконфигурированной структуры проекта. - Интеграция с CI: Плагины Maven/Gradle для Gatling упрощают запуск нагрузочных тестов в Jenkins, GitHub Actions или GitLab CI. Настройте assertions для провала сборки при деградации производительности.