之前写过一篇固定QPS压测模式探索文章,个人认为这个模型相比「固定线程数」并发请求压测服务的模型更加贴近实际情况,比较适合做负载测试。在最近的工作中尝试使用「固定QPS」的压测方案,有了一些实践成果(大部分还是修复了BUG
),分享一下。
先说一下实践代码,然后分享一下自己修复的两个BUG。
固定QPS实践
先讲一下最常用的HTTP
请求的负载测试实践,跳过单个请求的实践,这里用两个request
,通过一个list
完成存储,然后创建测试任务。
def requests = []
def base = getBase()
def order = new Order(base)
order.create(21, 17, "FunTester", "", 1, 1)
requests << FanLibrary.lastRequest
order.edit()
requests << FanLibrary.lastRequest
def threads = []
requests.size().times {
threads << new RequestTimesFixedQps<>(5, 50, new HeaderMark("requestid"), requests[it])
}
new FixedQpsConcurrent(threads,"固定QPS实践").start()
allOver()
下面分享一下通用型的内部类实现的测试脚本,只需要实现一下doing()
方法和clone()
方法即可。
public static void main(String[] args) {
TT qps = new TT(5, 50);
new FixedQpsConcurrent(qps).start();
}
static class TT extends FixedQpsThread{
public TT(int i, int i1) {
super(null, i1, i, null, true);
}
@Override
protected void doing() throws Exception {
sleep(1);
}
@Override
public TT clone() {
return this;
}
}
异步补偿线程的线程池状态验证
旧代码:
代码语言:javascript复制@Override
public void run() {
logger.info("补偿线程开始!");
while (key) {
sleep(HttpClientConstant.LOOP_INTERVAL);
int actual = executeTimes.get();
int qps = baseThread.qps;
long expect = (Time.getTimeStamp() - FixedQpsConcurrent.this.startTime) / 1000 * qps;
if (expect > actual qps) {
logger.info("期望执行数:{},实际执行数:{},设置QPS:{}", expect, actual, qps);
range((int) expect - actual).forEach(x -> {
sleep(100);
executorService.execute(threads.get(this.i % queueLength).clone());
});
}
}
logger.info("补偿线程结束!");
}
这里有个问题,在执行补偿的操作时候,线程池可能已经关闭了,可能会导致报错。
Exception in thread "Thread-1" java.util.concurrent.RejectedExecutionException: Task com.fun.frame.execute.FixedQpsConcurrent$TT@5e6bf970 rejected from java.util.concurrent.ThreadPoolExecutor@15897721[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 50]
这里修改代码如下:
代码语言:javascript复制 @Override
public void run() {
logger.info("补偿线程开始!");
try {
while (key) {
sleep(HttpClientConstant.LOOP_INTERVAL);
int actual = executeTimes.get();
int qps = baseThread.qps;
long expect = (Time.getTimeStamp() - FixedQpsConcurrent.this.startTime) / 1000 * qps;
if (expect > actual qps) {
logger.info("期望执行数:{},实际执行数:{},设置QPS:{}", expect, actual, qps);
range((int) expect - actual).forEach(x -> {
sleep(100);
if (!executorService.isShutdown())
executorService.execute(threads.get(this.i % queueLength).clone());
});
}
}
logger.info("补偿线程结束!");
} catch (Exception e) {
logger.error("补偿线程发生错误!", e);
}
}
增加了executorService.isShutdown()
的验证。
修改计数BUG
旧代码:
代码语言:javascript复制 @Override
public void run() {
try {
before();
threadmark = this.mark == null ? EMPTY : this.mark.mark(this);
long s = Time.getTimeStamp();
doing();
long e = Time.getTimeStamp();
long diff = e - s;
FixedQpsConcurrent.executeTimes.getAndIncrement();
FixedQpsConcurrent.allTimes.add(diff);
if (diff > HttpClientConstant.MAX_ACCEPT_TIME)
FixedQpsConcurrent.marks.add(diff CONNECTOR threadmark);
} catch (Exception e) {
FixedQpsConcurrent.errorTimes.getAndIncrement();
logger.warn("执行任务失败!,标记:{}", threadmark, e);
} finally {
after();
}
}
由于是在doing()
方法之后做的计数增加,可能会导致异步补偿线程判断是否需要补偿,补偿额度出现过度补偿的问题。虽然在较长时间的测试中会慢慢中和掉这个影响,但我还是决定修改掉。
这里修改代码如下:
代码语言:javascript复制 @Override
public void run() {
try {
before();
threadmark = this.mark == null ? EMPTY : this.mark.mark(this);
FixedQpsConcurrent.executeTimes.getAndIncrement();
long s = Time.getTimeStamp();
doing();
long e = Time.getTimeStamp();
long diff = e - s;
FixedQpsConcurrent.allTimes.add(diff);
if (diff > HttpClientConstant.MAX_ACCEPT_TIME)
FixedQpsConcurrent.marks.add(diff CONNECTOR threadmark);
} catch (Exception e) {
FixedQpsConcurrent.errorTimes.getAndIncrement();
logger.warn("执行任务失败!,标记:{}", threadmark, e);
} finally {
after();
}
}