How xxl-job Leverages Netty and Dynamic Proxies for High‑Throughput Asynchronous RPC

This article explains how xxl-job employs Netty HTTP, dynamic proxy patterns, and fully asynchronous processing to achieve efficient remote procedure calls, detailing the communication layer, overall workflow, key design points, and the underlying Java code that synchronously retrieves results from asynchronous operations.

Architect
Architect
Architect
How xxl-job Leverages Netty and Dynamic Proxies for High‑Throughput Asynchronous RPC

Communication Layer Overview

xxl-job uses Netty HTTP for communication, although it also supports Mina, Jetty, and Netty TCP; the implementation hard‑codes Netty HTTP.

Overall Process

The complete processing flow is illustrated with an activity diagram.

Activity Diagram
Activity Diagram

Key Design Highlights

Dynamic Proxy Pattern – Interfaces ExecutorBiz and AdminBiz encapsulate operations; XxlRpcReferenceBean creates a proxy whose getObject() performs remote communication.

Full Asynchronous Processing – The executor receives messages, deserializes them, and stores task information in a LinkedBlockingQueue. Worker threads consume tasks from the queue, execute them, and place results in a callback queue, reducing Netty worker thread time and increasing throughput.

Asynchronous Wrapper – The code appears synchronous while internally using asynchronous handling.

Scheduler Trigger Code

public static ReturnT<String> runExecutor(TriggerParam triggerParam, String address){
    ReturnT<String> runResult = null;
    try {
        ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
        // asynchronous processing, final 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("<br>address:").append(address);
    runResultSB.append("<br>code:").append(runResult.getCode());
    runResultSB.append("<br>msg:").append(runResult.getMsg());
    runResult.setMsg(runResultSB.toString());
    return runResult;
}

ExecutorBiz.run is invoked via the dynamic proxy; the scheduler thread blocks until the executor returns the result.

Dynamic Proxy Implementation

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();
    }
}

Future Response Handling

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;
}

Each remote call carries a UUID requestId, which acts as a key to locate the corresponding XxlRpcFutureResponse and wake the blocked thread.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaRPCAsynchronousNettyXXL-JOBDynamic Proxy
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

0 followers
Reader feedback

How this landed with the community

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.