序
在做压测的过程中,发现Jmeter的Arrivals Thread Group(后文以ATG指代)这个线程组会有一个问题,就是在完成测试目标以后,Running Thread不会降为0,测试计划不会正常终止。网上搜索一通,居然都没有搜到谁提出这个问题。还好这个项目是开源的,我们都是程序员,有代码就不怕解决不了。
ATG简单说明
ATG是以QPS为目标的压测方法,在指定了目标QPS以后,ATG会根据请求的响应时间,自动调整线程数去尽量维持压测期间的QPS,这种行为就非常类似我们服务端到服务端的调用行为。另外提一句,Open Model Thread Group也可以达到类似的效果,但是本人在使用OMTG的过程中发现解决不了长连接的问题,所以最终还是选用ATG。
修复过程
ATG的源代码在jmeter-plugin这个项目中,主要的类是ArrivalsThreadGroup,阅读代码会发现此类有一个poolThreads的成员,是Set类型。这很好理解,因为ATG要不断的调整活跃的线程数,设计一个线程池来避免频繁的创建销毁线程是很必要的。同时ArrivalsThreadGroup的父类中还定义了一个threads的成员,这个成员保存的就是当前活跃的线程。
在压测过程中,当ATG发现压测目标响应慢了,需要更多的活跃线程,就会尝试从poolThreads取出一个线程放入threads,反之亦然。一个线程不应该同时存在在poolThreads和threads中。
...此处省去的调试过程...
最终我们会留意到如下两个方法。
// 当前不需要这么多活跃线程,把活跃线程放入poolThreads
public boolean movedToPool(DynamicThread thread) {
threads.remove(thread);
...省略代码...
poolThreads.add(thread);
...省略代码...
synchronized (thread) {
try {
thread.wait();
} catch (InterruptedException e) {
log.debug("Interrupted", e);
}
}
}
// 需要更多活跃线程,把活跃线程加入threads
public synchronized boolean releasedPoolThread() {
...省略代码...
poolThreads.remove(thread);
threads.add(thread);
...省略代码...
synchronized (thread) {
thread.notify();
}
}
看到这个代码问题就很明显了,这样针对poolThreads和threads的操作在多线程环境下就有概率发生不一致的问题,导致一个线程同时存在在poolThreads和threads中。这样会导致活跃线程无法被成功释放,从而导致测试计划无法停止。修复方法也很粗暴,将针对poolThreads和threads的操作也放入到同步代码中就好了。修改代码如下
// 当前不需要这么多活跃线程,把活跃线程放入poolThreads
public boolean movedToPool(DynamicThread thread) {
synchronized (thread) {
threads.remove(thread);
...省略代码...
poolThreads.add(thread);
...省略代码...
try {
thread.wait();
} catch (InterruptedException e) {
log.debug("Interrupted", e);
}
}
}
// 需要更多活跃线程,把活跃线程加入threads
public synchronized boolean releasedPoolThread() {
synchronized (thread) {
...省略代码...
poolThreads.remove(thread);
threads.add(thread);
...省略代码...
thread.notify();
}
}
重新构建替换原来的jmeter-plugins-casutg-2.10.jar,问题消失。