在之前的链路压测中文章中,我对单链路测试和链路参数流转进行了一些实践,具体的效果还不错。产出如下:
但是在实际工作中,由于测试数据准备的误差、测试环境数据变更、测试环境数据时效性等等问题,并不能保证每一条链路都能准备按照链路模型中所设计的次数和路径运行。
还有一个比较重点的点:数据的错误,很容易导致链路实现代码抛异常,这个问题在框架中doing()
方法运行中进行了异常的捕获。
下面是固定请求次数
的模型中实现方法。
@Override
public void run() {
try {
before();
long ss = Time.getTimeStamp();
while (true) {
try {
threadmark = mark == null ? EMPTY : this.mark.mark(this);
long s = Time.getTimeStamp();
doing();
long et = Time.getTimeStamp();
executeNum++;
int diff =(int) (et - s);
costs.add(diff);
if (diff > HttpClientConstant.MAX_ACCEPT_TIME)
marks.add(diff + CONNECTOR + threadmark + CONNECTOR + Time.getNow());
if ((et - ss) > time || status() || ThreadBase.needAbort()) break;
} catch (Exception e) {
logger.warn("执行任务失败!", e);
logger.warn("执行失败对象的标记:{}", threadmark);
errorNum++;
}
}
long ee = Time.getTimeStamp();
logger.info("线程:{},执行次数:{}, 失败次数: {},总耗时: {} s", threadName, executeNum, errorNum, (ee - ss) / 1000.0);
Concurrent.allTimes.addAll(costs);
Concurrent.requestMark.addAll(marks);
} catch (Exception e) {
logger.warn("执行任务失败!", e);
} finally {
after();
}
}
但是问题来了,抛出异常之后,对于单次请求没有太多影响,但是对链路测试影响比较大。很有可能导致下一次的链路执行会出现问题。比如单链路性能测试实践中的案例,上一次链路测试并没有将收藏智课正常取消,那么下一次执行链路时候的推送课程可能就会少几个(需求是推送过滤了老师原创和老师收藏的),那么推荐列表中准备的测试数据很可能耗尽,导致某一次执行之后,就不会推荐任何课程了,最终测试会失败。
还有有一种情况,就是上一次链路的失败,加上每一个线程绑定了一个用户,很可能导致下一个链路执行的失败,例如:如何对修改密码接口进行压测、如何同时压测创建和删除接口文章中提到的,至少会将错误率提升一倍,影响统计结果。
针对上面提到的链路运行的问题,我想到一个解决思路:在链路节点执行之前或者之后进行一些简单的逻辑判断,将执行引入不同的之路,比如列表中已经没有可以继续执行的课程后,就结束改线程。由于测试中的线程数减少了,那么继续测试也就失去了意义,所以也就要结束整个性能测试。
当然逻辑控制
也可以用来针对不同接口的比例进行控制,不同于如何对 N 个接口按比例压测中执行生成不同的HttpRequestBase
对象的多线程任务类,可以通过在链路中逻辑控制器
来静态或者动态调整接口请求次数甚至是次序。这个以后有机会再分享实践经验。
我用单链路性能测试实践中的案例进行修改,对几个可能出现的问题点进行逻辑处理。
@Override
protected void doing() throws Exception {
mirro.getKList()
def klist = mirro.getKList()
def karray = klist.getJSONArray("data")
if (karray.size() < 3) return ThreadBase.stop()
def kss = []
karray.each {
JSONObject parse = JSON.parse(JSON.toJSONString(it))
def level = parse.getIntValue("node_level")
def type = parse.getIntValue("ktype")
def id = parse.getIntValue("id")
kss << new K(id, type, level)
}
K ks = kss.get(0)
K ks2 = kss.get(1)
K ks3 = kss.get(3)
clazz.recommend(ks3.id, ks3.type, ks3.level)
clazz.recommend(ks2.id, ks2.type, ks2.level)
JSONObject response = clazz.recommend(ks.id, ks.type, ks.level)
def minis = []
int i = 0
def array = response.getJSONArray("data")
if (karray.size() < 2) return
array.each {
if (i++ < 2) {
JSONObject parse = JSON.parse(JSON.toJSONString(it))
int value = parse.getIntValue("minicourse_id")
clazz.collect(value, ks.type, ks.id)
}
}
mirro.getMiniCourseListV3(ks.id, ks.type, 0, ks.level)
def res = mirro.getMiniCourseListV3(ks.id, ks.type, 0, ks.level)
res.getJSONObject("data").getJSONArray("minicourse_list").each {
def ss = JSON.parseObject(JSON.toJSONString(it))
def mid = ss.getIntValue("minicourse_id")
clazz.unCollect(mid, ks.type, ks.id)
}
}
这里我增加了两个分支控制器:1、知识点列表为空,直接结束线程if (karray.size() < 3) return ThreadBase.stop()
;2、推荐列表长度小于 2,退出当前链路的单次执行,不影响接下来的执行,if (karray.size() < 2) return
。
两个例子有点牵强,各位看官将就看看,有了更多的实践,我会及时写出来。
在链路测试中支路的问题中,还有一个同步结束测试的问题,因为一旦存在支路,势必会产生链路执行时间的差异,在固定请求次数的模型中,等待所有线程正常运行结束再去统计测试结果,误差会变大,所以要在最快运行的线程结束之后,就要停掉整个测试,统计数据。
具体实现方法如下:
@Override
protected void after() {
super.after()
ThreadBase.stop()
}
重写after()
方法,在最先完成的线程执行结束,即刻执行stop()
方法,修改表示符号,结束所有线程。