之前写过一个性能测试框架,只是针对单一的HTTP接口的测试,对于业务接口和非HTTP接口还无非适配,刚好前端时间工作中用到了,就更新了自己的测试框架,这次不再以请求为基础,而是以方法为基础,这样就可以避免了单一性,有一个base类,然后其他的各种单一性请求在单独写一个适配类就好了,如果只是临时用,直接重新实现base即可。下面分享:
package com.fun.frame.thead;import com.fun.frame.SourceCode;import com.fun.frame.excute.Concurrent;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.util.ArrayList;import java.util.List;import java.util.concurrent.CountDownLatch;import static com.fun.utils.Time.getTimeStamp;/*** 多线程任务基类,可单独使用*/public abstract class ThreadBase<T> extends SourceCode implements Runnable {private static final Logger logger = LoggerFactory.getLogger(ThreadBase.class);/*** 任务请求执行次数*/public int times;/*** 计数锁* <p>* 会在concurrent类里面根据线程数自动设定* </p>*/CountDownLatch countDownLatch;/*** 用于设置访问资源*/public T t;public ThreadBase(T t) {this();this.t = t;}public ThreadBase() {super();}/*** groovy无法直接访问t,所以写了这个方法** @return*/public String getT() {return t.toString();}public void run() {try {before();List<Long> t = new ArrayList<>();long ss = getTimeStamp();for (int i = 0; i < times; i++) {long s = getTimeStamp();doing();long e = getTimeStamp();t.add(e - s);}long ee = getTimeStamp();logger.info("执行次数:{},总耗时:{}", times, ee - ss);Concurrent.allTimes.addAll(t);} catch (Exception e) {logger.warn("执行任务失败!", e);} finally {after();if (countDownLatch != null)countDownLatch.countDown();}}/*** 运行待测方法的之前的准备*/protected abstract void before();/*** 待测方法** @throws Exception*/protected abstract void doing() throws Exception;/*** 运行待测方法后的处理*/protected abstract void after();public void setCountDownLatch(CountDownLatch countDownLatch) {this.countDownLatch = countDownLatch;}public void setTimes(int times) {this.times = times;}}
下面是几个实现过的基础类:
package com.fun.frame.thead;import com.fun.httpclient.ClientManage;import com.fun.httpclient.FanLibrary;import com.fun.httpclient.GCThread;import org.apache.http.HttpStatus;import org.apache.http.client.methods.CloseableHttpResponse;import org.apache.http.client.methods.HttpRequestBase;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.io.IOException;/*** http请求多线程类*/public class RequestThread extends ThreadBase {static Logger logger = LoggerFactory.getLogger(RequestThread.class);/*** 请求*/public HttpRequestBase request;/*** 单请求多线程多次任务构造方法** @param request 被执行的请求* @param times 每个线程运行的次数*/public RequestThread(HttpRequestBase request, int times) {this.request = request;this.times = times;}public void before() {request.setConfig(FanLibrary.requestConfig);GCThread.starts();}protected void doing() throws Exception {getResponse(request);}protected void after() {GCThread.stop();}/*** 多次执行某个请求,但是不记录日志,记录方法用 loglong* <p>此方法只适应与单个请求的重复请求,对于有业务联系的请求暂时不能适配</p>** @param request 请求* @throws IOException*/void getResponse(HttpRequestBase request) throws IOException {CloseableHttpResponse response = ClientManage.httpsClient.execute(request);String content = FanLibrary.getContent(response);if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK)logger.warn("响应状态码:{},响应内容:{}", content, response.getStatusLine());if (response != null) response.close();}}
下面是数据库的:
package com.fun.frame.thead;import com.fun.interfaces.IMySqlBasic;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.sql.SQLException;/*** 数据库多线程类*/public class QuerySqlThread extends ThreadBase {private static Logger logger = LoggerFactory.getLogger(QuerySqlThread.class);String sql;IMySqlBasic base;public QuerySqlThread(IMySqlBasic base, String sql, int times) {this.times = times;this.sql = sql;this.base = base;}public void before() {base.getConnection();}protected void doing() throws SQLException {base.excuteQuerySql(sql);}protected void after() {base.mySqlOver();}}
下面是concurrent类:
package com.fun.frame.excute;import com.fun.bean.PerformanceResultBean;import com.fun.frame.Save;import com.fun.frame.SourceCode;import com.fun.frame.thead.ThreadBase;import com.fun.profile.Constant;import com.fun.utils.Time;import com.fun.utils.WriteRead;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.util.List;import java.util.Vector;import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class Concurrent {private static Logger logger = LoggerFactory.getLogger(Concurrent.class);/*** 线程任务*/public ThreadBase thread;public List<ThreadBase> threads;public int num;public static Vector<Long> allTimes = new Vector<>();ExecutorService executorService;CountDownLatch countDownLatch;/*** @param thread 线程任务* @param num 线程数*/public Concurrent(ThreadBase thread, int num) {this(num);this.thread = thread;}/*** @param threads 线程组*/public Concurrent(List<ThreadBase> threads) {this(threads.size());this.threads = threads;}public Concurrent(int num) {this.num = num;executorService = Executors.newFixedThreadPool(num);countDownLatch = new CountDownLatch(num);}/*** 执行多线程任务*/public PerformanceResultBean start() {long start = Time.getTimeStamp();for (int i = 0; i < num; i++) {ThreadBase thread = getThread(i);thread.setCountDownLatch(countDownLatch);executorService.execute(thread);}shutdownService(executorService, countDownLatch);long end = Time.getTimeStamp();logger.info("总计" + num + "个线程,共用时:" + Time.getTimeDiffer(start, end) + "秒!");return over();}private static void shutdownService(ExecutorService executorService, CountDownLatch countDownLatch) {try {countDownLatch.await();executorService.shutdown();} catch (InterruptedException e) {logger.warn("线程池关闭失败!", e);}}private PerformanceResultBean over() {Save.saveLongList(allTimes, num);return countQPS(num);}ThreadBase getThread(int i) {if (threads == null) return thread;return threads.get(i);}/*** 计算结果* <p>此结果仅供参考</p>** @param name 线程数*/public static PerformanceResultBean countQPS(int name) {List<String> strings = WriteRead.readTxtFileByLine(Constant.LONG_Path + name + Constant.FILE_TYPE_LOG);int size = strings.size();int sum = 0;for (int i = 0; i < size; i++) {int time = SourceCode.changeStringToInt(strings.get(i));sum += time;}double v = 1000.0 * size * name / sum;PerformanceResultBean performanceResultBean = new PerformanceResultBean(name, size, sum / size, v);performanceResultBean.print();return performanceResultBean;}}
redis实现类缺失,因为没有遇到需要单独实现的需求。
关于用代码还是用工具实现并发,我个人看法所有所长,单究其根本,必然是代码胜于工具,原因如下:门槛高,适应性强;贴近开发,利于调优。性能测试,并发只是开始,只有一个好的开始才能进行性能数据分析,性能参数调优。所以不必拘泥于到底使用哪个工具那种语言,据我经验来说:基本的测试需求都是能满足的,只是实现的代价不同。
groovy是一种基于JVM的动态语言,我觉得最大的优势有两点,第一:于java兼容性非常好,大部分时候吧groovy的文件后缀改成java直接可以用,反之亦然。java的绝大部分库,groovy都是可以直接拿来就用的。这还带来了另外一个有点,学习成本低,非常低,直接上手没问题,可以慢慢学习groovy不同于Java的语法;第二:编译器支持变得更好,现在用的intellij的ide,总体来说已经比较好的支持groovy语言了,写起代码来也是比较顺滑了,各种基于groovy的框架工具也比较溜,特别是Gradle构建工具,比Maven爽很多。----此段文字为了撑字数强加的,与内容无关。
点击阅读原文,有兴趣的童鞋可以一起交流