背景
项目要求需要测试系统的查询功能的SDK的性能,考虑2种方法:
1.利用CountDownLatch和CyclicBarrier,自己编写高并发测试工具类,计算平均响应时间、QPS、错误率等;
2.利用Jmeter的Java Request来进行测试(推荐)
本文先介绍第一种方法,先解释设计思路,再给出源码实例;
这里的源码不能直接运行,需要具体替换成对应项目的待测方法才可以,下面会具体介绍
设计思路
1. 首先确定是否使用线程池管理线程,这里采用可缓存线程池newCachedThreadPool()进行线程的管理
参考文章:
[1] https://zhuanlan.zhihu.com/p/52981607
[2] https://www.cnblogs.com/zhujiabin/p/5404771.html
2. 重载run方法,将需要测试的方法放入run()中,让每个线程去执行,具体实现:
public class IdentifierThreads implements Runnable {
//每个线程都将执行run()函数,所以将待测方法放入其中
@Override
public synchronized void run() {
//TODO: set methods under test here
lookup(identifier); //待测方法
}
}
由于当多个线程访问同一个类、方法、变量时会造成线程安全问题,我测试的时候多个线程都会使用访问静态变量响应时间,所以使用了synchronized,但未对性能进行验证
参考文章:
[1] https://www.cnblogs.com/dolphin0520/p/3923737.html
[2] https://www.cnblogs.com/yw-ah/p/5856802.html
3. 初始化栅栏CyclicBarrier数,每个线程开始执行前,都执行CyclicBarrier.await(),即等待启动的线程数达到设定的栅栏数再放开栅栏,所有线程一起执行查询操作,具体实现:
public class IdentifierThreads implements Runnable {
@Override
public synchronized void run() {
barrier.await();//barrier为CyclicBarrier的实例
//放入需要测试的方法
lookup(identifier); //待测方法
}
}
4. 每个线程任务执行完,则执行CountDownLatch.countDown()操作,将CountDownLatch的计数减1,配合主函数内CountDownLatch.await(),该方法为等待CountDownLatch的计数为0后,才执行之后的操作,从而实现了当所有线程结束后,再开始计算总运行时间、QPS、错误率等参数,则具体实现:
public class IdentifierThreads implements Runnable {
@Override
public synchronized void run() {
barrier.await();//barrier为CyclicBarrier的实例
//放入需要测试的方法
lookup(identifier); //待测方法
//执行countDown()操作,将CountDownLatch的计数减1
countDownLatch.countDown();
}
}
5. 计算总运行时间,平均响应时间,QPS和错误率
平均响应时间算的是每个线程的执行时间之和/总请求数,而非程序运行的总时间/总请求数,因为所有请求都是并行运行的,所以如果按后者计算,算出来的时间并不准确。
QPS = 总运行时间/总请求数
错误率 就是发生错误的请求数,可以利用try/catch进行捕捉计算
源码示例
主要分两个文件,SdkConcurrencyTest.java是主函数,用来初始化CyclicBarrier,CountDownLatch,线程池,及计算;IdentifierThreads.java用来重写run函数
SdkConcurrencyTest.java
import java.text.DecimalFormat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SdkConcurrencyTest {
//读取并发线程数
public static int threadNum = 1;
//读取每个线程循环查询的次数
public static int loop = 1;
//初始化栅栏数,与barrier.await()联合实现设置线程开始条件为:当线程数达到threadNum后,再并发执行函数run();
public static CyclicBarrier barrier = new CyclicBarrier(threadNum);
//初始化计数器,与countDownLatch.await()联合实现线程结束条件,从而实现运行完所有线程之后才执行后面的操作
public static CountDownLatch countDownLatch = new CountDownLatch(threadNum);
public static void main(String[] args) throws InterruptedException {
//初始化线程池
ExecutorService pool = Executors.newCachedThreadPool();
//初始化开始时间
long beginTime=System.currentTimeMillis();
//循环初始化线程
for(int i = 0; i < threadNum; i++){
IdentifierThreads target = new IdentifierThreads(i,loop);
pool.execute(target);
}
//关闭线程池
pool.shutdown();
//这一步是为了将全部线程任务执行完以后,开始执行后面的任务(计算时间,数量)
countDownLatch.await();
//计算总耗时、平均响应时间、QPS和错误率
//计算总耗时
long totalTime = System.currentTimeMillis()-beginTime;
//等待1s
Thread.sleep(1000);
//设置取小数点后2位
DecimalFormat df=new DecimalFormat(".00");
System.out.println("总共发送"+threadNum*loop + "次请求"+
"\n"+"---------------------"+"\n"+
"总耗时为: "+totalTime+
"\n"+"---------------------"+"\n"+
"平均响应时间为"+ df.format((double)IdentifierThreads.respTime/(threadNum*loop))+"ms"+
"\n"+"---------------------"+"\n"+
"QPS为"+df.format((double)threadNum*loop*1000/totalTime)+"/s"+
"\n"+"---------------------"+"\n"+
"错误数为"+ IdentifierThreads.error);
}
}
IdentifierThreads.java
import cn.ac.caict.iiiiot.id.client.core.BaseResponse;
import cn.ac.caict.iiiiot.id.client.core.IdentifierException;
import cn.ac.caict.iiiiot.id.client.data.IdentifierValue;
import cn.ac.caict.iiiiot.id.client.data.MsgSettings;
import com.handle.handleVue.core.HndValue;
import com.handle.handleVue.core.ResolveIdentifierResponse;
import com.handle.handleVue.service.IChannelManageService;
import com.handle.handleVue.service.IIDManageServiceChannel;
import com.handle.handleVue.service.impl.ChannelManageServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.BrokenBarrierException;
public class IdentifierThreads implements Runnable {
//待测方法需要用到的参数identity
public static final String identity = "20.500.12410/560103/fertilizer.urea.c";
private int threadNum;
private int loop;
private long start;
public static long respTime=0;
public static int error=0;
public IdentifierThreads(int threadNum, int loop) {
this.threadNum=threadNum;
this.loop=loop;
}
@Override
public synchronized void run() {
try {
//放置栅栏
SdkConcurrencyTest.barrier.await();
//初始化线程开始时间
start = System.currentTimeMillis();
//开始线程内循环查询,如不需要,可以将该循环去掉
for (int i = 0; i < loop; i++) {
try {
testLookup(Integer.toString(threadNum*loop+i));
} catch (Exception e) {
e.printStackTrace();
error++;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
} finally {
//计算每个线程响应时间的总和
respTime = respTime + (System.currentTimeMillis() - start);
//计数器减一
SdkConcurrencyTest.countDownLatch.countDown();
}
}
//这是一个SDK待测方法示例
private static void testLookup(String cnt) throws IdentifierException {
String identifier = identity+cnt;
System.out.println("identifier is: "+identifier)
}
}
参考文章:
[1] https://www.cnblogs.com/yjxs/p/9911903.html
[2] https://my.oschina.net/u/3446892/blog/1649026
[3] https://www.cnblogs.com/xxj0316/p/9766974.html