Gatling案例

wen java案例 1

Gatling 性能测试实战案例

电商系统登录与商品浏览

场景描述

模拟1000个用户同时登录电商平台并浏览商品详情页。

Gatling案例

完整代码示例

import io.gatling.javaapi.core.*;
import io.gatling.javaapi.http.*;
import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.*;
import java.time.Duration;
public class ECommerceSimulation extends Simulation {
    // HTTP 协议配置
    HttpProtocolBuilder httpProtocol = http
        .baseUrl("https://www.example-ecommerce.com")
        .acceptHeader("application/json")
        .userAgentHeader("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
    // 测试数据 - 用户信息
    Iterator<Map<String, Object>> userFeeder = 
        Iterator.continually(Map.of(
            "username", "user" + RandomStringUtils.random(5, true, false) + "@test.com",
            "password", "test123"
        ));
    // 场景1: 登录场景
    ScenarioBuilder loginScenario = scenario("用户登录")
        .feed(userFeeder)
        .exec(http("打开登录页面")
            .get("/login")
            .check(status().is(200)))
        .pause(1, 3)
        .exec(http("提交登录请求")
            .post("/api/login")
            .header("Content-Type", "application/json")
            .body(StringBody("{\"username\":\"#{username}\",\"password\":\"#{password}\"}"))
            .check(jsonPath("$.token").saveAs("authToken"))
            .check(status().is(200)))
        .pause(2)
        .exec(session -> {
            System.out.println("用户 " + session.getString("username") + " 登录成功");
            return session;
        });
    // 场景2: 商品浏览场景
    ScenarioBuilder browseScenario = scenario("商品浏览")
        .exec(http("获取商品列表")
            .get("/api/products")
            .queryParam("page", "1")
            .queryParam("size", "20")
            .check(jsonPath("$.total").exists()))
        .pause(2)
        .exec(http("查看商品详情")
            .get("/api/products/${productId}")
            .check(status().in(200, 404)))
        .repeat(3) {
            pause(1)
            .exec(http("浏览商品图片")
                .get("/api/products/images/${imageId}"))
        };
    // 负载配置
    SetUp setUp = setUp(
        loginScenario.injectOpen(
            rampUsers(100).during(Duration.ofSeconds(10)),
            constantUsersPerSec(50).during(Duration.ofMinutes(5))
        ),
        browseScenario.injectOpen(
            rampUsers(500).during(Duration.ofSeconds(30)),
            constantUsersPerSec(100).during(Duration.ofMinutes(10))
        )
    ).protocols(httpProtocol);
}

RESTful API 压力测试

场景描述

对订单创建、查询、更新API进行持续压力测试。

import io.gatling.javaapi.core.*;
import io.gatling.javaapi.http.*;
import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.*;
public class OrderApiStressTest extends Simulation {
    // 环境配置
    private static final String BASE_URL = "https://api.example.com/v1";
    private static final String API_KEY = "your-api-key";
    // HTTP 协议
    HttpProtocolBuilder httpProtocol = http
        .baseUrl(BASE_URL)
        .header("Authorization", "Bearer ${token}")
        .header("X-API-Key", API_KEY)
        .contentTypeHeader("application/json")
        .check(status().is(200));
    // 创建订单场景
    ScenarioBuilder createOrder = scenario("创建订单")
        .exec(session -> session.set("orderId", UUID.randomUUID().toString()))
        .exec(http("创建订单")
            .post("/orders")
            .body(ElFileBody("data/create_order.json"))
            .check(jsonPath("$.id").saveAs("orderId"))
            .check(jsonPath("$.status").is("created")))
        .pause(1)
        .exec(http("查询订单")
            .get("/orders/${orderId}")
            .check(jsonPath("$.id").is("${orderId}")));
    // 批量更新场景
    ScenarioBuilder updateOrder = scenario("更新订单状态")
        .exec(http("获取待处理订单")
            .get("/orders")
            .queryParam("status", "pending")
            .check(jsonPath("$.orders[*].id").findRandom().saveAs("pendingOrderId")))
        .pause(2)
        .exec(http("更新订单")
            .put("/orders/${pendingOrderId}")
            .body(StringBody("{\"status\":\"processing\"}"))
            .check(jsonPath("$.status").is("processing")));
    // 并发测试配置
    SetUp setUp = setUp(
        createOrder.injectOpen(
            rampUsers(50).during(10),
            constantUsersPerSec(20).during(120),
            rampUsers(100).during(30)
        ),
        updateOrder.injectOpen(
            rampUsers(30).during(10),
            constantUsersPerSec(10).during(120)
        )
    ).protocols(httpProtocol)
     .assertions(
        global().responseTime().max().lt(2000),
        global().successfulRequests().percent().gt(95.0),
        forAll().responseTime().percentile3().lt(1500)
     );
}

WebSocket 实时通信测试

场景描述

测试即时通讯系统中的消息发送和接收。

import io.gatling.core.Predef._
import io.gatling.http.Predef._
import io.gatling.core.structure.ScenarioBuilder
import scala.concurrent.duration._
class WebSocketSimulation extends Simulation {
    val httpProtocol = http
        .baseUrl("wss://ws.example-chat.com")
        .acceptHeader("application/json")
        .userAgentHeader("Gatling-Perf-Test")
    // WebSocket 连接
    val connectWs = exec(
        ws("建立WebSocket连接")
            .connect("/chat/room/1")
            .await(5 seconds)(
                ws.checkTextMessage("连接确认")
                    .check(regex("connected"))
            )
    )
    // 发送消息场景
    val sendMessage = exec(
        ws("发送文本消息")
            .sendTextMessage("""{"type":"message","content":"Hello from Gatling!"}""")
    )
    val receiveMessage = exec(
        ws("接收回复")
            .await(10 seconds)(
                ws.checkTextMessage("收到回复")
                    .check(regex("message_id"))
            )
    )
    val chatScenario = scenario("实时聊天")
        .exec(connectWs)
        .pause(2)
        .repeat(10) {
            exec(sendMessage)
            .pause(500 milliseconds)
            .exec(receiveMessage)
        }
        .exec(ws("关闭连接").close)
    setUp(
        chatScenario.inject(
            rampUsers(200).during(30 seconds),
            constantUsersPerSec(50) during (2 minutes)
        )
    ).protocols(httpProtocol)
}

文件上传/下载测试

场景描述

测试文件上传功能和批量下载功能。

import io.gatling.core.Predef._
import io.gatling.http.Predef._
import java.util.UUID
class FileTransferSimulation extends Simulation {
    val httpProtocol = http
        .baseUrl("https://files.example.com")
        .acceptHeader("application/json")
        .userAgentHeader("Gatling-File-Test")
    // 文件上传场景
    val uploadScenario = scenario("文件上传")
        .exec(session => 
            session.set("fileName", s"test_${UUID.randomUUID()}.pdf")
                   .set("fileSize", 1024 * 1024) // 1MB
        )
        .exec(
            http("上传文件")
                .post("/upload")
                .header("Content-Type", "multipart/form-data")
                .bodyPart(RawFileBodyPart("file", "test_data.pdf")
                    .fileName("${fileName}")
                    .contentType("application/pdf"))
                .check(status().is(200))
                .check(jsonPath("$.fileId").saveAs("fileId"))
        )
        .pause(2)
        .exec(
            http("下载文件")
                .get("/download/${fileId}")
                .check(
                    status().is(200),
                    header("Content-Length").is("${fileSize}")
                )
        )
    // 批量下载场景
    val batchDownload = scenario("批量下载")
        .exec { session =>
            val fileIds = (1 to 10).map(i => s"file_${i}").toArray
            session.set("batchFiles", fileIds.mkString(","))
        }
        .exec(
            http("批量下载文件")
                .post("/batch-download")
                .body(StringBody("""{"files":["${batchFiles}"]}"""))
                .check(status().is(200))
        )
    setUp(
        uploadScenario.inject(
            rampUsers(100).during(60 seconds),
            constantUsersPerSec(10) during (5 minutes)
        ),
        batchDownload.inject(
            rampUsers(50).during(30 seconds),
            constantUsersPerSec(5) during (5 minutes)
        )
    ).protocols(httpProtocol)
}

数据库关联查询场景

场景描述

模拟用户进行复杂查询和结果分页浏览。

import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._
class ComplexQuerySimulation extends Simulation {
    val httpProtocol = http
        .baseUrl("https://search.example.com")
        .acceptHeader("application/json")
        .contentTypeHeader("application/json")
    // 搜索参数数据源
    val searchTerms = Array("手机", "电脑", "耳机", "相机", "平板")
    val priceRanges = Array("0-500", "500-1000", "1000-2000", "2000-5000")
    val sortOrders = Array("relevance", "price_asc", "price_desc", "rating")
    // 复杂搜索场景
    val searchScenario = scenario("复杂搜索")
        .exec { session =>
            val term = searchTerms(Random.nextInt(searchTerms.length))
            val price = priceRanges(Random.nextInt(priceRanges.length))
            val sort = sortOrders(Random.nextInt(sortOrders.length))
            session
                .set("searchTerm", term)
                .set("priceRange", price)
                .set("sortOrder", sort)
        }
        .exec(
            http("执行搜索")
                .get("/api/search")
                .queryParam("q", "${searchTerm}")
                .queryParam("price", "${priceRange}")
                .queryParam("sort", "${sortOrder}")
                .queryParam("page", "1")
                .queryParam("size", "20")
                .check(
                    jsonPath("$.total").saveAs("totalResults"),
                    jsonPath("$.results[*].id").findAll.saveAs("productIds")
                )
        )
        .pause(1)
        .exec { session =>
            val total = session("totalResults").as[Int]
            val totalPages = Math.min(Math.ceil(total/20.0).toInt, 10)
            session.set("totalPages", totalPages)
        }
        .exec { session =>
            val totalPages = session("totalPages").as[Int]
            val currentPage = Random.nextInt(totalPages) + 1
            session.set("currentPage", currentPage)
        }
        .exec(
            http("分页查询")
                .get("/api/search")
                .queryParam("q", "${searchTerm}")
                .queryParam("page", "${currentPage}")
                .queryParam("size", "20")
                .check(jsonPath("$.page").is("${currentPage}"))
        )
    // 产品详情查看
    val productDetail = scenario("产品详情")
        .repeat(5) {
            exec(
                http("获取热门产品")
                    .get("/api/products/hot")
                    .check(jsonPath("$[*].id").findRandom.saveAs("hotProductId"))
            )
            .pause(2)
            .exec(
                http("查看产品详情")
                    .get("/api/products/${hotProductId}")
                    .check(
                        jsonPath("$.reviews").exists(),
                        jsonPath("$.specifications").exists()
                    )
            )
            .pause(1)
            .exec(
                http("获取相关推荐")
                    .get("/api/products/${hotProductId}/recommendations")
                    .check(jsonPath("$[*].id").exists())
            )
        }
    setUp(
        searchScenario.inject(
            rampUsers(100).during(30 seconds),
            constantUsersPerSec(20) during (10 minutes)
        ),
        productDetail.inject(
            rampUsers(50).during(30 seconds),
            constantUsersPerSec(10) during (10 minutes)
        )
    ).protocols(httpProtocol)
}

测试数据文件示例

create_order.json

{
  "customerId": "${customerId}",
  "products": [
    {
      "productId": "PRD001",
      "quantity": 2,
      "unitPrice": 199.99
    },
    {
      "productId": "PRD002",
      "quantity": 1,
      "unitPrice": 349.50
    }
  ],
  "shippingAddress": {
    "street": "123 Test Street",
    "city": "Beijing",
    "postalCode": "100000",
    "country": "China"
  },
  "paymentMethod": "credit_card",
  "notes": "Priority delivery requested"
}

测试报告解读与常用断言

// 断言示例
setUp(
    // 场景定义...
).assertions(
    // 响应时间断言
    global.responseTime.max.lt(5000),  // 最大响应时间小于5秒
    global.responseTime.mean.lt(1000), // 平均响应时间小于1秒
    // 成功率断言
    global.successfulRequests.percent.gt(99), // 成功率大于99%
    // 分位数断言
    global.responseTime.percentile1.lt(500),   // 50%响应时间<500ms
    global.responseTime.percentile2.lt(1500),  // 75%响应时间<1500ms
    global.responseTime.percentile3.lt(3000),  // 95%响应时间<3000ms
    global.responseTime.percentile4.lt(4000),  // 99%响应时间<4000ms
    // 吞吐量断言
    global.requestsPerSec.gt(500),  // 每秒请求数大于500
    // 并发数断言
    global.concurrentUsers.min.gt(100), // 最小并发用户数大于100
    global.concurrentUsers.max.lt(1000) // 最大并发用户数小于1000
)

最佳实践建议

参数化设计

  • 使用feeder从外部文件读取测试数据
  • 避免硬编码数据,使用随机化
  • 数据和测试逻辑分离

断言设计

  • 验证业务成功标准(登录成功标志)
  • 验证数据完整性(JSON路径检查)
  • 性能阈值定义(P95 < 2秒)

监控指标

.manage(metrics {
    export {
        prometheus {
            port = 9095
        }
    }
})

报告输出

.enableReports {
    html {
        outputDirectory = "target/gatling-reports"
        openOnExit = false
    }
    json {
        path = "target/gatling-reports/results.json"
    }
}

性能调优建议

  • 测试前预热服务
  • 合理设置超时时间
  • 适时清理测试数据
  • 监控服务器资源使用情况

这些案例覆盖了常见的性能测试场景,按需取用,实际项目中应根据具体需求进行定制和优化。

抱歉,评论功能暂时关闭!