高QPS下的固定QPS模型
在之前写过的文章固定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场景下,测试结果比较满意,先就这么地吧,以后碰到坑了,再继续优化。