XXL-Job 的 5 个坑:任务重复执行、执行器掉线、分片广播失效、超时被中断、日志丢失

发布时间:2026/10/18 4:51:24
XXL-Job 的 5 个坑:任务重复执行、执行器掉线、分片广播失效、超时被中断、日志丢失 定时任务是那种上线就忘的功能——写完往那一挂直到某天凌晨告警炸了才想起来还有这东西。我项目里用 XXL-Job 跑数据同步、报表统计、库存对账这类活。踩过的坑攒了一堆挑 5 个最典型的讲每一个都是静默故障不看告警根本发现不了。坑 1任务重复执行——集群部署后同一个任务跑了两遍现象本地单机测试一切正常上线集群后对账任务每晚跑重复了两次数据库多出一倍数据。原因这里有个常见误解——很多人以为 XXL-Job 会自动帮你去重其实要看触发方式。XXL-Job 的调度中心通过DB 锁保证一次调度只触发一次执行这是它的第 22 条特性。但这句话有个前提走的是调度中心的调度。如果执行器是手动触发、GLUE 里自己起了线程、或者业务代码里又调了一次同一个任务DB 锁就管不着了。还有一种常见情况路由策略选了分片广播或轮询但任务本身没做幂等—— 广播模式下每个执行器都会跑一次如果任务写的是全局数据不是按分片参数分区处理就是 N 台机器重复写同一份数据。解决分两层防。第一层任务本身必须幂等——这是最根本的。定时任务和消息消费一样永远假设自己会被执行不止一次XxlJob(dailyReconcile)publicvoiddailyReconcile(){StringbizDateXxlJobHelper.getJobParam();// 业务日期从参数拿别用 now()if(StringUtils.isBlank(bizDate)){bizDateLocalDate.now().minusDays(1).toString();}// 幂等前置检查这一天已经跑过就跳过if(reconcileRecordMapper.existsByDate(bizDate)){XxlJobHelper.log({} 已完成对账跳过,bizDate);return;}doReconcile(bizDate);reconcileRecordMapper.save(bizDate);}关键点任务参数里传业务日期不要在任务内部用LocalDate.now()。否则重复执行时会按今天重算一遍幂等检查也拦不住。第二层用 DB 唯一索引兜底CREATETABLEt_reconcile_record(idBIGINTPRIMARYKEYAUTO_INCREMENT,biz_dateDATENOTNULL,create_timeDATETIMEDEFAULTCURRENT_TIMESTAMP,UNIQUEKEYuk_biz_date(biz_date)-- 重复插入直接报错跑不进去第二次);应用层幂等检查 数据库唯一索引双保险。只靠应用层检查是不够的——两个执行器同时跑到检查那一步、都发现没跑过然后都往下执行这就是并发漏洞。坑 2执行器掉线——任务状态一直是运行中现象某台执行器容器被 OOM Kill 重启那个正在跑的任务在调度中心一直显示运行中再也不会被重新调度。原因XXL-Job 有故障转移Failover特性——路由策略选它某台执行器挂了会自动切到正常的。但故障转移的前提是调度时刻发现机器不可用。已经在跑的任务如果执行器突然死了任务状态会卡在运行中直到超时时间到了才会被标记失败。如果任务的超时时间设成了0不限制那这个任务可能永远卡在那后续调度被阻塞处理策略判定为正在运行而跳过。解决三条一起上。① 一定要设超时时间别用 0// 在调度中心的任务配置里设置超时时间比如 30 分钟// 超时后调度中心会主动中断任务并把状态置为失败② 阻塞处理策略选对默认是单机串行排队等上一次跑完另外两个选项是丢弃后续调度和覆盖之前调度。对账这类任务建议用丢弃后续调度——堆积没意义等下一次调度就好。③ 注册方式用自动注册手填执行器地址xxl.job.executor.address在容器化环境里是灾难——Pod 一重建 IP 就变了。让执行器周期性自动上报调度中心自动发现xxl:job:executor:appname:order-executor# 不写 address让执行器自动注册容器/云环境必做ip:port:9999logpath:/data/applogs/xxl-job/jobhandlerlogretentiondays:30accessToken:${XXL_JOB_TOKEN}坑 3分片广播用错了——数据被处理了 4 遍现象给数据同步任务配了分片广播路由策略4 台执行器想着能并行加速。结果每台机器都把全量数据同步了一遍4 倍压力打到数据库。原因分片广播不是自动帮你切数据它只是把分片参数告诉你。每台执行器拿到的是我是第几片、一共几片具体怎么分数据要靠你自己写。XXL-Job 的机制是分片广播以执行器为维度分片一次调度广播触发集群中所有执行器执行一次通过分片参数支持开发分片任务这是它的第 13、14 条特性。没写分片逻辑 每个执行器跑全量。解决任务内部必须按分片参数过滤数据XxlJob(syncProduct)publicvoidsyncProduct(){// 取当前执行器的分片序号和总分片数intshardIndexXxlJobHelper.getShardIndex();intshardTotalXxlJobHelper.getShardTotal();// 关键按 id 取模分片每个执行器只处理属于自己那份ListProductproductsproductMapper.selectByShard(shardIndex,shardTotal);for(Productproduct:products){doSync(product);}XxlJobHelper.log(分片 {}/{} 完成处理 {} 条,shardIndex,shardTotal,products.size());}!-- 按 id 取模分片每台机器只捞自己那份 --selectidselectByShardresultTypeProductSELECT*FROMt_productWHEREMOD(id,#{shardTotal}) #{shardIndex}/select收益4 台机器并行理论提速 4 倍。代价是必须确保分片规则稳定——如果中途有一台执行器下线分片总数从 4 变成 3取模结果全变了同一批数据可能被两台机器分别处理。所以分片任务的幂等要求比单机任务更高。坑 4任务超时被中断但业务代码还在跑现象设了 5 分钟超时任务被调度中心标记为超时失败并触发了重试。但被中断的那次执行实际上还在后台跑着和重试的那次撞在一起数据乱了。原因XXL-Job 的超时中断是通过线程 interrupt 实现的。如果业务代码里有循环、等待、IO 阻塞这类不响应中断的操作interrupt只是设了个标志位代码该跑还是跑。解决业务代码要主动响应中断——在长循环里检查中断状态XxlJob(batchImport)publicvoidbatchImport(){ListFilefileslistPendingFiles();for(Filefile:files){// 每个文件开始处理前检查是否被要求中断if(Thread.currentThread().isInterrupted()){XxlJobHelper.log(收到中断信号已完成 {}/{}安全退出,processed,files.size());return;}importFile(file);processed;}}更稳的做法给任务设计断点续传能力——记录已处理到的位置下次启动从中断处继续而不是从头再来。// 每次处理完一批就落一次进度progressMapper.save(newProgress(batchImport,processed,files.size()));配合坑 1 的幂等表被中断的任务重跑时能自动跳过已处理部分。超时中断 断点续传 幂等这三个一起用才安全。坑 5日志看不见——出问题只能靠猜现象任务失败了调度中心点执行日志只看到一句Handler error和一堆框架堆栈看不到业务执行到哪一步。原因很多人习惯用log.info()打日志。XXL-Job 的执行日志在调度中心页面上看的那个必须用XxlJobHelper.log()log.info打的内容在执行器的本地日志文件里不在调度中心页面上。而且要注意调度中心只保留最近 30 天的日志logretentiondays配置排查陈年问题得去执行器本地看。解决关键节点全部用XxlJobHelper.log()让调度中心页面能自解释XxlJob(dailyReport)publicvoiddailyReport(){XxlJobHelper.log(开始生成日报日期{},bizDate);try{ListOrderordersorderMapper.selectByDate(bizDate);XxlJobHelper.log(查出订单 {} 条,orders.size());reportService.generate(bizDate,orders);XxlJobHelper.log(日报生成完成);}catch(Exceptione){// 异常必须打日志再抛否则页面上什么都看不到XxlJobHelper.log(日报生成失败: {},e.getMessage());XxlJobHelper.log(e);throwe;// 抛出去让调度中心标记为失败才能触发重试和告警}}关键认知异常要打日志 抛出。只打日志不抛调度中心认为任务成功不会重试也不会告警——这是最隐蔽的坑任务明明失败了但你永远收不到通知。总结坑本质解决1 重复执行任务不幂等 / 分片任务写全局数据业务日期从参数传 唯一索引兜底2 执行器掉线卡运行中没设超时任务永远卡住设超时时间 “丢弃后续调度” 自动注册3 分片广播失效只拿参数不写分片逻辑getShardIndex/TotalMOD(id, total)分片4 超时中断但代码还在跑interrupt 不被响应循环里检查isInterrupted() 断点续传5 日志看不见用了log.info而非XxlJobHelper.log关键节点用XxlJobHelper.log异常打日志后抛出核心心法定时任务的设计原则只有一条——假设它会被执行 0 次、1 次或 N 次。0 次要有兜底扫描比如每天检查有没有漏跑的任务N 次要幂等。围绕这条原则设计上面 5 个坑基本都能提前避开。你们项目用的什么调度框架踩过重复执行或者分片失效吗评论区聊聊。如果这篇帮你避开凌晨三点被叫醒转给组里维护定时任务的同事。「今天写BUG了么」关注看更多

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询