Getting Started with the XXL‑Job Distributed Task Scheduling Framework in Java
This article provides a comprehensive step‑by‑step guide on installing, configuring, and using the open‑source XXL‑Job distributed scheduling framework—including server and executor deployment, task creation via annotations, API and sharding, execution, logging, and an in‑depth look at its Netty‑based communication design.
Task scheduling is a common component in Java projects, with Spring's Quartz being the most familiar, while distributed solutions like Elastic‑Job and Azkaban are built on top of Quartz. This article introduces XXL‑Job, a lightweight open‑source distributed scheduling framework that consists of a management console and executor nodes.
Project Introduction
XXL‑Job offers easy learning and powerful features. The management console handles task configuration and logs, while executors only need connection settings to run business logic.
1. Server Deployment
Clone the project from https://github.com/xuxueli/xxl-job , execute the SQL script doc/db/table_xxl_job.sql to create eight tables, and copy the xxl-job-admin files into a new Spring Boot project. Update application.properties with the database connection and adjust the xxl-job-core version to 2.2.0. Modify logback.xml for log output, then start the application and access http://localhost:8080/xxl-job-admin/ (default admin/admin123456).
2. Executor Configuration
Create a new module, add the xxl-job-core dependency, and modify logback.xml . Provide two configuration files ( application-9998.properties and application-9999.properties ) to simulate two executor instances with different ports.
Define a Java config class that creates an XxlJobSpringExecutor bean using the properties.
3. Task Development
Three common task creation methods are demonstrated:
Annotation‑based tasks: add an annotation on a method.
API‑based tasks: configure via the console.
Sharding/Broadcast tasks: execute the same job on multiple executors.
4. Task Execution
Examples show single task execution with a round‑robin routing strategy, sub‑task execution, and sharding broadcast execution, including screenshots of the console and logs.
5. Task Logging
Logs are essential for tracing execution history, viewing parent‑child relationships, and debugging via the XxlJobLogger output.
Communication Layer
XXL‑Job uses Netty HTTP for communication (though Mina, Jetty, and Netty TCP are also supported). The design employs dynamic proxy patterns to hide communication details, full asynchronous processing with a LinkedBlockingQueue , and a wrapper that makes asynchronous calls appear synchronous.
Key code snippets:
public static ReturnT
runExecutor(TriggerParam triggerParam, String address) {
ReturnT
runResult = null;
try {
ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
// asynchronous handling, finally get result synchronously
runResult = executorBiz.run(triggerParam);
} catch (Exception e) {
logger.error(">>>>>>>>>>> xxl-job trigger error, please check if the executor[{}] is running.", address, e);
runResult = new ReturnT
(ReturnT.FAIL_CODE, ThrowableUtil.toString(e));
}
StringBuffer runResultSB = new StringBuffer(I18nUtil.getString("jobconf_trigger_run") + ":");
runResultSB.append("
address:").append(address);
runResultSB.append("
code:").append(runResult.getCode());
runResultSB.append("
msg:").append(runResult.getMsg());
runResult.setMsg(runResultSB.toString());
return runResult;
} // Proxy asynchronous invoke
if (CallType.SYNC == callType) {
XxlRpcFutureResponse futureResponse = new XxlRpcFutureResponse(invokerFactory, xxlRpcRequest, null);
try {
client.asyncSend(finalAddress, xxlRpcRequest);
XxlRpcResponse xxlRpcResponse = futureResponse.get(timeout, TimeUnit.MILLISECONDS);
if (xxlRpcResponse.getErrorMsg() != null) {
throw new XxlRpcException(xxlRpcResponse.getErrorMsg());
}
return xxlRpcResponse.getResult();
} catch (Exception e) {
logger.info(">>>>>>>>>>> xxl-rpc, invoke error, address:{}, XxlRpcRequest{}", finalAddress, xxlRpcRequest);
throw (e instanceof XxlRpcException) ? e : new XxlRpcException(e);
} finally {
futureResponse.removeInvokerFuture();
}
} public void setResponse(XxlRpcResponse response) {
this.response = response;
synchronized (lock) {
done = true;
lock.notifyAll();
}
}
@Override
public XxlRpcResponse get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
if (!done) {
synchronized (lock) {
try {
if (timeout < 0) {
lock.wait();
} else {
long timeoutMillis = (TimeUnit.MILLISECONDS == unit) ? timeout : TimeUnit.MILLISECONDS.convert(timeout, unit);
lock.wait(timeoutMillis);
}
} catch (InterruptedException e) {
throw e;
}
}
}
if (!done) {
throw new XxlRpcException("xxl-rpc, request timeout at:" + System.currentTimeMillis() + ", request:" + request.toString());
}
return response;
}The framework generates a unique request ID for each remote call, which is used to locate the corresponding XxlRpcFutureResponse and wake the waiting thread.
Overall, the article walks readers through the complete lifecycle of using XXL‑Job—from environment setup to advanced design insights—making it a valuable resource for backend developers interested in distributed task scheduling.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
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.