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. 核心执行流程图
理解了数据怎么走,问题就解决了一半。
注册阶段:执行器启动 -> 询问 Nacos 获取 Admin IP -> 调用
/api/registry报到。调度阶段:Admin 触发任务 -> 查找注册表获取执行器 IP -> 调用执行器
9999端口。执行阶段:执行器收到指令 -> 反射调用
@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 的
@Scheduled和RestTemplate手写一个注册器,避开脆弱的原生 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.GsonTool和com.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. 关键排查指南(查错清单)
5. 总结
解决分布式中间件问题的三板斧:
看日志:Admin 和 Executor 的日志要对照着看。
测链路:用 Postman 模拟注册请求,排除网络和权限问题。
读源码:当依赖冲突无法通过 Maven 解决时,直接在本地包路径下重写该类。