经过一阵子的断断续续的测试,DCS_FunTester 分布式压测框架更新(一)完毕之后又增加了一些更新。
在分布式性能测试框架用例方案设想(三)、基于 docker 的分布式性能测试框架功能验证(三)中,我提到了方案三:基于Groovy
脚本执行的测试用例,这次更新将支持执行Groovy
测试用例。目前除了访问验证以外,但是对于脚本内容尚未过滤。
下面是master
节点实现方法:
@Override
int runScript(GroovyScript script) {
def mark = SourceCode.getMark()
def num = script.getMark()
def hosts = NodeData.getRunHost(num)
try {
hosts.each {
script.setMark(mark)
def re = MasterManager.runRequest(it, script)
if (!re) FailException.fail()
NodeData.addTask(it, mark)
}
} catch (FailException e) {
hosts.each { f -> MasterManager.stop(f) }
FailException.fail("多节点执行失败!")
}
mark
}
下面是slave
节点的实现方法:
@Override
public void runScript(GroovyScript script) {
ExecuteGroovy.executeScript(script.getScript());
}
这里没有传值,留个参数params
以后可以用来做脚本化参数配置。
增加了master
节点之后也就没有slave
节点的直接访问。
注册机制我自己写了一个简单的实现,放在一个类里面。
package com.funtester.master.common.basedata;
import com.funtester.base.bean.PerformanceResultBean;
import com.funtester.base.exception.FailException;
import com.funtester.frame.SourceCode;
import com.funtester.master.common.bean.manager.RunInfoBean;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
public class NodeData {
/**
* 节点状态
*/
public static ConcurrentHashMap<String, Boolean> status = new ConcurrentHashMap<>();
/**
* 节点的运行信息,通过progress获取
*/
public static ConcurrentHashMap<String, String> runInfos = new ConcurrentHashMap<>();
/**
* 节点的运行结果
*/
public static ConcurrentHashMap<Integer, List<PerformanceResultBean>> results = new ConcurrentHashMap<>();
/**
* 节点更新时间
*/
public static ConcurrentHashMap<String, Integer> time = new ConcurrentHashMap<>();
/**
* 节点运行的任务id
*/
public static ConcurrentHashMap<String, Integer> tasks = new ConcurrentHashMap<>();
public static void register(String host, boolean s) {
synchronized (status) {
status.put(host, s);
mark(host);
}
}
/**
* 可用节点
*
* @return
*/
public static List<String> available() {
synchronized (status) {
List<String> availables = new ArrayList<>();
status.forEach((k, v) -> {
if (v) availables.add(k);
});
return availables;
}
}
/**
* 标记节点时间
*
* @param host
*/
private static void mark(String host) {
time.put(host, SourceCode.getMark());
}
/**
* 检查,删除过期节点和过期数据,提供定时任务执行
*/
public static void check() {
int timeStamp = SourceCode.getMark();
List<String> hkeys = new ArrayList<>();
synchronized (status) {
time.forEach((k, v) -> {
if (timeStamp - v > 12) {
hkeys.add(k);
}
});
hkeys.forEach(f -> status.remove(f));
}
synchronized (runInfos) {
hkeys.forEach(f -> runInfos.remove(f));
}
synchronized (tasks) {
hkeys.forEach(f -> tasks.remove(f));
tasks.forEach((k, v) -> {
if (timeStamp - v > 60 * 30) tasks.put(k, 0);
});
}
synchronized (results) {
List<Integer> tkeys = new ArrayList<>();
results.forEach((k, v) -> {
if (k - timeStamp > 3_3600) {
tkeys.add(k);
}
});
tkeys.forEach(f -> results.remove(f));
}
}
/**
* 添加运行信息
*
* @param bean
*/
public static void addRunInfo(RunInfoBean bean) {
synchronized (runInfos) {
runInfos.put(bean.getHost(), bean.getRuninfo());
}
}
/**
* 获取描述的的用例任务运行信息
*
* @param desc 任务描述信息
* @return
*/
public static List<String> getRunInfo(String desc) {
synchronized (runInfos) {
ArrayList<String> infos = new ArrayList<>();
runInfos.forEach((k, v) -> {
if (v.contains(desc)) {
infos.add(v);
}
});
return infos;
}
}
/**
* 添加运行信息
*
* @param bean
*/
public static void addResult(int mark, PerformanceResultBean bean) {
synchronized (results) {
results.computeIfAbsent(mark, f -> new ArrayList<PerformanceResultBean>());
results.get(mark).add(bean);
}
}
/**
* 添加节点运行任务id
* @param host
* @param mark
*/
public static void addTask(String host, Integer mark) {
synchronized (tasks) {
if (status.get(host) != null && status.get(host) == false) {
tasks.put(host, mark);
}
}
}
public static List<String> getRunHost(int num) {
synchronized (status) {
List<String> available = available();
if (num < 1 || num > available.size())
FailException.fail("没有足够节点执行任务");
List<String> nods = new ArrayList<>();
for (int i = 0; i < num; i++) {
String random = SourceCode.random(available);
status.put(random, false);
nods.add(random);
}
return nods;
}
}
}
这里写的有点复杂,未来计划写入Redis
或者借助其他的成熟组件完成。本来想把节点信息封装成一个对象的形式,后来想想还是比较麻烦,如果分开处理会比较容易。
统一由master
节点分配任务运行用例,自然要取消slave
节点的访问权限,但是现在还有一部分接口暴露出来,swagger
文档中没有表名。其实刷新master节点信息
和重新注册节点
两个功能留作子节点出错时候使用。
之前的功能全然写成了一个静态方法,提取了service
接口,主要方法如下:
package com.funtester.master.service
import com.funtester.slave.common.bean.run.GroovyScript
import com.funtester.slave.common.bean.run.HttpRequest
import com.funtester.slave.common.bean.run.HttpRequests
import com.funtester.slave.common.bean.run.LocalMethod
import com.funtester.slave.common.bean.run.ManyRequest
interface IRunService {
public int runRequest(HttpRequest request)
public int runRequests(HttpRequests request)
public int runMethod(LocalMethod method)
public int runScript(GroovyScript script)
}
其中每个对象都有一个mark
属性,对于master
节点来说,就是执行的节点数,对于slave
节点来说,就是执行任务的标记。
这里分享一下思路:
master
节点slave
节点,首先会请求master
(配置或者接口设置),获取本机IP
slave
节点通过定时任务将状态同步到master
节点。没有使用Socket
接口,总觉得麻烦。
Have Fun ~ Tester !
点击阅读阅文,查看 FunTester 历史原创集合