XXL-JOB 2.5.0 多节点部署踩坑总结
场景:生产环境将 xxl-job-core 从 2.3.x 升级至 2.5.0,calculation 服务部署在双节点(11.122.187.86 / 11.122.187.87),升级后 XXL-JOB Admin 无法调用 calculation 执行器,报
Connection reset。
一、背景说明
SD 系统使用 XXL-JOB 做定时任务调度,整体结构如下:
┌──────────────────────────────────────────┐ │ sdms-job(调度中心 + 自身执行器) │ │ server.port = 9093 │ │ executor.appname = xxl-job-executor-sample│ │ executor.port = 9998 │ └────────────────┬─────────────────────────┘ │ 调度 ┌───────────┴──────────┐ ▼ ▼ sd-calculation sd-calculation (11.122.187.86) (11.122.187.87) server.port = 9094 server.port = 9094 executor.port = 9988 executor.port = 9988 appname = em-calculation-executor升级 xxl-job-core 到2.5.0后,sdms-job 自身执行器正常,但调度中心调用 sd-calculation 的两个节点时持续报错:
xxl-job remoting error(Connection reset), for url : http://11.122.187.86:9988/run xxl-job remoting error(Connection reset), for url : http://11.122.187.87:9988/run二、遇到的报错清单
升级过程中依次遭遇以下问题:
报错 1:NoClassDefFoundError: io/netty/handler/timeout/IdleStateHandler
java.lang.NoClassDefFoundError: io/netty/handler/timeout/IdleStateHandler at com.xxl.job.core.server.EmbedServer.start(EmbedServer.java:...)原因:xxl-job-core 2.5.0 依赖 Netty,但项目原有 Netty 版本(4.1.43.Final)与 2.5.0 要求不兼容,或 Netty 未被正确传递依赖。
解决:在 pom.xml 中将 Netty 显式升级到4.1.90.Final:
<dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.90.Final</version></dependency>报错 2:Optional int parameter 'triggerStatus' cannot be translated into a null value
java.lang.IllegalStateException: Optional int parameter 'triggerStatus' is present but cannot be translated into a null value due to being declared as a primitive type. Consider declaring it as object wrapper for the corresponding primitive type.原因:xxl-job 2.5.0 Admin 页面的某些查询接口中,请求参数triggerStatus使用了基本类型int接收,但前端传参时值为空,Spring MVC 无法将空值转为int。
解决:在调度中心(sdms-job)对应 Controller 的入参中,将int triggerStatus改为Integer triggerStatus。
报错 3:Connection reset— 核心问题
xxl-job remoting error(Connection reset), for url : http://11.122.187.86:9988/run这是本次升级最核心的问题,下一章详细分析。
三、核心问题:Connection reset的根本原因
3.1 XXL-JOB Executor 的通信机制
XXL-JOB Executor 内嵌了一个Netty HTTP Server,专门用于接收来自 Admin 的调度指令(/run、/beat、/kill等)。
Admin ──HTTP──▶ Executor 的 Netty Server(executor.port)──▶ 执行 JobHandler这个 Netty Server 监听的端口就是配置文件中的executor.port,与 Spring Boot 的server.port完全独立。
3.2 IP 自动探测机制
当配置文件中executor.ip和executor.address均为空时,XXL-JOB 会调用IpUtil.getIp()自动探测本机 IP,逻辑如下:
// XXL-JOB 源码 IpUtil.getIp()(简化)for(NetworkInterfaceiface:NetworkInterface.getNetworkInterfaces()){if(iface.isLoopback()||iface.isVirtual()||!iface.isUp())continue;for(InetAddressaddr:iface.getInetAddresses()){if(addrinstanceofInet4Address){returnaddr.getHostAddress();// 返回第一个找到的非回环 IPv4}}}对于 86、87 两台可直接访问的宿主机而言,自动探测完全没有问题:86 节点自动探测到11.122.187.86,87 节点自动探测到11.122.187.87,各自向 Admin 注册自己的地址,Admin 可正常回调。
⚠️注意:如果是容器(Docker/K8s)部署,自动探测拿到的是容器内网 IP(如
172.17.0.x),Admin 将无法路由,此时必须显式指定ip和address。
3.3 问题链路还原
本次Connection reset的根本原因是配置错误——有人试图把两个节点的地址用逗号写在同一份配置里(详见 3.4),导致注册地址非法,Admin 拿到错误的 URL 后发起调用,自然 reset。
1. sdms-calculation 配置了非法 address(逗号分隔多个 IP) 注册地址:http://11.122.187.87:9988,http://11.122.187.86:9988(整体当成一个字符串) 2. Admin 触发任务 调用:http://11.122.187.87:9988,http://11.122.187.86:9988/run ← 非法 URL! 3. 报错 Connection reset为什么 sdms-job 主节点没问题?
sdms-job 只部署一个节点,address为空,自动探测到本机 IP 正常注册,Admin 与执行器在同一服务内调用自己,链路完全可达。
3.4 配置写成多个 IP 逗号分隔是错误的
部分同学尝试将两个节点的 IP 写在同一份配置里:
# ❌ 错误写法executor:address:http://11.122.187.87:9988,http://11.122.187.86:9988ip:11.122.187.87,11.122.187.86这是不合法的。executor.address和executor.ip是单值字段,代表"这个 JVM 实例自己的地址"。逗号分隔后,框架会把整个字符串当成一个 URL 去注册,导致注册异常,Admin 控制台出现非法地址。
四、多节点部署的正确配置方式
核心原则
同一个项目打一个 JAR 包,部署到多台机器。每台机器启动后,XXL-JOB 自动探测本机 IP,分别向 Admin 注册各自的地址,从而在 Admin 上形成一个多机执行器集群。
4.1 正确理解:多节点 ≠ 一份配置写多个 IP
XXL-JOB 的多节点是通过**“多个实例各自注册”**实现的,不是在一份配置里写多个地址:
同一份 application-emeaprod.yml(appname = em-calculation-executor,ip/address 留空) │ ├── 部署到 11.122.187.86 → 启动后自动注册 http://11.122.187.86:9988 │ └── 部署到 11.122.187.87 → 启动后自动注册 http://11.122.187.87:9988 ↓ 两个实例都向 Admin 注册后 ↓ Admin 中 em-calculation-executor 执行器组 ┌─────────────────────────────┐ │ http://11.122.187.86:9988 │ [在线] │ http://11.122.187.87:9988 │ [在线] └─────────────────────────────┘ 按路由策略分发任务4.2 配置文件(两台机器共用同一份)
ip和address留空,XXL-JOB 在每台机器上启动时自动探测本机 IP 注册,两台机器无需区分配置:
# application-emprod.ymlxxl:job:admin:addresses:http://vicoe-xs-job-prod.earth.xpaas.t.comaccessToken:uqCkJP#xB4mmiPZ3WIVvO9sQP78aXd!timeout:30executor:appname:em-calculation-executor# 两台机器相同,代表同一个执行器组address:# 留空,自动探测本机 IP 生成注册地址ip:# 留空,自动探测本机 IPport:9988# Netty 监听端口,两台机器相同logpath:/data/applogs/emeaCalculation/jobhandlerlogretentiondays:-1为什么 86 和 87 可以留空自动探测?
因为两台机器均为可直接访问的宿主机,网络接口与业务 IP 一一对应。86 机器自动探测结果就是11.122.187.86,87 机器自动探测结果就是11.122.187.87,Admin 可以直接回调,无需手动指定。
4.3 启动命令(两台机器相同)
java-jarsdms-calculation.jar--spring.profiles.active=emeaprod86 节点启动 → 自动注册http://11.122.187.86:9988
87 节点启动 → 自动注册http://11.122.187.87:9988
4.4 什么情况下需要显式指定 ip/address?
| 场景 | 是否需要显式指定 |
|---|---|
| 裸机 / VM,单网卡,IP 直接可达 | ❌ 不需要,留空自动探测即可 |
| 裸机 / VM,多网卡,需指定特定网卡 | ✅ 需要,否则可能探测到错误网卡 |
| 容器部署(Docker/K8s),宿主机与容器 IP 不同 | ✅ 必须指定宿主机 IP,否则注册容器内网 IP 导致 Admin 无法回调 |
五、实现轮询效果
两台机器都正确注册后,在XXL-JOB Admin → 任务管理中,将em-calculation-executor执行器下的任务路由策略设为:
| 路由策略 | 说明 |
|---|---|
| ROUND(轮询) | 依次调用每个在线节点,均衡分发 ✅ 推荐 |
| RANDOM(随机) | 随机选一个节点 |
| FAILOVER(故障转移) | 优先调用第一个在线节点,故障时切换 |
| BUSYOVER(忙碌转移) | 优先调用空闲节点 |
六、验证步骤
步骤 1:确认 9988 端口在宿主机上监听
在 86/87 节点执行:
ss-lntp|grep9988# 或netstat-lntp|grep9988期望输出:java ... LISTEN 0.0.0.0:9988
步骤 2:确认 Admin 可达执行器端口
在 Admin 所在机器执行:
curl-vhttp://11.122.187.86:9988/ --max-time3curl-vhttp://11.122.187.87:9988/ --max-time3只要没有Connection reset/Connection refused即为可达(返回什么 HTTP 状态码不重要)。
步骤 3:查看执行器启动注册日志
在 sdms-calculation 日志中搜索:
xxl-job registry success确认打印的注册地址是http://11.122.187.86:9988而不是http://172.17.x.x:9988。
步骤 4:Admin 控制台确认
执行器管理 → emea-calculation-executor,机器地址列表应显示:
http://11.122.187.86:9988 [在线] http://11.122.187.87:9988 [在线]七、总结
| 问题 | 原因 | 解决方案 |
|---|---|---|
NoClassDefFoundError: IdleStateHandler | Netty 版本不兼容 2.5.0 | 升级netty-all到4.1.90.Final |
triggerStatus cannot be translated into null | Admin Controller 参数类型用int接收空值 | 改为Integer包装类型 |
Connection reset无法调用执行器 | address/ip用逗号写了两个节点的地址,框架将整个字符串当成非法 URL 注册,Admin 调用失败 | 留空让每台机器自动探测各自 IP,两台机器用相同appname各自注册即可组成集群 |
| 多节点配置误区 | 认为在一份配置里写多个 IP 就能"告知"两台机器,实际上address/ip是当前实例自身地址的单值字段 | 同一 JAR 包部署到多台机器,每台各自启动注册,Admin 自动感知所有在线节点 |
核心记住一句话:
XXL-JOB 多节点集群的正确做法:同一份配置(
ip/address留空)+ 同一个appname,部署到多台可直接访问的机器,每台机器启动后自动探测本机 IP 向 Admin 注册,Admin 按路由策略(轮询/随机等)在所有在线节点间分发任务。
