Gatling 性能测试实战案例
电商系统登录与商品浏览
场景描述
模拟1000个用户同时登录电商平台并浏览商品详情页。

完整代码示例
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"
}
}
性能调优建议
- 测试前预热服务
- 合理设置超时时间
- 适时清理测试数据
- 监控服务器资源使用情况
这些案例覆盖了常见的性能测试场景,按需取用,实际项目中应根据具体需求进行定制和优化。