高QPS下的固定QPS模型

507 阅读2分钟

在之前写过的文章固定QPS压测模式探索固定QPS压测初试中,我用到了一个任务发生器和sleep()方法来达到固定QPS的请求实现。但是在最近的工作中,在高QPS场景下,这种方式就会显示出其缺点:单线程任务生成器性能不足,由此带来的副作用就是误差较大。为此,我引入了多线程任务生成器的功能。

主要思路就是在性能测试软启动完成后,根据设置QPS大小分配多个的线程来完成生成任务的功能。

这里引入一个常量:

    /**
     * 单个线程执行的最大QPS任务速率
     */
    public static int QPS_PER_THREAD = 250;

固定QPS测试用例启动方式改成如下:

    /**
     * 执行多线程任务
     * 默认取list中thread对象,丢入线程池,完成多线程执行,如果没有threadname,name默认采用desc+线程数作为threadname,去除末尾的日期
     */
    public PerformanceResultBean start() {
        boolean isTimesMode = baseThread.isTimesMode;
        int limit = baseThread.limit;
        int qps = baseThread.qps;
        executeThread = qps / Constant.QPS_PER_THREAD + 1;
        interval = 1_000_000_000 / qps;//此处单位1s=1000ms,1ms=1000000ns
        int runupTotal = qps * PREFIX_RUN;//计算总的请求量
        double diffTime = 2 * (Constant.RUNUP_TIME / PREFIX_RUN * interval - interval);//计算最大时间间隔和最小时间间隔差值
        double piece = diffTime / runupTotal;//计算每一次请求时间增量
        for (int i = runupTotal; i > 0; i--) {
            executorService.execute(threads.get(limit-- % queueLength).clone());
            sleep((long) (interval + i * piece));
        }
        sleep(1.0);
        allTimes = new Vector<>();
        marks = new Vector<>();
        executeTimes.getAndSet(0);
        errorTimes.getAndSet(0);
        logger.info("=========预热完成,开始测试!=========");
        Progress progress = new Progress(threads, StatisticsUtil.getTrueName(desc), executeTimes);
        new Thread(progress).start();
        startTime = Time.getTimeStamp();
        AidThread aidThread = new AidThread();
        new Thread(aidThread).start();
        CountDownLatch countDownLatch = new CountDownLatch(executeThread);
        for (int i = 0; i < executeThread; i++) {
            new FunTester(countDownLatch).start();
        }
        endTime = Time.getTimeStamp();
        aidThread.stop();
        progress.stop();
        GCThread.stop();
        try {
            countDownLatch.wait();
            executorService.shutdown();
            executorService.awaitTermination(HttpClientConstant.WAIT_TERMINATION_TIMEOUT, TimeUnit.SECONDS);//此方法需要在shutdown方法执行之后执行
        } catch (InterruptedException e) {
            logger.error("线程池等待任务结束失败!", e);
        }
        logger.info("总计执行 {} ,共用时:{} s,执行总数:{},错误数:{}!", baseThread.isTimesMode ? baseThread.limit + "次任务" : "秒", Time.getTimeDiffer(startTime, endTime), executeTimes, errorTimes);
        return over();
    }

其中第二个for循环正是启动多个线程执行测试:

        for (int i = 0; i < executeThread; i++) {
            new FunTester(countDownLatch).start();
        }

内部多线程类com.funtester.frame.execute.FixedQpsConcurrent.FunTester的代码如下:

    /**
     * 执行请求生成和执行类
     */
    private class FunTester extends Thread {

        FunTester(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }

        CountDownLatch countDownLatch;

        boolean isTimesMode = baseThread.isTimesMode;

        int limit = baseThread.limit / executeThread;

        long nanosec = interval * executeThread;

        @Override
        public void run() {
            try {
                while (true) {
                    executorService.execute(threads.get(limit-- % queueLength).clone());
                    if (needAbord || (isTimesMode ? limit < 1 : Time.getTimeStamp() - startTime > limit)) break;
                    SourceCode.sleep(nanosec);
                }
            } catch (Exception e) {
                logger.warn("任务发生器执行发生错误了!", e);
            } finally {
                countDownLatch.countDown();
            }
        }

    }

目前在1-2万的QPS场景下,测试结果比较满意,先就这么地吧,以后碰到坑了,再继续优化。


FunTester腾讯云年度作者Boss直聘签约作者GDevOps官方合作媒体,非著名测试开发。