Что такое Gatling?

Gatling — это высокопроизводительный open-source инструмент нагрузочного тестирования, предназначенный для непрерывной интеграции и рабочих процессов, ориентированных на разработчиков. Построенный на Scala с асинхронной неблокирующей архитектурой (Akka + Netty), Gatling может симулировать тысячи конкурентных пользователей со значительно меньшим потреблением памяти, чем инструменты на основе потоков, такие как JMeter.

Gatling создаёт детальные интерактивные HTML-отчёты «из коробки» — часто считающиеся лучшими отчётами о производительности в индустрии. Эти отчёты включают распределение времён ответа, перцентили, количество запросов во времени и анализ ошибок.

Когда выбирать Gatling

У каждого инструмента нагрузочного тестирования есть свои сильные стороны. Вот сравнение Gatling.

ХарактеристикаGatlingJMeterk6
ЯзыкDSL Scala/Java/KotlinGUI + XMLJavaScript
АрхитектураAsync неблокирующаяПоток на пользователяГорутины Go
ОтчётыКрасивый HTML встроенныйБазовые, нужны плагиныТерминал + JSON/CSV
CI/CDПлагин Maven/Gradle/sbtРежим CLICLI нативный
ПротоколHTTP, WebSocket, JMS, MQTTHTTP, JDBC, FTP, LDAP, JMSHTTP, WebSocket, gRPC
Потребление ресурсовОчень низкоеВысокоеНизкое
Кривая обученияКрутая (Scala)СредняяЛёгкая (JS)

Выбирайте Gatling, когда: Нужны отличные отчёты, команда использует JVM-языки, нужно низкое потребление ресурсов при масштабировании или нативная интеграция с Maven/Gradle для CI/CD.

Структура симуляции Gatling

Симуляция Gatling — это класс Scala, расширяющий Simulation. Он содержит три основные части:

  1. Конфигурация протокола — настройки HTTP, базовый URL, заголовки
  2. Определение сценария — путь пользователя (последовательность запросов)
  3. Профиль инъекции — как пользователи добавляются во времени
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/loginPOSTАвторизация пользователя
/api/auctionsGETСписок активных аукционов
/api/auctions/{id}GETДетали аукциона
/api/auctions/{id}/bidPOSTСделать ставку

Требования

  1. Используйте CSV feeder для учётных данных
  2. Цепочка запросов: логин, просмотр аукционов, просмотр деталей, ставка
  3. Извлеките токен из логина и ID аукциона из списка
  4. Добавьте паузы между действиями (1-3 секунды)
  5. Профиль инъекции: 50 пользователей за 2 минуты ramp-up, удержание 3 минуты, снижение
  6. 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 для провала сборки при деградации производительности.