📌 Gatling实战

1. Gatling vs JMeter 全方位对比

Gatling 和 JMeter 是当前最主流的两款开源性能测试工具,二者在设计理念、技术架构和应用场景上各有千秋。选型的关键不是"哪个更好",而是"哪个更适合当前场景"。

对比维度JMeterGatling推荐场景
开发语言 Java(支持BeanShell/Groovy/JS等脚本) Scala(底层基于Netty + Akka) Java团队 → JMeter;Scala/Akka团队 → Gatling
脚本编写方式 GUI拖拽 + XML(JMX),也可纯代码 纯代码(Scala DSL),无GUI编辑器 非开发人员 → JMeter GUI;开发人员 → Gatling
并发模型 线程模型(1虚拟用户 = 1线程) 异步非阻塞(Akka Actor模型) 高并发(>10000) → Gatling更高效
资源消耗 较高(每个线程占用栈内存约1MB) 极低(Actor复用,内存占用极小) 资源受限环境 → Gatling
单机并发上限 约500-1000(取决于脚本复杂度) 可轻松达到5000-10000 大规模单机压测 → Gatling
协议支持 极丰富:HTTP/HTTPS、JDBC、FTP、JMS、SMTP、TCP、gRPC(插件)等 HTTP/HTTPS为主,支持JMS、JDBC(插件较少) 多协议场景 → JMeter
报告能力 需配合插件(如JMeter Plugins)生成图表;HTML Dashboard功能较基础 内置实时HTML报告,图表精美、指标全面,无需额外配置 重视报告美观度 → Gatling
脚本可维护性 JMX为XML格式,版本管理不友好 Scala代码,天然支持Git版本管理和Code Review 重视DevOps/CI/CD → Gatling
学习曲线 低:GUI上手快,非技术人员也可使用 中高:需要掌握Scala语法和DSL编程 快速上手 → JMeter
CI/CD集成 支持(Maven/Gradle插件),但配置较复杂 原生支持:Maven/Gradle/SBT插件,开箱即用 CI/CD流水线 → Gatling
社区生态 ⭐⭐⭐⭐⭐ 最庞大的社区,海量资料和插件 ⭐⭐⭐ 社区规模中等,但文档质量高 遇到问题快速搜索 → JMeter
💡 选型建议
  • 选JMeter:团队以Java为主、需要GUI操作、涉及多协议压测、团队中有非开发人员
  • 选Gatling:团队采用DevOps/CI/CD流程、需要代码化脚本管理、高并发场景、重视报告专业性
  • 混合使用:并非二选一——可以在CI/CD中使用Gatling做基准测试,在专项压测中使用JMeter覆盖特殊协议场景

2. Gatling核心概念

2.1 架构原理

Gatling 基于 Akka Actor 模型Netty 异步网络框架 构建,这是其高性能的根本原因:

  • Akka Actor:每个虚拟用户不是一个重量级线程,而是一个轻量级Actor。Actor之间通过消息传递通信,可以轻松创建数万个并发Actor而不会耗尽系统资源
  • Netty NIO:异步非阻塞IO,单个网络线程可以管理数千个HTTP连接,避免了传统BIO模型中"一个连接一个线程"的资源浪费
  • 响应式流(Reactive Streams):采用背压(Back-Pressure)机制,自动调节请求速率,避免压垮被测系统

2.2 脚本结构

一个Gatling脚本由以下几个核心部分组成:

  • Scenario(场景):定义用户行为流程,由一系列exec(执行步骤)组成
  • Simulation(模拟):定义如何向被测系统注入负载——多少用户、以什么速率、持续多长时间
  • Protocol(协议配置):HTTP请求的基础配置(baseUrl、headers、连接池等)
  • Feeder(数据源):提供参数化数据,类似JMeter的CSV Data Set
  • Check(校验):对响应进行断言,类似JMeter的Assertion

3. Scala DSL场景编写实战

3.1 基础HTTP压测脚本

// ============================================================
// Gatling Simulation: 电商系统基准压测
// 文件名: src/test/scala/ECommerceSimulation.scala
// ============================================================
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._

class ECommerceSimulation extends Simulation {

  // 1. HTTP协议配置
  val httpProtocol = http
    .baseUrl("https://api.ecommerce.example.com")  // 基础URL
    .acceptHeader("application/json")              // 默认Accept头
    .contentTypeHeader("application/json")          // 默认Content-Type
    .userAgentHeader("Gatling/LoadTest")            // 自定义UA
    .connectionHeader("keep-alive")                 // 复用连接

  // 2. 定义Feeder:从CSV读取用户数据
  val userFeeder = csv("data/users.csv").random  // random: 随机读取

  // 3. 定义Scenario:用户行为流程
  val scn = scenario("E-Commerce User Journey")
    // 3.1 注入用户数据
    .feed(userFeeder)

    // 3.2 步骤1:用户登录
    .exec(http("Login")
      .post("/api/auth/login")
      .body(StringBody("""{"username":"${username}","password":"${password}"}"""))
      .asJson
      .check(status.is(200))
      .check(jsonPath("$.token").saveAs("authToken"))  // 提取Token
    )

    // 3.3 等待1-3秒(模拟用户思考时间)
    .pause(1, 3)

    // 3.4 步骤2:浏览商品列表
    .exec(http("Browse Products")
      .get("/api/products?page=1&size=20")
      .header("Authorization", "Bearer ${authToken}")
      .check(status.is(200))
      .check(jsonPath("$.data[*].id").findRandom.saveAs("productId"))
    )

    // 3.5 等待2-5秒
    .pause(2, 5)

    // 3.6 步骤3:查看商品详情
    .exec(http("View Product Detail")
      .get("/api/products/${productId}")
      .header("Authorization", "Bearer ${authToken}")
      .check(status.is(200))
    )

    // 3.7 步骤4:加入购物车
    .exec(http("Add to Cart")
      .post("/api/cart/items")
      .header("Authorization", "Bearer ${authToken}")
      .body(StringBody("""{"productId":"${productId}","quantity":1}"""))
      .asJson
      .check(status.is(200))
    )

  // 4. 注入策略:阶梯式加压
  setUp(
    scn.inject(
      rampUsers(100).during(60.seconds),     // 0-60s: 线性增加到100用户
      constantUsersPerSec(10).during(120.seconds), // 60-180s: 保持10用户/秒速率
      rampUsersPerSec(5).to(20).during(60.seconds) // 180-240s: 速率从5升到20
    )
  ).protocols(httpProtocol)
   .assertions(
     global.responseTime.percentile3.lte(500),    // P99 < 500ms
     global.successfulRequests.percent.gt(99),     // 成功率 > 99%
     global.requestsPerSec.gt(50)                  // 吞吐 > 50 RPS
   )
}

3.2 高级场景:条件分支和循环

// ============================================================
// 高级场景:根据用户类型分支、错误重试、动态参数
// ============================================================
val advancedScn = scenario("Advanced Scenario")

  // 循环执行3次
  .repeat(3, "attempt") {

    // 条件分支:VIP用户和普通用户走不同流程
    .doIf("${userType}", "vip") {
      exec(http("VIP Exclusive Offer")
        .get("/api/vip/offers")
        .check(status.is(200))
      )
    }
    .doIf("${userType}", "normal") {
      exec(http("Normal User Page")
        .get("/api/products/recommended")
        .check(status.is(200))
      )
    }

    // 错误重试:如果状态码不是200,最多重试2次
    .tryMax(2) {
      exec(http("Payment - May Fail")
        .post("/api/payment/process")
        .body(StringBody("""{"orderId":"${orderId}"}"""))
        .asJson
        .check(status.is(200))
      )
    }
  }

  // 在整个场景执行期间,每5秒输出一次统计信息
  .exec { session =>
    println(s"Completed attempt ${session("attempt").as[Int]}")
    session
  }

3.3 多场景组合与流量配比

真实系统中,不同用户类型的访问模式不同。Gatling支持多场景组合并按比例分配流量:

// 多场景组合:模拟真实流量配比
class MultiScenarioSimulation extends Simulation {

  val httpProtocol = http.baseUrl("https://api.example.com")

  // 场景1: 浏览型用户(占60%)
  val browserScn = scenario("Browsers")
    .exec(http("Home").get("/"))
    .pause(3, 8)
    .exec(http("Search").get("/search?q=test"))

  // 场景2: 购买型用户(占30%)
  val buyerScn = scenario("Buyers")
    .exec(http("Login").post("/login"))
    .pause(1, 3)
    .exec(http("Order").post("/orders"))

  // 场景3: 管理型用户(占10%)
  val adminScn = scenario("Admins")
    .exec(http("Admin Login").post("/admin/login"))
    .exec(http("Dashboard").get("/admin/dashboard"))

  setUp(
    browserScn.inject(constantUsersPerSec(60).during(300.seconds)),   // 60/秒 = 60%
    buyerScn.inject(constantUsersPerSec(30).during(300.seconds)),     // 30/秒 = 30%
    adminScn.inject(constantUsersPerSec(10).during(300.seconds))      // 10/秒 = 10%
  ).protocols(httpProtocol)
}

4. Gatling实时报告

Gatling的一大亮点是其自动生成的专业HTML报告,无需任何额外插件或配置。执行压测后,报告在 target/gatling/ 目录下生成。

4.1 报告核心指标

报告区域包含指标用途
Global Information 总请求数、总时间、成功率、平均TPS 整体压测结果概览
Response Time Distribution 响应时间分布直方图、P50/P75/P95/P99分位数 分析延迟长尾问题
Response Time Percentiles (随时间) 各分位数随压测进程的时间变化曲线 观察系统是否在加压后性能劣化
Active Users (随时间) 活跃虚拟用户数的时间曲线 验证用户注入是否符合预期
Requests Per Second 每秒请求数的时间曲线(含成功/失败分解) 衡量系统吞吐能力
Responses Per Second 各HTTP状态码的每秒响应数堆叠图 快速发现错误爆发的时间点
Details (按请求分组) 每个请求的详细统计:P50/P95/P99、失败率、TPS 定位慢接口和失败接口

4.2 关键指标解读

  • P95 vs P99的差距:如果P99远大于P95(如P95=200ms、P99=2s),说明存在严重的延迟长尾,可能有少数请求触发慢查询或GC
  • 成功率突降点:在"Responses Per Second"图中寻找4xx/5xx响应突增的时间点,关联加压节奏分析系统容量瓶颈
  • 活跃用户 vs TPS:如果活跃用户增加但TPS不再增长,说明系统已达到吞吐上限,继续加压只会增加延迟

5. CI/CD集成

5.1 Maven/Gradle插件集成

Gatling提供了官方的Maven和Gradle插件,可以无缝集成到CI/CD流水线中:

// ============================================================
// Maven pom.xml: Gatling Maven Plugin 配置
// ============================================================
<plugin>
  <groupId>io.gatling</groupId>
  <artifactId>gatling-maven-plugin</artifactId>
  <version>4.7.0</version>
  <configuration>
    <simulationClass>com.example.ECommerceSimulation</simulationClass>
    <runMultipleSimulations>false</runMultipleSimulations>
    <includes>
      <include>com.example.*Simulation</include>
    </includes>
  </configuration>
</plugin>

// 命令行执行:
// mvn gatling:test -Dgatling.simulationClass=com.example.ECommerceSimulation

5.2 Jenkins Pipeline集成示例

// ============================================================
// Jenkinsfile: Gatling压测流水线
// ============================================================
pipeline {
    agent any

    environment {
        GATLING_HOME = tool name: 'Gatling', type: 'hudson.plugins.gatling.GatlingInstallation'
    }

    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }

        stage('Build & Package') {
            steps {
                sh 'mvn clean package -DskipTests'
                // 部署应用到测试环境
                sh 'kubectl apply -f k8s/test-deployment.yaml'
                sh 'kubectl rollout status deployment/my-app-test'
            }
        }

        stage('Performance Test') {
            steps {
                // 执行Gatling压测
                sh '''
                    mvn gatling:test \
                        -Dgatling.simulationClass=com.example.ECommerceSimulation \
                        -DbaseUrl=https://test-api.example.com \
                        -Dusers=500 \
                        -Dduration=300
                '''
            }
            post {
                always {
                    // 发布Gatling报告到Jenkins
                    gatlingArchive()
                    // 将报告上传为构建产物
                    archiveArtifacts artifacts: 'target/gatling/**/*.html'
                }
            }
        }

        stage('Performance Gate') {
            steps {
                // 解析Gatling全局断言结果
                script {
                    def assertionFile = readFile 'target/gatling/*/js/global_stats.json'
                    def stats = readJSON text: assertionFile

                    // 性能门禁检查
                    if (stats.meanResponseTime > 500) {
                        error "P95响应时间 ${stats.meanResponseTime}ms 超过门禁500ms"
                    }
                    if (stats.successRate < 99.5) {
                        error "成功率 ${stats.successRate}% 低于门禁99.5%"
                    }
                    echo "✅ 性能门禁通过: P95=${stats.meanResponseTime}ms, 成功率=${stats.successRate}%"
                }
            }
        }
    }

    post {
        failure {
            // 性能测试失败时,发送通知
            emailext(
                subject: "❌ [${env.JOB_NAME}] 性能测试未通过",
                body: "构建 #${env.BUILD_NUMBER} 的性能测试未通过门禁检查,请查看报告: ${env.BUILD_URL}",
                to: 'team@example.com'
            )
        }
    }
}

5.3 GitLab CI集成示例

# ============================================================
# .gitlab-ci.yml: GitLab CI Gatling压测
# ============================================================
stages:
  - build
  - deploy-test
  - perf-test
  - perf-report

variables:
  MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"

# 缓存Maven依赖
cache:
  paths:
    - .m2/repository/

performance-test:
  stage: perf-test
  image: maven:3.8-openjdk-17
  before_script:
    - apt-get update && apt-get install -y jq  # jq用于解析JSON报告
  script:
    - mvn gatling:test
        -Dgatling.simulationClass=com.example.ECommerceSimulation
        -DbaseUrl=${TEST_API_URL}
        -Dusers=${PERF_USERS}
        -Dduration=${PERF_DURATION}
  artifacts:
    when: always
    paths:
      - target/gatling/
    reports:
      performance: target/gatling/*/js/global_stats.json  # GitLab 性能报告
    expire_in: 30 days
  only:
    - main
    - develop
📖 CI/CD最佳实践
  • 分级压测:CI流水线中运行快速冒烟压测(1-2分钟,验证基本性能不劣化);夜间定时运行完整压测(30分钟以上,评估系统容量)
  • 版本对比:将每次压测的P95延迟写入时序数据库(如InfluxDB),构建性能趋势图,发现渐进式劣化
  • 环境一致性:CI/CD中的压测环境规格必须与生产环境一致(或等比例缩小),否则压测结果无参考意义
  • 数据预热:首次压测前先跑一轮"预热"(Warm-up)脚本,避免冷启动(JIT编译、连接池初始化等)影响指标准确性