1. 背景与架构目标

在微服务架构中,我们希望实现:

  • 调度中心(Admin):统一管理定时任务。

  • 业务执行器(Java):动态注册到 Nacos,并由 Admin 驱动。

  • 跨语言协同(Python):由 Java 执行器通过 HTTP 指令驱动 AI 推理任务。

核心技术栈:Spring Boot 3.2.x, JDK 21, Spring Cloud Alibaba 2022, XXL-JOB 2.4.0。


2. 核心执行流程图

理解了数据怎么走,问题就解决了一半。

  1. 注册阶段:执行器启动 -> 询问 Nacos 获取 Admin IP -> 调用 /api/registry 报到。

  2. 调度阶段:Admin 触发任务 -> 查找注册表获取执行器 IP -> 调用执行器 9999 端口。

  3. 执行阶段:执行器收到指令 -> 反射调用 @XxlJob 方法 -> 异步返回结果。


3. 遇到的三大“巨坑”及解决方案

坑一:JDK 21 环境下 HttpClient 报错 timeout invalid

  • 现象:执行器启动后,注册线程不断报错 RuntimeException: http client invoke fail, timeout invalid

  • 真相:XXL-JOB 2.4.0 默认的 xxl-tool-http 在 JDK 21 下无法正确处理请求超时和代理对象,且报错信息被混淆。

  • 解决手动接管注册逻辑。利用 Spring 的 @ScheduledRestTemplate 手写一个注册器,避开脆弱的原生 HttpClient。

package com.manga.mangabusinessserver.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Component
@Slf4j
public class XxlJobManualRegistry {

    @Autowired
    private DiscoveryClient discoveryClient;

    // 使用 Spring 默认的 RestTemplate,它在 JDK 21 下非常稳
    private final RestTemplate restTemplate = new RestTemplate();

    @Value("${xxl.job.executor.appname}")
    private String appname;

    @Value("${xxl.job.accessToken}")
    private String accessToken;

    // 每 30 秒向 Admin 报到一次
    @Scheduled(fixedRate = 30000)
    public void manualRegistry() {
        try {
            // 1. 获取 Admin 地址
            List<ServiceInstance> instances = discoveryClient.getInstances("manga-admin-server");
            if (instances.isEmpty()) return;

            String adminUrl = "http://" + instances.get(0).getHost() + ":" + instances.get(0).getPort() + "/xxl-job-admin/api/registry";

            // 2. 构造注册参数
            Map<String, Object> param = new HashMap<>();
            param.put("registryGroup", "EXECUTOR");
            param.put("registryKey", appname);
            param.put("registryValue", "http://"+ instances.get(0).getHost() +":9999/");

            // 3. 设置 Header
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            if (accessToken != null && !accessToken.isEmpty()) {
                headers.set("XXL-JOB-ACCESS-TOKEN", accessToken);
            }

            HttpEntity<Map<String, Object>> entity = new HttpEntity<>(param, headers);
            ResponseEntity<String> response = restTemplate.postForEntity(adminUrl, entity, String.class);
            if (response.getStatusCode().is2xxSuccessful()) {
                log.info(">>> XXL-JOB 手动注册成功!");
            }
        } catch (Exception e) {
            log.error(">>> XXL-JOB 手动注册失败: {}", e.getMessage());
        }
    }
}

坑二:Spring Boot 3 缺失 GsonTool 等依赖

  • 现象:Admin 发送任务指令后,执行器报错 java.lang.NoClassDefFoundError: com/xxl/tool/gson/GsonTool

  • 原因:XXL-JOB 源码将工具类抽离到了 xxl-tool 包,而 Spring Boot 3 环境下的类加载机制导致该间接依赖丢失或包名冲突。

  • 解决源码级补丁。在业务模块手动创建 com.xxl.tool.gson.GsonToolcom.xxl.tool.exception.ThrowableTool 类,直接接管解析逻辑。

package com.xxl.tool.exception;

import java.io.PrintWriter;
import java.io.StringWriter;

public class ThrowableTool {
    public static String toString(Throwable e) {
        if (e == null) return "";
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        e.printStackTrace(pw);
        return sw.toString();
    }
}


package com.xxl.tool.gson;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class GsonTool {
    private static final Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();

    public static String toJson(Object src) {
        return gson.toJson(src);
    }

    public static <T> T fromJson(String json, Class<T> classOfT) {
        return gson.fromJson(json, classOfT);
    }
}

4. 关键排查指南(查错清单)

异常现象

检查路径

常用排查命令/方法

Admin 找不到执行器

执行器注册逻辑

SELECT * FROM xxl_job_registry; 查库

执行器不在线

端口与 AppName

netstat -tlnp | grep 9999 确认监听

任务触发 404

Context-Path

确认 Admin 地址是否带 /xxl-job-admin

任务触发 500

Token 与 依赖

观察执行器控制台是否缺类(NoClassDefFound)

调度失败/连接超时

防火墙

确保服务器已放行 8080(Admin) 和 9999(Executor)


5. 总结

解决分布式中间件问题的三板斧:

  1. 看日志:Admin 和 Executor 的日志要对照着看。

  2. 测链路:用 Postman 模拟注册请求,排除网络和权限问题。

  3. 读源码:当依赖冲突无法通过 Maven 解决时,直接在本地包路径下重写该类。