SDK压力测试实现方法(一)- 基于CountDownLatch和CyclicBarrier编写高并发测试工具类

背景

项目要求需要测试系统的查询功能的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

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值