Backend Development 14 min read

XXL-Job Distributed Task Scheduling Framework: Project Overview and Hands‑On Guide

This article introduces the open‑source XXL-Job distributed task scheduling framework, explains its architecture and communication design, and provides a step‑by‑step hands‑on tutorial covering server deployment, executor configuration, task development (annotation, API, sharding), execution, and log inspection, with code examples and screenshots.

Top Architect
Top Architect
Top Architect
XXL-Job Distributed Task Scheduling Framework: Project Overview and Hands‑On Guide

XXL‑Job is a lightweight open‑source distributed task scheduling framework written in Java. It consists of an admin (management) console and one or more executor (worker) nodes. The admin handles task configuration and log viewing, while executors run the actual job logic.

Project Introduction

XXL‑Job can be downloaded from https://github.com/xuxueli/xxl-job . After cloning, import the SQL script doc/db/table_xxl_job.sql into MySQL to create the required tables.

Server Deployment

Create a new Spring Boot project and copy the xxl-job-admin directory and its pom.xml into the project, or use the original project directly. Modify application.properties to point to your MySQL instance.

Update the xxl-job-core dependency version to 2.2.0 and adjust logback.xml for log output. Start the application and access the admin console at http://localhost:8080/xxl-job-admin/ (default credentials admin/123456).

Executor Configuration

Create a separate module for the executor, add the same logback.xml and the xxl-job-core dependency. Provide two configuration files (e.g., application-9998.properties and application-9999.properties ) with different server.port and xxl.job.executor.port values to simulate two executor instances.

Define a Spring configuration class that creates a XxlJobSpringExecutor bean, setting properties such as appname , address , ip , port , and logPath . Start both executors; the admin console will show both nodes registered.

Task Development

Three common ways to create jobs are demonstrated:

Annotation‑based job : add @XxlJob("myJob") on a method.

API‑based job : invoke the admin API to register a job programmatically.

Sharding‑broadcast job : configure a job with multiple shards that are executed on every executor.

Task Execution

In the admin UI, create a job with a cron expression and select the handler name (e.g., myJobAnnotationHandler ). Trigger the job manually to see real‑time execution and log output. The framework supports round‑robin routing, child jobs, and sharding broadcast.

Log Inspection

Job logs are stored in the database and can be viewed from the admin console, showing execution timestamps, parameters, and any exceptions printed by XxlJobLogger .

Communication Design

XXL‑Job uses Netty HTTP for communication between admin and executors (other transports like Mina or Netty TCP are optional but not used in the default code). The design employs dynamic proxies ( ExecutorBiz and AdminBiz ) to hide network details, fully asynchronous processing with a LinkedBlockingQueue , and a future‑response mechanism to turn asynchronous calls into synchronous results.

Key Code Snippets

public static ReturnT
runExecutor(TriggerParam triggerParam, String address) {
    ReturnT
runResult = null;
    try {
        ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
        // asynchronous handling, finally get synchronous result
        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;
}
if (CallType.SYNC == callType) {
    // future‑response set
    XxlRpcFutureResponse futureResponse = new XxlRpcFutureResponse(invokerFactory, xxlRpcRequest, null);
    try {
        // do invoke
        client.asyncSend(finalAddress, xxlRpcRequest);
        // future get
        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 {
        // future‑response remove
        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) {
                    // thread block
                    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;
}

Each remote call carries a UUID request ID, which is used to locate the corresponding XxlRpcFutureResponse and wake the waiting thread.

Overall, the article provides a complete end‑to‑end guide for setting up XXL‑Job, configuring multiple executors, writing jobs in different styles, and understanding the underlying asynchronous communication mechanism.

Javadistributed schedulingbackend developmentSpring Bootxxl-jobTask Execution
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.