在经过了一些人生的思考和积累,终于开始第一轮的更新,除了修复BUG以外,还进行不少功能性更新。另外本地部署已经完成了。有兴趣的可以自己多体验一下。分布式性能测试框架单节点内测
确定名字
确定项目名字DCS_FunTester
,DCS
全称Distributed Control System
,既分布式控制系统。后缀FunTester
。目前项目是slave
项目,至于master
项目还在思考。但是不影响slave
项目的多节点部署。slave
项目不依赖master
项目,具有独立部署提供服务能力,当然也具有多节点部署能力。只是需要调用HTTP
接口时候需要请求所有slave
节点的接口。
swagger支持
尝试了一些方式,觉得swagger
还是最优方案,接口文档地址:http://106.53.152.151:8000/swagger-ui/#/
,过段时间服务器到期,各位尝试本地部署时替换域名和端口即可。
本地部署
之前计划的本地部署,但是进度有点落后了,本周把代码推送到了gitee
上,可以随意下载部署。具体的部署文档还没写,因为内测阶段有些东西还在调整。大体的部署步骤如下:
构建FunTester
gitee地址https://gitee.com/fanapi/tester
,最新分支oker
,直接拉取最近代码即可。构建工具Gradle
,执行build
或者jar
即可,不需要本地Groovy SDK
。
构建DCS_FunTester
此处依赖FunTester
的jar
包,gradle.build
引用compile files('/Users/oker/Library/groovy-3.0.8/lib/funtester-1.0.jar')
,各位直接修改目录地址即可。gitee地址https://gitee.com/fanapi/dcs
,默认分支master
,拉取最近代码即可。构建工具Gradle
,执行build
或者jar
即可,不需要本地Groovy SDK
。
启动服务
目前还没使用docker
,直接用的Java -jar **** &
这样的命令行。
更换验证方式
由于之前的考虑不周,把密钥验证放在了接口参数中,导致后面的一系列问题。我已经换了一种方式:放在header
中进行验证。忽略GET
请求。这个功能仅限于公网内测服务的使用(由于云服务器到期,近期会下线),本地部署不需要。代码坐标org.funtester.dcs.common.wapper.WrappingFilter#doFilter
,内容如下:
String headerKey = req.getHeader(DcsConstant.HEADER_KEY); String method = requestWrapper.getMethod(); if (!method.equalsIgnoreCase("get") && (StringUtils.isEmpty(headerKey) || !headerKey.equalsIgnoreCase(DcsConstant.HEADER_VALUE))) { response.getOutputStream().write(Result.fail("验证失败!").toString().getBytes()); return; }
本地部署可以删除此段。
异步执行用例
来处来
之前测试接口是串行运行测试用例,在实际使用中会导致接口响应时间特别长,但是内测接口对于线程数和请求次数做了限制,所以基本也都控制在10s
以内,这次的更新会放开限制,采用异步运行测试用例的方式。
具体限制如下:
class HttpRequest extends AbstractBean implements Serializable {
private static final long serialVersionUID = 324324327948379L;
@NotNull JSONObject request
//压测模式 @Range(min = 1L, max = 2000L) Integer times
String mode; //线程数,这里默认固定线程模式 @Range(min = 1L, max = 100L) Integer thread
//软启动时间 Integer runup
//用例描述 @Length(min = 1, max = 100) String desc
}
去处去
异步执行思路比较简单,就是在处理中把com.funtester.frame.execute.Concurrent
对象组装完成之后,调用一个异步执行的方法,返回一个标记值即可。使用如下:
@PostMapping(value = "/post") public Result tests(@Valid @RequestBody HttpRequest request) { if (!ThreadBase.needAbort()) return Result.fail(); JSONObject r = request.getRequest(); HttpRequestBase re = FunRequest.initFromJson(r).getRequest(); Integer times = request.getTimes(); String mode = request.getMode(); Integer thread = request.getThread(); Integer runup = request.getRunup(); String desc = request.getDesc(); if (mode.equalsIgnoreCase("ftt")) { Constant.RUNUP_TIME = runup; RequestThreadTimes task = new RequestThreadTimes(re, times); Concurrent concurrent = new Concurrent(task, thread, desc); return Result.success(execute(concurrent)); } return Result.fail(); }
其中execute
方法内容如下:
/**
* 异步执行的用例的方法
*
* @param concurrent
* @return
*/ public int execute(Concurrent concurrent) { int mark = SourceCode.getMark(); new Thread(new Runnable() { @Override public void run() { PerformanceResultBean start = concurrent.start(); FunData.results.put(mark, start); } }).start(); return mark; }
因为目前设计的一个slave
节点同时只允许一个任务运行,所以这里暂不考虑线程安全的问题,认为mark
唯一。org.funtester.dcs.common.FunData#results
是一个ConcurrentHashMap<Integer, PerformanceResultBean>()
用例保存测试用例执行结果已提供接口访问。由于资源有限(缺钱),没有进行数据库存储和用户区分。直接放在这个map
里面了。用一个定时任务去定期清理里面的数据。
/**
* 定时删除存的过期数据
* @return
*/ @Scheduled(cron = "0 0 0/3 * * ?") def saveRequestBean() { def mark = SourceCode.getMark() List<Integer> dels = [] FunData.results.keySet().each { if (mark - it > 7200) { dels << it } } dels.each {FunData.results.remove(it)} logger.info("定时任务执行完毕! 时间:{}", Time.getDate()) }
同样的原因,这里也不考虑线程安全的问题。
获取测试结果接口:
@GetMapping(value = "/get/{id}") public Result getRunResult(@PathVariable(name = "id") int id) { PerformanceResultBean performanceResultBean = FunData.results.get(id); return Result.success(performanceResultBean); }
多请求支持
本次更新增加的多请求的线程压测支持。思路如下,在新建测试任务时候,将请求list
存一下,然后再并发的时候依次去拿里面的请求发送出去,也可以采用随机取请求,或者用i++
这种方式去取,都是可以的。这里不要求精度的话,三种方式都可以。至于流量编排(各个请求按比例发送)的功能,下期尽量添加上。
实现代码如下:
@PostMapping(value = "/post2") public Result tests2(@Valid @RequestBody HttpRequest2 request) { if (!ThreadBase.needAbort()) return Result.fail(); JSONArray requests = request.getRequests(); request.print(); List<HttpRequestBase> res = new ArrayList<>(); requests.forEach(f -> { res.add(FunRequest.initFromString(JSON.toJSONString(f)).getRequest()); }); Integer times = request.getTimes(); String mode = request.getMode(); Integer thread = request.getThread(); Integer runup = request.getRunup(); String desc = request.getDesc(); if (mode.equalsIgnoreCase("ftt")) { Constant.RUNUP_TIME = runup; ListRequestMode task = new ListRequestMode(res, times); Concurrent concurrent = new Concurrent(task, thread, desc); return Result.success(execute(concurrent)); } return Result.success(); }
自定义类org.funtester.dcs.template.ListRequestMode
代码如下:
package org.funtester.dcs.template
import com.funtester.base.constaint.FixedThreadimport com.funtester.base.constaint.ThreadBaseimport com.funtester.httpclient.FunLibraryimport org.apache.http.client.methods.HttpRequestBase
class ListRequestMode<List> extends FixedThread {
ListRequestMode(List<HttpRequestBase> res, int times) { super(res, times, true) }
@Override protected void doing() throws Exception { // FunLibrary.executeSimlple(res.get(index.getAndDecrement() % res.size())) FunLibrary.executeSimlple(random(f)) }
@Override ThreadBase clone() { return new ListRequestMode(f, limit); }}
获取测试进度
这里有个前提就是单个节点只能同时运行一个用例,所以我在com.funtester.base.constaint.ThreadBase
中增加了一个属性com.funtester.base.constaint.ThreadBase#progress
用于外部访问运行状况。
HTTP访问接口:
@GetMapping(value = "/progress") public Result progeress() { String s = ThreadBase.progress == null ? "没有运行任务" : ThreadBase.progress.runInfo; return Result.success(s); }
com.funtester.frame.execute.Progress#runInfo
格式如下:runInfo = String.format("%s进度:%s %s ,当前QPS: %d", taskDesc, getManyString(ONE, (int) (pro * LENGTH)), getPercent(pro * 100), getQPS());
。