Building a Distributed Load‑Testing Framework: Gradle Multi‑Module & Maven Tips
This article details the final updates to the DCS_FunTester distributed testing framework, covering Gradle multi‑module configuration, dependency handling, result aggregation, task dispatch, health‑check endpoints, and registration optimizations with practical code examples in Java and Maven.
After two rounds of updates, the core features of the DCS_FunTester framework are stable, and no further functional updates are planned.
Part 1 – Gradle Multi‑Module
The project originally used two separate modules named master and slave. To simplify management, they were merged into a single Gradle multi‑module project, revealing several pitfalls.
1. Adding a Dependency on Another Module
A working solution is to declare the dependency in build.gradle as:
dependencies {
implementation project(':slave')
}Typical tutorials often miss this configuration, and the correct template for multi‑module Gradle projects was discovered through trial and error.
2. Sub‑module Dependency Configuration
Attempts to configure sub‑module dependencies in the parent build.gradle using a subprojects block failed, likely due to misuse of the compile configuration or local JAR references.
3. settings.gradle
The essential settings.gradle content is:
rootProject.name = 'dcs_funtester'
include 'slave'
include 'master'Part 2 – Result Collection
Test results are aggregated on the master node. After a slave node finishes executing a test case, it synchronizes its results to the master node, which then handles storage, merging, and calculation.
Example of a request execution on a slave node:
@Async
@Override
public void runRequest(HttpRequest request) {
BaseRequest r = request.getRequest();
HttpRequestBase re = FunRequest.initFromJson(r.toJson()).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);
PerformanceResultBean resultBean = concurrent.start();
SlaveManager.updateResult(resultBean, request.getMark());
}
}The corresponding master controller endpoint receives the aggregated result:
@ApiOperation(value = "更新测试结果")
@ApiImplicitParam(name = "bean", value = "测试结果对象", dataTypeClass = PerformanceResultBean.class)
@PostMapping(value = "/upresult/{mark}")
public Result updateResult(@PathVariable(value = "mark") int mark, @RequestBody PerformanceResultBean bean) {
NodeData.addResult(mark, bean);
return Result.success();
}Result storage is kept in memory within the JVM for now, without persistence, as the design may evolve to use server‑side statistics.
Part 3 – Task Distribution
When the master node receives a task, it compares the required node count with available nodes. If sufficient, it dispatches the task to each node; on any failure, it rolls back already started nodes and reports failure.
@Override
int runRequest(HttpRequest request) {
def num = request.getMark();
def hosts = NodeData.getRunHost(num);
def mark = SourceCode.getMark();
request.setMark(mark);
try {
hosts.each {
def re = MasterManager.runRequest(it, request);
if (!re) FailException.fail();
NodeData.addTask(it, mark);
}
} catch (FailException e) {
hosts.each { f -> MasterManager.stop(f) };
FailException.fail("多节点执行失败!");
}
return mark;
}Part 4 – Health‑Check Interface
A temporary health‑check endpoint was added to slave nodes so that the master can periodically verify node liveness, reducing reliance on passive reports.
@ApiOperation(value = "节点状态,是否存活")
@GetMapping(value = "/alive")
public Result alive() {
return Result.success();
}The master side checks liveness via HTTP:
static boolean alive(String host) {
try {
String url = SlaveApi.ALIVE;
return isRight(getGetResponse(host, url, null));
} catch (Exception e) {
logger.warn("节点: {}探活失败!", host);
return false;
}
}Part 5 – Registration Optimization
To prevent a slave node that can reach the master but not vice‑versa from being registered, the registration process now includes a health‑check step.
@ApiOperation(value = "注册接口")
@PostMapping(value = "/register")
public Result register(@Valid @RequestBody RegisterBean bean) {
def url = bean.getUrl();
def stop = MasterManager.alive(url);
if (!stop) FailException.fail("注册失败!");
NodeData.register(url, false);
return Result.success();
}The overall development of DCS_FunTester is now complete, with basic functionality in place; future work will integrate a service‑discovery component such as Nacos for more robust node management.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
