在高并发应用测试时,数据库测试是性能测试的重要一步。利用 Gatling 进行 JDBC 协议测试,可以精准地评估数据库连接池的性能和 SQL 查询的响应时间,帮助提前发现性能瓶颈,保证系统稳定。
Gatling JDBC测试重点
在Gatling的测试体系中,JDBC协议允许绕过应用程序的HTTP层,直接对数据库发起性能测试。意味着可以更纯粹地评估数据库本身、连接池以及SQL查询的效率。
测试目标:测试的目的是评估和验证数据库层在高并发场景下的表现。需要关注两个主要方面:数据库连接池的性能(如获取和释放连接的效率、连接池的容量和并发性)以及SQL查询的响应时间。
测试的概念:在编写测试脚本时,会用到几个Gatling的重点组件:
Scenario(场景):定义了虚拟用户在数据库中执行的一系列操作,例如执行特定的 SQL 语句。
Simulation(模拟):这是测试的主体,它包含了测试场景、负载模型(如并发用户数、加压方式)以及协议配置(这里就是 JDBC 协议)。
Session(会话):每个虚拟用户都会拥有一个独立的 Session,可以在其中存储和传递数据,例如将一次查询的结果作为下一次操作的输入。
配置测试环境和连接
开始之前,需要保证Gatling项目能够连接到目标数据库。
添加依赖:先在的构建管理工具(如 Maven 或 Gradle)中引入Gatling JDBC 的依赖。对于Gradle,添加 testCompile group: 'io.gatling.highcharts', name: 'gatling-charts-highcharts', version: '2.3.0'。
定义 JDBC 协议:在脚本中,需要配置数据库连接。包括数据库的 URL、驱动、用户名和密码等。
scala
import io.gatling.core.Predef._
import io.gatling.jdbc.Predef._
val jdbcProtocol = jdbc
.url("jdbc:postgresql://192.168.99.100:5432/gatling") // 数据库URL
.username("gatling")
.password("gatling123")
.driver("org.postgresql.Driver")
准备测试数据:为了获得准确的性能数据,需要一个和生产环境数据结构、数据量相似的数据库,并准备足够的测试数据,避免因数据量差异导致结果失真。
设计专业的测试场景
设计场景是性能测试的重点,需要模拟真实的压力。
基础 SQL 执行:使用jdbc函数来定义要执行的SQL语句。可以执行查询(select)、更新(update)、插入(insert)甚至调用存储过程。
scala
val scn = scenario("Database Load Test")
.exec(
jdbc("simple select")
.select("SELECT * FROM person WHERE id = 1")
.check(status.is(0)) // JDBC操作一般检查status是否为0表示成功
)
使用Feeder实现数据驱动:为了让测试更真实,避免查询缓存带来的失准,可以使用 Feeder 从外部文件(如 CSV)中读取数据,为每个虚拟用户提供不同的参数。
scala
val userIdFeeder = csv("user_ids.csv").circular // 从CSV文件循环读取用户ID
val dataDrivenScn = scenario("Data-Driven Test")
.feed(userIdFeeder)
.exec(
jdbc("parameterized select")
.select("SELECT * FROM person WHERE id = ${userId}") // 使用Feeder中的变量
.check(status.is(0))
)
模拟混合工作负载:一个完整的业务流一般包含读写操作。可以在一个场景中组合多种SQl语句。
scala
val complexScn = scenario("Complex Workload")
.forever() { // 循环执行以配合固定时长测试
exec(
jdbc("insert operation")
.insert("INSERT INTO log_table (message) VALUES ('Test log entry')")
)
.pause(1) // 模拟用户思考时间或业务间隔
.exec(
jdbc("update operation")
.update("UPDATE counters SET value = value + 1 WHERE name = 'visitor_count'")
)
.pause(1)
.exec(
jdbc("select operation")
.select("SELECT COUNT(*) FROM log_table")
)
}
实施负载模型和断言
Gatling的强大在于可以精确控制负载并验证性能指标。
选择负载模型:根据测试目标选择合适的模型很重要。
开放模型:适用于模拟用户独立到达的场景,如网站访问,通过控制用户到达速率来施压。例如 rampUsersPerSec(1) to 20 during (2 minutes) 表示在2分钟内,每秒到达的用户数从1逐步增加到20。
封闭模型:适用于测试目标和同时在线用户数相关的情况,如连接池测试。它通过控制系统中的并发用户数来施压。例如 constantConcurrentUsers(50) during (5 minutes) 能保证系统中始终有50个活跃的并发用户。当一个用户任务完成退出时,Gatling会立即注入一个新用户来维持总数。
开放模型:模拟用户独立到达,如网站访问、API压力测试,控制用户到达速率,系统并发用户数可变,constantUsersPerSec(10) during(1 minute)
封闭模型:测试连接池容量、固定规模用户群(如已登录用户),控制系统中并发用户数,用户"完成一个,补充一个",constantConcurrentUsers(50) during(5 minutes)
分阶段压力测试:为了更真实地模拟线上流量并找到系统瓶颈,可以设计分阶段的压力场景。
scala
setUp(
complexScn.inject(
// 1. 热身阶段:30秒内逐渐增加到10个并发用户
rampConcurrentUsers(1) to 10 during (30 seconds),
// 2. 稳定阶段:保持10个并发用户,持续2分钟
constantConcurrentUsers(10) during (2 minutes),
// 3. 压力阶段:1分钟内将并发用户数提升到50个
rampConcurrentUsers(10) to 50 during (1 minute),
// 4. 峰值维持:保持50个并发用户,持续3分钟
constantConcurrentUsers(50) during (3 minutes)
)
).protocols(jdbcProtocol)
.maxDuration(7 minutes) // 总测试时长控制
设置性能断言:断言是性能测试的门禁,保证系统达到预设的性能目标。可以对响应时间、成功率等重要指标设置断言。
scala
.assertions(
// 全局所有请求的95%分位响应时间应小于500毫秒
global.responseTime.percentile3.lt(500),
// 所有请求的成功率应高于99.9%
global.successfulRequests.percent.gt(99.9),
// 所有请求中,失败率应小于0.1%
global.failedRequests.percent.lt(0.1)
)
测试报告和性能优化
测试完成后,Gatling 会自动生成一份详尽的 HTML 报告。
性能指标:
响应时间:重点关注 平均值、95th/99th 百分位数(例如 P95=253ms 表示 95% 的请求响应时间快于 253 毫秒)。后者能更好地反映长尾延迟对用户体验的影响。
请求成功率:检查KO率(失败率),理想情况下应为 0 或低于可接受阈值。
吞吐量:关注 requests/sec(每秒请求数),这直接反映了数据库处理能力。
连接池性能问题定位:
如果观察到响应时间随着并发增加而急剧上升,或大量请求失败,可能是连接池配置不足(如 maxConnections 设置过小)。
通过监控数据库端的活动连接数,可以验证连接池的最大连接数限制是否生效。
关注连接获取时间,如果这个时间过长,可能需要调整连接池参数,例如连接超时时间。
SQL查询效率分析:
报告中会列出每个SQL语句的执行情况。针对响应时间特别长的SQL,需要回到数据库层面,使用EXPLAIN等工具分析其执行计划,判断是否存在全表扫描、索引缺失等问题。
结合分阶段负载,观察不同压力下 SQL 响应时间的变化曲线,评估稳定性。
注意事项
控制测试时长:为了让测试精确运行指定时长,可以结合maxDuration和循环场景(使用 .forever())来实现。
避免测试陷阱:
不要在生产数据库上直接运行高压测试。
测试环境应尽量和生产环境隔离,并且硬件、软件(包括数据库版本)及数据规模要尽可能接近。
测试数据要有代表性,并注意清理测试产生的垃圾数据,避免影响后续测试。
持续集成:Gatling可以和 Jenkins等 CI/CD 工具集成,将性能测试自动化,在每次代码变更后快速得到性能反馈。