先交代一下背景。我手上有个老项目定时任务一直用的是 Spring 自带的Scheduled代码里一坨一坨的Component加注解方法部署了四个实例之后问题开始集中爆发同一个任务在凌晨同时被四台机器执行某些不幂等的业务逻辑直接算错某天一台实例内存溢出重启结果第二天用户投诉才知道整条任务链断了。后来我把这套调度体系整体迁移到 XXL-Job从调度中心部署、执行器接入到分片任务调优踩了不少坑也把整个链路彻底摸通了。这篇文章就把 Spring Boot 整合 XXL-Job 的完整路径梳理一遍包括每一步怎么操作、每个配置为什么这么写以及那些网上不太有人讲清楚的故障排查思路给正在折腾这块的兄弟们一个参考。1. 单机定时任务撑不住的几个场景为什么要引入 XXL-Job1.1 多实例部署下的重复执行问题很多人一开始接触Scheduled的时候都觉得这个注解真香一个方法加个 cron 就完事甚至比 Quartz 还要简单。但在分布式环境下Scheduled是没有分布式锁概念的应用部署几个副本任务就会在几个副本上同时执行。如果任务本身是幂等的比如把某个缓存刷新一下那重复执行影响不大可一旦遇到定时发送短信定时对账生成报表并推送这类有副作用的逻辑重复执行就是事故现场。网上应对这个问题的常规套路是用 Redis 分布式锁或者数据库里的select for update悲观锁来保证同一时刻只有一个实例在跑。听起来可行但实际落地会发现每加一个定时任务都得写一套加锁解锁逻辑而且锁的过期时间、宕机后的锁释放也都是坑。任务少的时候还能忍任务一多代码里全是和业务无关的锁逻辑维护成本直线上升。1.2 没有统一的任务管理视图用Scheduled的第二个痛点是任务状态完全黑盒。系统里到底有多少定时任务每个任务上次运行是什么时候耗时多少成功还是失败这些问题没有一个统一的地方能看到。你只能去翻服务日志用grep去捞每个任务的执行痕迹非常痛苦。更麻烦的是动态调整执行频率。业务方说这个报表能不能改成半小时出一次你只能改代码里的 cron 表达式然后重新打包、发布、重启服务。整个过程少说半小时而且还有发布窗口的限制。想手动触发一次任务来看看效果Scheduled根本做不到只能等下一次调度。1.3 缺失败重试和告警第三个问题是最致命的任务挂了之后没有人知道。Scheduled方法里抛异常默认结果就是控制台打一段堆栈然后这个任务的下一次执行要等到下一个周期。如果是一个凌晨跑批的任务半夜两点执行失败你早上十点上班前根本不会注意到直到业务方反馈今天的账单数据怎么没生成你才知道出了问题。对很多对时效性敏感的业务来说这种体验绝对是灾难。当然Scheduled本身并非不能用如果你的项目是单机部署、任务数量很少、对失败容忍度高它确实是最省事的选择。但一旦上了多实例、任务数量和业务敏感性上来一个真正的分布式任务调度平台就是刚需。1.4 XXL-Job 的核心设计理念XXL-Job 是国内用的比较多的开源分布式任务调度平台它的核心设计是调度中心和执行器分离。调度中心是一个独立部署的 Web 应用负责任务的创建、编排、触发、日志存储和告警通知执行器则是嵌入到你的 Spring Boot 应用里的一个轻量级组件负责接收调度中心的指令并执行业务逻辑。两者之间通过 HTTP 接口通信。简单类比调度中心是导演执行器是演员。导演负责决定什么时候开拍、拍哪场戏演员接到指令后执行表演再把结果回报给导演。这样业务系统不需要关心任务什么时候触发失败了怎么重试这些事只需要专注于这个任务具体要做什么逻辑。对比维度ScheduledXXL-Job多实例执行每个实例都会执行通过路由策略控制单次/分片执行任务管理界面无有完整的后台管理界面动态调整 cron改代码发版界面直接修改实时生效失败重试手动实现内置失败重试次数配置告警通知无内置邮件告警手动触发任务不支持后台一键执行日志查看翻服务日志调度中心在线查看执行日志2. 调度中心先行XXL-Job Admin 的部署与初始化2.1 下载源码包与初始化数据库XXL-Job 的调度中心本身也是一个 Spring Boot 应用叫xxl-job-admin。我建议从 Gitee 的官方仓库xuxueli/xxl-job拉取 release 版本的源码包我当时用的是 2.4.0。解压之后你会看到两个核心目录xxl-job-admin是调度中心xxl-job-core是后面我们要引入到业务项目里的核心依赖。初始化数据库这一步是最容易被忽略的。XXL-Job 的所有任务配置、调度记录、执行器信息都存储在数据库里官方在源码目录下提供了一个建表脚本xxl-job/doc/db/tables_xxl_job.sql。我们需要先在 MySQL 里创建一个专用库比如xxl_job然后把脚本执行进去mysql -uroot -p -e CREATE DATABASE xxl_job DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; mysql -uroot -p xxl_job tables_xxl_job.sql脚本执行完之后库里会出现多张表比较关键的有xxl_job_info任务信息表存的是每个任务的基本配置。xxl_job_log调度日志表每个任务每次触发的记录都在这里。xxl_job_registry执行器注册表在线执行器的心跳信息在这个表里能看到。xxl_job_group执行器分组表。2.2 修改配置并启动 Admin 服务数据库初始化好之后进入xxl-job-admin模块打开src/main/resources/application.properties文件核心配置项就这么几处server.port8080 server.servlet.context-path/xxl-job-admin spring.datasource.urljdbc:mysql://localhost:3306/xxl_job?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai spring.datasource.usernameroot spring.datasource.passwordroot123 spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver xxl.job.accessTokendefault_token这里有两个地方要特别说一下。第一个是context-path必须保留/xxl-job-admin因为 Admin 的前端页面和接口路径都基于这个上下文你要是给它改掉后面执行器回调的地址匹配会出问题。第二个是xxl.job.accessToken这是调度中心和执行器之间的通信令牌两边必须配置一致否则调度中心发出的请求会被执行器拒绝。我习惯先设成一个固定值比如default_token后面引入执行器时保持一致。如果你想做更严格一点用 UUID 或一串随机字符串本地演示没必要。还有两个可选配置spring.mail.host、spring.mail.username等邮件配置用于失败告警本地调试可以先不配等上线前再补上。配置完成后在xxl-job-admin目录下执行 Maven 打包mvn clean package -DskipTests打包产物在xxl-job-admin/target/xxl-job-admin-2.4.0.jar直接java -jar启动java -jar xxl-job-admin-2.4.0.jar启动完成后浏览器访问http://localhost:8080/xxl-job-admin默认账号密码是admin / 123456。能看到登录页说明调度中心已经跑起来了。2.3 Admin 后台必须熟悉的四大模块登录进去之后左侧菜单里有几块是日常使用频率极高的建议先花几分钟过一遍。执行器管理这里维护的是所有接入调度中心的业务应用。执行器以 appname 为唯一标识一个 Spring Boot 服务对应一个执行器。我们在后面接入业务项目时第一步就是在这里新增一条执行器记录。任务管理所有定时任务的入口。创建任务、修改 cron、配置路由策略、手动触发、查看执行日志都从这里进。任务页面的字段比较多后面我会逐个展开讲。调度日志每个任务每次调度的完整记录包括调度结果、执行结果、执行耗时、执行的机器地址。排查任务不动、执行失败的问题基本都靠这个页面。用户管理XXL-Job 自带了一个简单的权限体系可以给团队成员分配不同的角色和权限。默认的 admin 账号是全权限生产环境建议创建只读账号给业务方查看任务状态用避免误操作。3. Spring Boot 项目接入执行器从依赖到自动注册3.1 Maven 依赖引入与版本选择调度中心部署好之后接下来就是把我们的 Spring Boot 业务项目变成执行器。这一步的核心是引入xxl-job-core依赖。dependency groupIdcom.xuxueli/groupId artifactIdxxl-job-core/artifactId version2.4.0/version /dependency版本选择上有个值得注意的点。如果你用的是 Spring Boot 2.6 以下版本用 2.3.1 这类老版本问题不大但如果你跟我一样用 Spring Boot 2.7.x甚至是 Spring Boot 3.x建议直接上 2.4.0 或更新版本。原因是 Spring Boot 2.6 之后默认禁止了 Bean 的循环依赖而 XXL-Job 老版本内部存在循环引用启动时会直接报错。2.4.0 版本对这块做了适配能减少很多不必要的麻烦。Spring Boot 版本推荐 XXL-Job 版本说明2.x2.6 以下2.3.1老版本稳定社区资料多2.6.x / 2.7.x2.4.0需要处理循环引用问题2.4.0 已适配3.x2.4.0注意检查 jakarta 命名空间等依赖兼容问题3.2 application.yml 中执行器配置项逐项解析引入依赖之后在application.yml里增加如下配置xxl: job: admin: addresses: http://localhost:8080/xxl-job-admin accessToken: default_token executor: appname: my-springboot-app address: ip: port: 9999 logpath: /data/applogs/xxl-job/jobhandler logretentiondays: 30这些配置项我逐个解释一下因为每个都可能在特定场景下坑到你。xxl.job.admin.addresses调度中心的完整地址。如果调度中心集群部署了多台用逗号分隔多个地址即可。注意这里的地址是调度中心对外提供接口的根路径不是首页地址所以要以/xxl-job-admin结尾。xxl.job.accessToken通信令牌必须与调度中心的xxl.job.accessToken完全一致。这个不一致会导致调度中心调用执行器时返回 500 或 401而且调度日志里只会看到一句很笼统的失败原因。xxl.job.executor.appname执行器在调度中心里的名字也是执行器的唯一标识。这一步的坑在于你在这里填了什么后面在 Admin 后台执行器管理新增执行器时AppName 必须一模一样大小写和符号都不能差否则执行器注册不上。xxl.job.executor.ip这一项强烈建议留空。XXL-Job 会通过InetAddress自动获取本机 IP留空时它会扫描网卡拿到一个内网 IP。但如果你猜过这台机器你知道多网卡环境下自动获取的 IP 不一定是调度中心能访问到的那个。如果出现注册地址不对可以在这一项手动指定 IP。默认留空就好让它自动判断。xxl.job.executor.port执行器的 HTTP 端口默认是 9999。这个端口是执行器用来接收调度中心回调请求的监听端口需要保证该端口没有被占用且防火墙放行。如果你的服务部署在容器里记得在 Dockerfile 或 Service 的端口映射里把这个端口也暴露出去。xxl.job.executor.logpath执行器运行日志的落盘路径。执行器在执行业务逻辑时XxlJobHelper.log()输出的内容会先写到这个目录下的日志文件再传给调度中心展示。这个目录必须存在且对运行用户可写否则任务能执行但日志看不到排查问题时会很头疼。xxl.job.executor.logretentiondays日志保留天数我一般设置 30 天过期日志会自动清理。这个属性是防止日志文件无限增长把磁盘打满的生产环境最好设置一个合理的值。3.3 执行器配置类与自动注册原理配置文件写完之后还需要创建一个 Java 配置类把XxlJobSpringExecutor注册到 Spring 容器中。这个类是执行器组件的核心入口它的作用简单说就是启动时把当前应用注册到调度中心同时维护一个任务方法映射表收到调度请求后根据 JobHandler 名称找到对应的方法并执行。Configuration public class XxlJobConfig { Value(${xxl.job.admin.addresses}) private String adminAddresses; Value(${xxl.job.accessToken}) private String accessToken; Value(${xxl.job.executor.appname}) private String appname; Value(${xxl.job.executor.port}) private int port; Value(${xxl.job.executor.logpath}) private String logPath; Value(${xxl.job.executor.logretentiondays}) private int logRetentionDays; Bean public XxlJobSpringExecutor xxlJobExecutor() { XxlJobSpringExecutor executor new XxlJobSpringExecutor(); executor.setAdminAddresses(adminAddresses); executor.setAccessToken(accessToken); executor.setAppname(appname); executor.setPort(port); executor.setLogPath(logPath); executor.setLogRetentionDays(logRetentionDays); return executor; } }这里想多说一句原理。XxlJobSpringExecutor实现了SmartInitializingSingleton接口在 Spring 容器完成所有单例 Bean 的实例化之后它会扫描容器中所有带XxlJob注解的方法把这些方法注册到内部的 map 结构中映射关系就是注解里的名字和方法的实际调用关系。随后它通过 HTTP 请求向调度中心上报自己这台机器的实例信息调度中心收到后写入xxl_job_registry表执行器就完成了在线注册。这个注册过程不是一次性的执行器会按大约 30 秒一次的心跳周期持续上报调度中心在 90 秒内没收到某个执行器的心跳就会把它标记为离线。所以如果看到执行器时上时下大概率是网络不通或者心跳周期不对。3.4 在调度中心添加执行器并验证注册执行器配置类和业务代码写完、启动 Spring Boot 应用之后回到 Admin 后台左侧执行器管理页面点击新增执行器AppName填my-springboot-app必须和application.yml里的xxl.job.executor.appname完全一致。名称随便填比如订单服务仅用于后台展示。注册方式选自动注册。机器地址自动注册模式下不需要填手动注册模式才需要填具体 IP:PORT。保存之后如果执行器已经启动等几秒钟刷新页面在OnLine 机器地址这一列就能看到类似192.168.1.20:9999的地址。看到这个地址说明执行器已经成功注册到调度中心了。一个常见问题是Spring Boot 应用已经启动了但这里的在线机器地址一直为空。排查思路是先看服务日志里有没有报错比如连接不上调度中心、AccessToken 不一致如果没有报错在服务器上 curl 一下执行器的端口确认接口是否响应curl http://192.168.1.20:9999/正常情况下会返回一串 JSON 或空白但至少 TCP 能通。如果 curl 都连不上多半是防火墙或者容器端口映射的问题。4. 写第一个任务 Handler 并跑通调度链路4.1 基于 XxlJob 注解的任务实现执行器注册成功接下来的重头戏就是写任务了。XXL-Job 支持两种任务模式一种是BEAN模式直接通过XxlJob注解标记一个 Spring Bean 的方法为任务方法另一种是GLUE模式允许在调度中心在线编写和修改任务代码无需发版就能调整逻辑。日常项目里 BEAN 模式用得最多先讲这个。新建一个组件类在里面定义任务方法Component public class SampleXxlJob { XxlJob(demoJobHandler) public void demoJobHandler() throws Exception { XxlJobHelper.log(XXL-JOB, Hello World.); XxlJobHelper.log(当前时间: {}, LocalDateTime.now().toString()); // 这里写你的业务逻辑 // 比如查库、调接口、批量处理数据 XxlJobHelper.handleSuccess(执行成功); } }这里最关键的一点是方法名无所谓类名无所谓但XxlJob注解里的值——demoJobHandler——才是这个任务的全局唯一标识。后面在调度中心配置任务时填的 JobHandler 就必须是这个名字。如果填错了调度中心会提示没有找到对应的 JobHandler。再强调一下XxlJobHelper的用法。这个工具类里的log()方法会把日志写到执行器的日志文件里面同时回传到调度中心你在 Admin 后台的执行日志里能看到。而如果你用System.out.println或者log.info这些日志只会在业务应用本地文件里出现调度中心是完全看不到的。所以任务里要输出关键信息请一律用XxlJobHelper.log()。XxlJobHelper.handleSuccess()和handleFail()则是主动标记任务的成功或失败状态。如果任务方法正常执行完毕没抛异常即使不调用任何方法任务也会被标记为成功但如果你希望任务能返回一些更明确的提示信息或者希望在业务逻辑中主动判断某一步失败并立即结束用这两个方法会更清晰。4.2 调度中心配置任务JobHandler、cron 与路由策略任务类写好后回到 Admin 后台左侧任务管理点击新增任务。这里字段比较多我把最重要的几个逐一说清楚。执行器下拉框里选择我们刚添加的那个执行器比如订单服务。JobHandler填demoJobHandler和代码里XxlJob注解的值保持一致。运行模式选BEAN。如果选了GLUE(Java)调度中心会给你一段可编辑的 Java 代码让你在线写任务逻辑这种方式不需要发布应用但使用场景相对特殊后面再展开。Cron 表达式填0 0/1 * * * ?表示每分钟执行一次。XXL-Job 用的是 Quartz 风格的 6 位 cron和 Spring 的 6 位表达式的顺序是一致的基本可以直接套用。路由策略这是 XXL-Job 比较核心的功能之一。默认第一个意思就是固定选第一台在线机器执行。如果你有多个执行器实例可以选择轮询随机故障转移分片广播等策略。单机调试用第一个就够。阻塞处理策略任务执行时间超过下一次调度时间点时的处理方式。单机串行表示下一次调度排队等上一次执行完丢弃后续调度表示下一次直接跳过覆盖之前调度表示终止上一次的下一次直接执行。默认选单机串行最安全。失败重试次数这里填 0 表示不重试填 2 表示失败后自动重试 2 次。生产环境的任务我一般根据业务幂等性来决定非幂等任务不建议开重试。任务参数这个字段可以填任意字符串在任务代码中通过XxlJobHelper.getJobParam()获取。这个参数是任务级别的配置不用改代码就能动态改变任务行为后面讲进阶玩法时会专门演示。配置完成后保存然后回到任务列表可以看到刚才创建的这条任务。如果你没改 cron 的启动状态任务默认是开启的到点就会自动触发。4.3 查看调度日志与执行日志任务创建后建议先不要干等 cron 触发直接在任务列表右侧点击执行一次手动触发一下然后马上进调度日志页面看结果。调度日志页面有非常清晰的表格调度时间、调度结果、执行结果、执行耗时、执行机器地址。点击执行日志按钮你会看到完整链路2025-01-12 14:30:01 [com.xxl.job.core.thread.JobThread] - 触发调度请求 2025-01-12 14:30:01 [com.xxl.job.core.thread.JobThread] - 任务执行成功展开详情你还能看到XxlJobHelper.log()输出的内容。如果任务中加了业务日志在这里就能直接定位问题不用再去翻应用服务器上的日志文件了。这也是很多人一旦用了 XXL-Job 就回不去Scheduled的重要原因——排障效率真的提升太多。4.4 链路验证与常见状态解释跑通一个任务后把调度日志里的状态解释一下方便你做判断调度成功、执行成功链路正常任务逻辑正常完成。调度成功、执行失败调度中心已经把请求发到执行器但执行器跑任务时抛了异常看执行日志定位原因。调度失败调度中心未能把请求发到执行器可能原因包括执行器离线、AccessToken 不一致、端口不通。另外有个细节调度中心界面上的执行结果和实际业务是否成功是有可能不一致的。比如你任务里调用了一个外部接口接口返回了错误码但你的代码没有主动抛异常任务依然会显示成功。所以在业务代码里凡是关键路径成功与否最好通过XxlJobHelper.handleFail()或主动抛异常来让状态真实反映到调度中心。5. 进阶玩法分片广播、动态参数与失败重试5.1 分片广播把海量数据拆到多台实例并行处理任务跑通只是开始实际业务里一定会遇到单台执行太慢的问题。比如一个任务要处理全量用户数据量几百万单实例要跑几十分钟业务方不乐意。这时候就要用分片广播策略了。路由策略选择分片广播后调度中心会把同一个任务同时分发给所有在线执行器并且每个执行器会拿到一个分片编号shardIndex和总分片数shardTotal。比如你有 3 台执行器在线那么每台拿到的分片编号分别是 0、1、2总分片数是 3。业务代码里只需要按照分片规则取模就能让每台机器只处理自己负责的数据。XxlJob(shardingJobHandler) public void shardingJobHandler() { // 当前执行器的分片索引0 ~ shardTotal-1 int shardIndex XxlJobHelper.getShardIndex(); int shardTotal XxlJobHelper.getShardTotal(); XxlJobHelper.log(当前分片: {} / {}, shardIndex, shardTotal); // 模拟一批用户ID ListLong userIds getAllUserIds(); // 每个执行器只处理 userId % shardTotal shardIndex 的数据 for (Long userId : userIds) { if (userId % shardTotal shardIndex) { processUser(userId); } } }判断一个用户该由哪台机器处理最常用的方式就是userId % shardTotal。这种水平拆分的思路在极大提升处理速度的同时也带来一个问题一旦某个执行器实例在任务执行过程中挂掉这个实例负责的那一部分数据就没有人处理了。所以使用分片广播时建议配合失败重试策略或者业务上接受短时数据延迟。5.2 任务动态参数不改代码改变任务行为很多任务在不同场景下需要跑不同的数据范围比如按日期补跑某一天的数据、按照业务类型只处理特定类型的数据。XXL-Job 在任务配置里提供的任务参数字段就是干这个用的。任务里获取参数的方法很直接XxlJob(paramJobHandler) public void paramJobHandler() { String jobParam XxlJobHelper.getJobParam(); XxlJobHelper.log(收到任务参数: {}, jobParam); // 假设参数格式是 date2025-01-01typeORDER if (StringUtils.hasText(jobParam)) { MapString, String paramMap parseQueryString(jobParam); String date paramMap.get(date); String type paramMap.get(type); // 按参数处理业务 } }比如业务方临时要补跑某一天的数据你不需要改代码在调度中心把任务参数改成date2025-01-01typeORDER手动触发一次就完事。这个设计对数据修复场景非常友好简直就是运维救星。建议在任务设计时就把参数规则约定好比如都用keyvalue格式多个参数之间用分隔方便解析和兼容扩展。5.3 失败重试、超时控制与父子任务依赖关于失败重试我建议按任务类型区别对待。纯读取类任务比如同步缓存、拉取接口数据失败重试是安全的可以设置 2-3 次。有写操作的任务比如发短信、扣款、生成对账单重试之前一定要确认业务本身是否幂等否则重试可能导致重复扣款或者重复发消息。超时控制方面在任务管理的高级配置里有任务超时时间字段单位是秒。任务执行超过这个时间执行器会主动中断这个任务的执行线程并在日志里标记为超时失败。对于那些可能有死循环或慢 SQL 的任务强烈建议设置一个合理的超时时间避免任务线程长时间占用导致后期任务堆积。父子任务依赖也是一个很实用的功能。比如先同步数据数据准备完成后再生成报表可以在任务 A 的配置里设置子任务 ID为任务 B这样 A 执行成功之后会自动触发 B。不过要注意这个子任务触发的逻辑是 A 成功后才把 B 加入调度队列如果配置了失败重试A 重试成功后才触发 B。链路较长时建议把每个节点的日志打清楚。6. 实战踩坑版本冲突、注册掉线与日志丢失6.1 Spring Boot 版本过高导致的启动失败这个坑在我接手的一个新项目里踩过。项目用的是 Spring Boot 2.7.16我引入了xxl-job-core2.3.1启动时直接报了一个诡异的错误The dependencies of some of the beans in the application context form a cycle: ┌─────┐ | xxlJobSpringExecutor └─────┘原因就是 Spring Boot 2.6 之后默认禁止了 Bean 循环依赖而老版 XXL-Job 的XxlJobSpringExecutor内部存在循环引用。网上一搜很多帖子会告诉你在配置里加一行spring.main.allow-circular-referencestrue把循环依赖放行。这个方案确实能启动但我不推荐一上来就这么干。更好的做法是升级 XXL-Job 到 2.4.0 或更新版本官方在新版本里重构了这块逻辑不需要放开循环依赖限制就能正常工作。如果你因为兼容性原因只能留在老版本再考虑那个兜底开关。另外提醒一句如果项目本身用了 Spring Boot 3.x务必确认你引入的xxl-job-core版本支持 Spring Boot 3避免出现命名空间或依赖冲突的问题。6.2 执行器注册不上或注册后立即掉线执行器注册不上不要慌按这条路径排查。第一步看服务启动日志。如果配置了debug级别日志XXL-Job 启动时通常会打印注册相关的调用信息比如xxl-job register executor success或fail。如果完全没有相关日志先确认XxlJobSpringExecutor这个 Bean 是否真的被 Spring 容器扫描到了配置类的位置和启动类的关系有时候会让人忽略。第二步看 IP 和端口。多网卡机器上最常见的坑就是注册了错误的 IP比如服务器上有eth0和docker0两块网卡自动获取到的 IP 是一个 172.17.x.x 的 Docker 网段地址调度中心在另一个网段根本访问不到。这种情况就是在application.yml里手动指定xxl.job.executor.ip为业务网卡对应的 IP。第三步看在线状态。如果注册上之后几秒又掉线基本是心跳不通。检查执行器的 9999 端口是不是只监听了本机或者被防火墙拦截了netstat -tlnp | grep 9999如果端口是好的再用curl从调度中心所在机器访问执行器端口验证网络连通性。容器化部署时还要特别注意9999端口必须映射到宿主机外部否则调度中心永远访问不到执行器。6.3 任务执行成功但调度中心却看不到日志这个问题的表现形式是任务在业务系统里确实执行了但调度中心的任务日志要么一直显示调度成功执行结果为空要么点开执行日志只有接口调用记录没有XxlJobHelper.log()输出的内容。原因很可能是xxl.job.executor.logpath配置的目录不存在或没有写权限。执行器在写日志的时候如果目录不可写会静默失败任务该跑还是跑但日志回传就是空的。解决办法是提前把目录创建好并赋予写权限mkdir -p /data/applogs/xxl-job/jobhandler chown -R 应用运行用户:应用运行用户 /data/applogs/xxl-jobWindows 本地调试时把logpath配置成一个真实存在的目录比如D:/logs/xxl-job/jobhandler避免测试环境莫名其妙看不到日志。6.4 AccessToken 不一致导致调度失败调度中心配置了accessToken执行器也配置了accessToken但发现任务始终调度失败。检查一下是不是有多个环境、多套配置导致某个环境里的执行器用的accessToken和调度中心不一致。这种问题在本地调试时尤其隐蔽因为本地可能有一套配置测试环境又有另一套。排查方式是看调度中心的调度日志失败原因如果类似xxl-job access token is invalid那就明确是 token 不匹配。把两边的xxl.job.accessToken改成同一个值重启执行器即可。如果你不想用 token 校验两边都留空也是可以的但生产环境强烈建议加上防止内网里有人伪造请求触发你的任务。6.5 集成测试时如何绕开调度中心最后分享一个测试相关的经验。Spring Boot 项目的单元测试或集成测试如果直接启动整个 Spring 容器XxlJobSpringExecutor会尝试连接调度中心。测试环境如果连不上调度中心这些用例就会失败或者多出一堆无意义的超时等待。我的做法是给配置类加一个开关专门用于测试环境# application-test.yml xxl: job: enabled: false然后在配置类上加上条件注解Configuration ConditionalOnProperty(name xxl.job.enabled, havingValue true, matchIfMissing true) public class XxlJobConfig { // 配置内容不变 }这样测试环境的配置里把xxl.job.enabled设为false执行器就不会启动也不会尝试注册到调度中心。测试用例可以专注于业务逻辑本身不会被外部依赖干扰。我这两年用下来XXL-Job 在绝大多数场景下都能解决分布式定时任务的管理问题。刚开始接入时可能会被它那一堆配置项和策略选项唬住但其实核心链路就一条调度中心负责调度执行器负责干活JobHandler 把两边串起来。把这条主线跑通再去根据业务场景调整路由策略、失败重试和分片规则整个体系的掌控感就完全不一样了。