ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

分布式定时任务常见问题 解决方案

分布式定时任务常见问题  解决方案 主流框架XXL‑Job、Elastic‑Job、Spring‑TaskRedis、Quartz 集群、PowerJob。核心痛点集中在重复执行、任务丢失、分片错乱、时间漂移、死锁、雪崩、长任务。1. 任务重复执行最常见现象同一时刻多台机器同时跑同一个定时任务数据重复处理产生脏数据。原因Spring 原生 Scheduled 只做单机定时集群部署多实例时每台机器都会触发Quartz 锁竞争失败、数据库锁超时释放XXL‑Job 执行器多实例注册调度中心推送任务到多个执行器网络超时调度端未收到执行器 ACK重试推送任务。解决思路1. Redis 分布式锁最常用Spring 项目大量使用⚠️坑点1. 锁过期了任务还没跑完 → 任务被另一个节点拿到重复执行。Redisson 的看门狗watchdog自动续期解决这个问题。2. 不要自己手写简单 setnx 锁优先用 Redisson。Spring Boot 示例Quartz Redisson用 RedisSETNX 过期时间主流框架 Redisson 封装好可重入锁。 逻辑每个实例定时任务触发尝试获取锁 key设置超时时间大于任务最大执行时长防止任务卡死锁永远释放不了获取锁成功 → 执行业务逻辑获取锁失败 → 直接 return本次任务放弃任务执行完成释放锁异常也要释放锁// 定时任务方法 Scheduled(cron 0 0 1 * * ?) public void task(){ RLock lock redissonClient.getLock(lock:task:order_stat); try{ // tryLock(long waitTime, long leaseTime, TimeUnit unit) // waitTime 0抢锁等待时间 //多实例同时来抢锁如果锁已经被别人占有不等待立刻返回 false。 // 场景凌晨 1 点A 实例抢到锁B、C 实例过来发现锁被占不等直接返回 false不执行业务。 boolean getLock lock.tryLock(0,-1, TimeUnit.SECONDS); //leaseTime -1开启看门狗 WatchDog 自动续期。 // 锁默认 30s后台线程每 10s 检查如果持有锁的线程还活着就把锁过期时间重新刷回 30s。任务跑 10 分钟也不会锁过期。 if(getLock){ // 真正业务逻辑 doBusiness(); } }finally { finally { if(lock.isHeldByCurrentThread()){ lock.unlock(); } } } }看门狗原理Redisson 启动后台定时线程每隔leaseTime * 1/3默认 30s 的 1/310s给锁续期。⚠️只有不手动指定 leaseTime 才生效。返回值boolean getLocktrue当前线程抢到分布式锁可以执行业务false锁被其他实例占有放弃本次任务doBusiness 不执行。2. 数据库悲观 / 乐观锁任务表记录任务状态status0待执行执行时 update 把状态改为执行中行锁保证只有一个实例更新成功。update job_task set status1 where idxxx and status0;更新行数 0 才执行业务适合自建定时任务表。3. 调度中心统一调度主流方案XXL‑Job/SchedulerX调度器是独立中心节点只由调度中心下发任务执行器接收任务执行。 调度中心只下发一次任务多个执行器注册任务只会派给其中一台执行器从根源避免多实例同时跑。4. Elastic‑Job基于 ZK 的分片锁选举主节点执行。❗注意不要只靠单机内存锁多实例内存锁互相隔离完全无效。2. 核心问题 2任务丢失该跑没跑现象到时间任务完全没执行数据没处理无报错日志。原因触发时刻所有服务实例全部宕机恢复后错过时间不会自动补跑。调度中心宕机无法下发任务。任务执行器和调度中心网络抖动任务消息丢失。Quartz 集群时间到但是所有节点 GC / 卡顿错过触发窗口。解决方案1. 任务失败补偿 错过执行时间支持 “错过执行补跑”misfireXXL‑Job 支持misfire策略错过调度时间支持立即执行一次、忽略、丢弃。Quartz 有 misfire 阈值配置。业务上重要定时任务建议开启 misfire 补执行。2. 调度中心高可用部署调度中心集群多节点互备XXL‑Job 调度中心集群通过数据库锁保证同一时间只有一个节点做调度不会重复下发任务。3. 任务执行日志 状态持久化任务执行状态存入数据库待调度、运行中、成功、失败。后台可以看到哪些任务没跑手动触发重试。4. 幂等 业务层兜底校验即使补跑业务必须做幂等防止补跑带来重复数据。3. 长任务 / 任务执行超时现象任务跑很久没结束调度到下一个周期又触发新任务同一个任务实例多版本并发运行线程占满。原因任务处理数据量过大IO 慢上一轮还没跑完下一轮调度时间到没有超时控制线程无限阻塞。解决方案设置任务执行超时时间超时自动终止杀死任务线程禁止长周期大任务直接用定时做分片、分页分批处理开启任务互斥上一次未完成禁止启动新一轮XXL‑Job 支持 “任务互斥” 开关大任务拆分为定时只做触发真正业务丢 MQ 异步执行监控任务执行时长超时告警。坑线程池如果直接 interrupt如果业务不响应中断杀不掉任务只能业务层自己做中断标记。4. 核心问题 4服务器时间不一致时钟漂移现象多台机器系统时间不一样A 机器快 30sB 机器慢导致任务触发时间错乱。SpringScheduled(cron)是本机时间多机时间不一致灾难。解决方案统一 NTP 时间同步所有服务器同步时间运维层面保证。调度中心使用调度中心机器时间执行器不根据本机时间触发。XXL‑Jobcron 表达式在调度中心解析调度中心按自己时间下发执行器不解析 cron只接收任务。这一点很关键规避执行器机器时间错乱。 如果自己手写分布式定时千万不要每个实例本地解析 cron。5. 核心问题 5分片任务数据处理重复或者漏数据现象大数据量定时任务比如 “每天同步千万条订单”希望多机器分片并行处理提升速度每条数据只被一台机器处理一次。原因如果简单多实例跑全部实例扫描全表大量重复处理简单取模扩容后分片逻辑失效。解决方案Elastic‑Job 分片调度中心分配分片号总分片数 N每台执行器拿到部分分片只处理属于自己分片的数据。 例子总分片 4实例 A 拿到分片 0、1实例 B 拿到分片 2、3。SQLwhere id % 4 in (0,1)。执行器上下线自动重新分片动态扩容缩容。XXL‑Job 也支持分片广播任务所有执行器同时执行但业务自己区分分片数据。自建分片要注意扩容后分片规则改变要处理历史数据迁移Elastic‑Job 自动重分配。6. 核心问题 6任务失败缺少重试、告警问题定时任务报错无人知晓等到业务发现问题才知道。解决框架内置失败重试任务执行异常自动重试 N 次。告警失败推送钉钉 / 企业微信邮件告警。XXL‑Job 原生支持。记录执行历史执行耗时、返回值、异常栈持久化数据库。7. 核心问题 7长周期任务 调度中心重启场景一个任务要跑 2 小时调度中心重启。问题调度中心重启后不知道这个任务还在运行有可能再次下发任务造成重复执行。解决执行器上报任务心跳状态给调度中心调度中心维护运行中的任务列表。开启任务运行锁正在运行的任务不允许再次调度。对比三种实现方案优缺点方案 A手写 Redis 分布式锁 Scheduled✅简单不用中间件❌缺点时间依赖本机时间锁过期时间不好控制任务超时会重复跑没有 misfire 补跑调度中心没有实例全部挂掉任务丢失没有分片、告警、重试全部自己开发。适合简单小任务不适合核心业务。方案 BQuartz 集群数据库锁✅基于 DB 行锁多实例竞争保证只执行一次。❌缺点每个节点本地解析 cron机器时间不一致会乱misfire 配置复杂缺少运维后台告警重试分片要自己写。方案 CXXL‑Job / SchedulerX中心化调度✅工业级调度中心统一计算时间下发任务支持 misfire、超时控制、禁止并发、分片、告警、手动触发、日志。生产环境优先选这个。方案 DElastic‑Job✅强项是分片分布式任务ZK 做协调适合大数据分片处理。 ❌需要维护 ZooKeeper。生产环境业务最佳实践必做业务逻辑必须实现幂等无论什么定时框架网络、重试、misfire 都有可能多次执行业务不怕重复执行。重要任务开启失败告警不要靠日志看问题。配置任务超时开启禁止并发执行防止任务堆积卡死线程池。不要用原生 SpringScheduled直接上多实例生产必须搭配分布式锁或者使用调度平台。统一服务器 NTP 时间同步。开启 misfire 错过执行策略关键业务错过时间要补执行。定时任务不要写太重业务耗时久的任务考虑分片或者转消息队列异步处理。面试速记版Q分布式定时任务会遇到什么问题怎么解决重复执行分布式锁 (Redis/ZK)、数据库状态锁、中心化调度中心下发任务。任务丢失调度中心集群高可用misfire 错过补跑持久化任务状态手动重试。任务卡死超时并发任务超时时间禁止并发执行业务设置超时。时钟漂移NTP 时间同步调度中心统一解析 cron执行器不解析时间。大数据量漏处理重复处理分片任务 Elastic‑Job动态分片。失败无感知失败重试钉钉告警持久执行日志。兜底业务层幂等。
返回列表