构建高负载下的数据库/ 脚本 写入稳定性测试方案

2026-04-08 323 1  当前有 9 人在读此篇文章

数据库部分

验证数据库在高并发、大数据量负载下的稳定性  本文将分享一个基于 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

Jinming

95后典型金牛座,强迫症。

相关推荐

使用手册

1.客户端配置: 将匹配浏览器的驱动覆盖到此目录 win平台 点击 此文件启动客户端 如授权过期 需要编辑sy ...

1 条评论

  1. 8mg

    这些都是你写的呀?

发表评论

小程序 小程序
小程序