数据库部分
验证数据库在高并发、大数据量负载下的稳定性 本文将分享一个基于 Java 编写的自动化压力测试脚本,通过模拟业务场景中的“大载荷”写入,来探测数据库连接池配置、网络带宽及磁盘 I/O 的极限。
1. 设计思路
本次测试的核心目标是验证**长期运行**下的系统表现。我们关注以下几个维度:
-
并发稳定性:多线程环境下连接池的分配与回收是否正常。
-
大数据块压力:模拟每条记录携带 5KB 以上的 Payload,测试磁盘吞吐。
-
异常容错:当遇到数据库抖动时,客户端能否自动恢复。
2. 核心代码实现
选用了经典的 C3P0 作为连接池,配合 Apache Commons Lang 处理数据填充。
package com.tech.test;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.lang3.StringUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder;
/**
* 数据库写入稳定性测试工具
* 功能:模拟多线程高并发下的大载荷数据写入,并监控实时 TPS
*/
public class SqlStabilityTester {
private static final String DB_URL = "jdbc:mysql://[YOUR_DB_HOST]:[PORT]/[DB_NAME]?rewriteBatchedStatements=true&useSSL=false&serverTimezone=UTC";
private static final String USER = "your_username";
private static final String PASS = "your_password";
// --- 测试参数配置 ---
private static final int THREAD_COUNT = 8; // 线程数
private static final int BATCH_SIZE = 1000; // 批处理条数
private static final int PAYLOAD_KB = 5; // (KB)
private static final int TEST_DURATION_HOURS = 1; // 测试持续时长
private static final LongAdder totalCounter = new LongAdder();
private static final LongAdder errorCounter = new LongAdder();
private static volatile boolean isRunning = true;
public static void main(String[] args) throws Exception {
System.setProperty("com.mchange.v2.log.MLog", "com.mchange.v2.log.FallbackMLog");
System.setProperty("com.mchange.v2.log.FallbackMLog.DefaultCutoffLevel", "WARNING");
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass("com.mysql.cj.jdbc.Driver");
cpds.setJdbcUrl(DB_URL);
cpds.setUser(USER);
cpds.setPassword(PASS);
cpds.setInitialPoolSize(5);
cpds.setMinPoolSize(5);
cpds.setMaxPoolSize(THREAD_COUNT + 5);
cpds.setAcquireIncrement(2);
cpds.setMaxIdleTime(300);
cpds.setIdleConnectionTestPeriod(60);
cpds.setPreferredTestQuery("SELECT 1");
System.out.println(">>> 启动...");
System.out.println(">>> 配置信息: " + THREAD_COUNT + " 线程 | 数据: " + PAYLOAD_KB + "KB");
ExecutorService pool = Executors.newFixedThreadPool(THREAD_COUNT);
Thread monitor = new Thread(new StatisticsTask());
monitor.setDaemon(true);
monitor.start();
for (int i = 0; i < THREAD_COUNT; i++) {
pool.execute(() -> startLoad(cpds));
}
TimeUnit.HOURS.sleep(TEST_DURATION_HOURS);
isRunning = false;
pool.shutdown();
if (pool.awaitTermination(1, TimeUnit.MINUTES)) {
cpds.close();
System.out.println(">>> 结束关闭。");
}
}
/**
* 逻辑
*/
private static void startLoad(ComboPooledDataSource ds) {
final String dataPayload = StringUtils.repeat("X", PAYLOAD_KB * 1024);
while (isRunning) {
try (Connection conn = ds.getConnection()) {
conn.setAutoCommit(false); // 开启事务
try (PreparedStatement ps = conn.prepareStatement(
"INSERT INTO test_stability (id, name, data) VALUES (?, ?, ?)")) {
for (int i = 0; i < BATCH_SIZE; i++) {
ps.setString(1, UUID.randomUUID().toString());
ps.setString(2, "Worker-" + Thread.currentThread().getId());
ps.setString(3, dataPayload);
ps.addBatch();
}
ps.executeBatch(); // 批处理
conn.commit(); // 提交
totalCounter.add(BATCH_SIZE);
}
} catch (SQLException e) {
errorCounter.increment();
System.err.println("异常: " + e.getMessage());
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException ignored) {}
}
}
}
/**
* 内部类
*/
private static class StatisticsTask implements Runnable {
private long lastTotal = 0;
@Override
public void run() {
while (isRunning) {
try {
TimeUnit.SECONDS.sleep(10);
long currentTotal = totalCounter.sum();
long delta = currentTotal - lastTotal;
double tps = delta / 10.0;
System.out.printf("[监控] 累计写入: %-10d | 近10s平均TPS: %-8.2f | 异常数: %d%n",
currentTotal, tps, errorCounter.sum());
lastTotal = currentTotal;
} catch (InterruptedException e) {
break;
}
}
}
}
}
3. 关键点解析
3.1 批处理优化
rewriteBatchedStatements=true。
3.2 资源防泄漏
try-with-resources 语法
3.3 参数配置
IdleConnectionTestPeriod
PreferredTestQuery
4. 监控与观察
StatisticsTask 线程
自动化压测脚本
使用 Shell 循环擦写脚本。 dd 命令配合 conv=fdatasync 参数,强制绕过 Linux 内核级的文件系统内存缓存 ,将数据流直接入物理磁盘。 当使用率达到 95% 时触发 rm -f 物理清理并开始下一轮写入。同时,在每一组测试的始末,利用 smartctl 工具精准抓取底层硬盘控制器中 ID 为 241 的 Total_LBAs_Written 原始计数,用于计算硬件级的物理写放大。
设定了 MAX_USAGE=95% 最大磁盘占用
设定每次写入的数据块大小为 1024m / 500k
进入 while true 无限循环
sudo smartctl -A /dev/sda

smartctl 工具精准抓取底层硬盘控制器中 ID 为 241 的 Total_LBAs_Written 原始计数
| 关键指标 (ID 241) | 压测前 (Start) | 压测后 (End) | 差值 (Δ) |
| Total_LBAs_Written | 2606 | 3269 | 663 |


在 1GB 大块顺序写入模式下,脚本总计下发 500GB 的写入。测试结束后,通过抓取 SMART 底层指标,计数器从 2411 增长至 2470,共计增加 59 个单位。经底层实际测算,对应的物理写放大率(WA)为 1.32。

这些都是你写的呀?