之前写过一篇固定 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;
}
}
异步补偿线程的线程池状态验证
旧代码:
@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]
这里修改代码如下:
@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
旧代码:
@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()
方法之后做的计数增加,可能会导致异步补偿线程判断是否需要补偿,补偿额度出现过度补偿的问题。虽然在较长时间的测试中会慢慢中和掉这个影响,但我还是决定修改掉。
这里修改代码如下:
@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();
}
}
公众号FunTester首发,原创分享爱好者,腾讯云、开源中国和掘金社区首页推荐,知乎八级强者,欢迎关注、交流,禁止第三方擅自转载。