之前写过一个性能测试框架,只是针对单一的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(); } @Override 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; } @Override public void before() { request.setConfig(FanLibrary.requestConfig); GCThread.starts(); } @Override protected void doing() throws Exception { getResponse(request); } @Override 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; } @Override public void before() { base.getConnection(); } @Override protected void doing() throws SQLException { base.excuteQuerySql(sql); } @Override 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爽很多。----此段文字为了撑字数强加的,与内容无关。