XXL-JOB调度中心从入门到实战:分布式任务调度一文搞定 本文适合刚接触分布式任务调度的开发者以及准备面试的同学。从核心概念到实战部署代码简洁易懂快速上手企业级调度中心。一、什么是调度中心一句话调度中心就是定时任务管理平台统一管理和监控所有定时任务。传统方式 调度中心方式 ┌─────────┐ ┌─────────────────────┐ │ 服务A │ Scheduled │ 调度中心 │ │ 服务B │ cron表达式 │ (XXL-JOB) │ │ 服务C │ 分散在各服务中 │ ↓ │ └─────────┘ │统一管理、监控、报警 │ └─────────────────────┘ ↓ ┌─────────────────────┐ │服务A 服务B 服务C │ └─────────────────────┘常见调度中心对比调度中心特点适用场景Spring Task简单、单机简单定时任务Quartz功能强大、复杂单体应用XXL-JOB可视化、分布式、易用企业级首选推荐Elastic-Job分片、弹性大数据量任务二、XXL-JOB核心架构┌─────────────────────────────────────────────────────────────┐ │ 调度中心Admin │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │任务管理 │ │调度日志 │ 执行器管理│ │用户管理 │ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ └─────────────────────────────────────────────────────────────┘ ↓ 调度触发 ┌─────────────────────────────────────────────────────────────┐ │ 执行器Executor │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │服务A执行器 │ │服务B执行器 │ │服务C执行器 │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ └─────────────────────────────────────────────────────────────┘核心概念调度中心任务管理后台负责触发任务执行器实际执行任务的服务注册到调度中心任务具体的业务逻辑一个Java方法三、快速部署10分钟搞定3.1 下载源码# GitHub地址 git clone https://github.com/xuxueli/xxl-job.git ​ # 目录结构 xxl-job/ ├── doc/ # 文档和SQL脚本 ├── xxl-job-admin/ # 调度中心后台管理 └── xxl-job-executor-samples/ # 执行器示例3.2 初始化数据库-- 执行 doc/db/tables_xxl_job.sql -- 创建8张表 ​ -- 1. xxl_job_info任务信息 -- 2. xxl_job_log调度日志 -- 3. xxl_job_log_report报表 -- 4. xxl_job_logglueGLUE日志 -- 5. xxl_job_group执行器分组 -- 6. xxl_job_user用户 -- 7. xxl_job_lock分布式锁3.3 启动调度中心# xxl-job-admin/src/main/resources/application.properties ​ server.port8080 spring.datasource.urljdbc:mysql://127.0.0.1:3306/xxl_job?useUnicodetruecharacterEncodingUTF-8 spring.datasource.usernameroot spring.datasource.password123456 ​ # 调度中心通讯TOKEN执行器需要配置相同TOKEN xxl.job.accessTokenxxl-job-token启动XxlJobAdminApplication访问http://localhost:8080账号admin密码123456四、创建执行器Spring Boot项目4.1 引入依赖!-- pom.xml -- dependency groupIdcom.xuxueli/groupId artifactIdxxl-job-core/artifactId version2.4.0/version /dependency4.2 配置文件# application.yml server: port: 8081 ​ xxl: job: admin: addresses: http://127.0.0.1:8080/xxl-job-admin # 调度中心地址 executor: appname: my-executor # 执行器名称 port: 9999 # 执行器端口0则自动 logpath: /data/applogs/xxl-job/jobhandler # 日志路径 logretentiondays: 30 # 日志保留天数 accessToken: xxl-job-token # 与调度中心一致4.3 执行器配置类/** * XXL-JOB执行器配置 */ 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.setAppname(appname); executor.setPort(port); executor.setLogPath(logPath); executor.setLogRetentionDays(logRetentionDays); executor.setAccessToken(accessToken); return executor; } }五、任务开发实战5.1 方式一Bean模式推荐/** * 任务HandlerBean模式 * 在调度中心配置JobHandler名称即可 */ Component public class SampleJobHandler { ​ private static final Logger log LoggerFactory.getLogger(SampleJobHandler.class); ​ /** * 简单任务示例 * 调度中心JobHandler填写sampleJobHandler */ XxlJob(sampleJobHandler) public void sampleJobHandler() throws Exception { log.info(XXL-JOB任务执行sampleJobHandler); // 业务逻辑 for (int i 0; i 5; i) { log.info(执行进度{}/5, i 1); Thread.sleep(1000); } log.info(任务执行完成); } ​ /** * 带参数的任务 * 调度中心运行参数填写hello world */ XxlJob(paramJobHandler) public void paramJobHandler() throws Exception { // 获取调度中心传入的参数 String param XxlJobHelper.getJobParam(); log.info(接收到参数{}, param); // 业务处理 XxlJobHelper.log(参数处理完成 param); } ​ /** * 分片任务大数据量并行处理 * 路由策略使用分片广播 */ XxlJob(shardingJobHandler) public void shardingJobHandler() throws Exception { // 获取分片参数 int shardIndex XxlJobHelper.getShardIndex(); // 当前分片序号 int shardTotal XxlJobHelper.getShardTotal(); // 分片总数 log.info(分片参数当前第{}片共{}片, shardIndex, shardTotal); // 业务逻辑按分片处理数据 // 例如shardTotal3则每个执行器处理1/3的数据 ListInteger dataList getAllData(); for (int i 0; i dataList.size(); i) { if (i % shardTotal shardIndex) { // 当前执行器负责的数据 processData(dataList.get(i)); } } } /** * 返回执行结果成功/失败 */ XxlJob(resultJobHandler) public void resultJobHandler() throws Exception { try { // 业务逻辑 boolean success doBusiness(); if (success) { XxlJobHelper.handleSuccess(执行成功); } else { XxlJobHelper.handleFail(执行失败业务处理异常); } } catch (Exception e) { XxlJobHelper.handleFail(执行异常 e.getMessage()); } } }5.2 方式二GLUE模式Java代码在线编辑适用场景任务逻辑需要频繁修改不想重新发布服务 ​ 操作步骤 1. 调度中心创建任务路由策略选择第一个或轮询 2. 运行模式选择GLUE(Java) 3. 点击GLUE IDE在线编写代码 4. 保存后立即生效无需重启// GLUE模式代码示例在线编辑 package com.xxl.job.service.handler; ​ import com.xxl.job.core.handler.annotation.XxlJob; import org.slf4j.Logger; import org.slf4j.LoggerFactory; ​ public class DemoGlueJobHandler { private static final Logger log LoggerFactory.getLogger(DemoGlueJobHandler.class); ​ XxlJob(demoGlueJobHandler) public void execute() throws Exception { log.info(GLUE模式任务执行代码可在线修改); // 业务逻辑... } }六、调度中心配置详解6.1 创建任务1. 打开调度中心http://localhost:8080 2. 任务管理 → 新增 ​ 必填配置 ├── 任务描述用户数据同步任务 ├── 执行器my-executor选择刚注册的执行器 ├── JobHandlersampleJobHandler与XxlJob注解值一致 ├── Cron0 0 2 * * ? 每天凌晨2点执行 └── 路由策略轮询6.2 路由策略说明策略说明适用场景第一个固定选择第一个执行器单机执行轮询依次轮流执行负载均衡随机随机选择执行器负载均衡一致性HASH相同任务始终到同一执行器有状态任务最不经常使用选择使用最少的执行器动态负载最近最久未使用选择最久未使用的执行器动态负载故障转移忽略超时的选择下一个高可用忙碌转移忽略执行中的选择下一个高可用分片广播广播到所有执行器分片执行大数据量6.3 Cron表达式速查格式秒 分 时 日 月 周 ​ 常用示例 ┌──────────────────────────────────────────────────────────┐ │ 0 0/5 * * * ? 每5分钟执行 │ │ 0 0 2 * * ? 每天凌晨2点 │ │ 0 0 1 * * 1 每周一凌晨1点 │ │ 0 0 0 1 * ? 每月1号零点 │ │ 0 0 0 * * ? 每天零点 │ │ 0 30 9 * * ? 每天9:30 │ │ 0 0 9-18 * * ? 每天9-18点整点 │ │ 0 0/30 9-18 * * ? 每天9-18点每30分钟 │ └──────────────────────────────────────────────────────────┘七、实战场景示例7.1 场景一数据同步任务/** * 每天凌晨同步订单数据 */ Component public class OrderSyncHandler { ​ Autowired private OrderService orderService; ​ XxlJob(orderSyncJob) public void orderSyncJob() throws Exception { // 获取参数同步天数 String param XxlJobHelper.getJobParam(); int days param ! null ? Integer.parseInt(param) : 1; // 计算日期范围 LocalDate endDate LocalDate.now(); LocalDate startDate endDate.minusDays(days); XxlJobHelper.log(开始同步订单{} 到 {}, startDate, endDate); // 分页查询并同步 int page 1; int pageSize 1000; int total 0; while (true) { ListOrder orders orderService.queryByPage(startDate, endDate, page, pageSize); if (orders.isEmpty()) break; // 批量处理 orderService.batchSync(orders); total orders.size(); XxlJobHelper.log(已同步{}条, total); page; } XxlJobHelper.handleSuccess(同步完成共 total 条); } }7.2 场景二超时订单处理/** * 每5分钟检查超时未支付订单 */ Component public class TimeoutOrderHandler { ​ Autowired private OrderService orderService; ​ XxlJob(timeoutOrderJob) public void timeoutOrderJob() throws Exception { // 查询30分钟内未支付的订单 LocalDateTime timeout LocalDateTime.now().minusMinutes(30); ListOrder timeoutOrders orderService.queryUnpaidBefore(timeout); XxlJobHelper.log(发现超时订单{}笔, timeoutOrders.size()); int successCount 0; for (Order order : timeoutOrders) { try { orderService.cancelOrder(order.getId()); successCount; } catch (Exception e) { XxlJobHelper.log(取消订单失败{}原因{}, order.getId(), e.getMessage()); } } XxlJobHelper.handleSuccess(处理完成成功 successCount 笔); } }7.3 场景三报表生成/** * 每天生成日报表分片广播模式 */ Component public class ReportHandler { ​ XxlJob(dailyReportJob) public void dailyReportJob() throws Exception { int shardIndex XxlJobHelper.getShardIndex(); int shardTotal XxlJobHelper.getShardTotal(); XxlJobHelper.log(分片 {}/{} 开始生成报表, shardIndex, shardTotal); // 按分片处理不同业务线 ListString businessLines Arrays.asList(order, user, product, finance); for (int i 0; i businessLines.size(); i) { if (i % shardTotal shardIndex) { String line businessLines.get(i); XxlJobHelper.log(处理业务线{}, line); generateReport(line); } } XxlJobHelper.handleSuccess(报表生成完成); } private void generateReport(String businessLine) { // 生成报表逻辑... } }7.4 场景四邮件/短信提醒/** * 工作日早上9点发送提醒 */ Component public class ReminderHandler { ​ Autowired private EmailService emailService; ​ XxlJob(morningReminderJob) public void morningReminderJob() throws Exception { // 判断是否工作日 if (!isWorkDay(LocalDate.now())) { XxlJobHelper.log(今天不是工作日跳过); return; } // 获取需要提醒的用户 ListUser users getReminderUsers(); for (User user : users) { try { emailService.sendReminder(user.getEmail(), 早安提醒); XxlJobHelper.log(发送成功{}, user.getEmail()); } catch (Exception e) { XxlJobHelper.log(发送失败{}原因{}, user.getEmail(), e.getMessage()); } } } private boolean isWorkDay(LocalDate date) { DayOfWeek day date.getDayOfWeek(); return day ! DayOfWeek.SATURDAY day ! DayOfWeek.SUNDAY; } }八、高级功能8.1 任务依赖DAG场景任务A完成后触发任务B和CB和C都完成后触发D ​ 配置方式 1. 任务B、C的父任务ID填写A的ID 2. 任务D的父任务ID填写B和C的ID ​ 效果 A / \ B C \ / D8.2 邮件报警配置# 调度中心 application.properties spring.mail.hostsmtp.qq.com spring.mail.port465 spring.mail.usernameyour-emailqq.com spring.mail.passwordyour-password spring.mail.properties.mail.smtp.ssl.enabletrue ​ # 任务失败时发送邮件到 xxl.job.alarm.emailadminexample.com8.3 运行报表调度中心 → 运行报表 ​ 可查看 ├── 调度次数趋势 ├── 执行结果分布成功/失败/失败重启 ├── 执行器在线状态 └── 任务执行耗时统计九、常见问题排查问题1执行器注册失败# 检查项 1. 调度中心地址是否正确 2. TOKEN是否与调度中心一致 3. 执行器名称appname是否在调度中心创建 4. 网络是否通畅 ​ # 日志查看 tail -f /data/applogs/xxl-job/jobhandler/xxl-job-executor.log问题2任务一直不执行# 检查项 1. 任务状态是否为运行中 2. Cron表达式是否正确在线工具cron.qqe2.com 3. 执行器是否在线 4. 路由策略是否匹配问题3任务执行超时# 解决方案 1. 增大任务超时时间默认30分钟 2. 任务内记录日志XxlJobHelper.log() 3. 大任务改为分片广播模式十、面试高频问题Q1XXL-JOB的架构是怎样的答采用中心化架构由调度中心和执行器两部分组成调度中心负责任务管理、触发调度、日志管理执行器注册到调度中心接收调度请求执行任务调度中心通过HTTP调用执行器执行器回调执行结果。Q2XXL-JOB如何保证任务不重复执行答三种机制路由策略分片广播模式下每个执行器只处理自己的分片分布式锁调度中心使用数据库锁保证同一任务同一时刻只触发一次阻塞策略默认丢弃后续调度避免任务堆积Q3XXL-JOB和Quartz的区别答对比项QuartzXXL-JOB架构嵌入式中心化可视化无有分布式需自己实现原生支持路由策略无9种策略日志管理无完整日志报警无邮件报警学习成本中等低Q4如何实现任务失败重试答调度中心 → 任务配置 → 失败重试次数3 ​ 任务代码中抛出异常即为失败 if (执行失败) { throw new RuntimeException(任务失败); } ​ 调度中心会自动重试指定次数Q5分片广播的使用场景答适用于大数据量并行处理场景同步100万条数据 ​ 单机1个执行器处理100万条耗时长 分片3个执行器各处理33万条并行执行 ​ 代码获取分片参数 int shardIndex XxlJobHelper.getShardIndex(); // 0,1,2 int shardTotal XxlJobHelper.getShardTotal(); // 3十一、最佳实践任务幂等任务可重复执行结果一致日志记录关键步骤使用XxlJobHelper.log()超时设置根据任务耗时合理设置报警配置生产环境必须配置邮件报警分片处理大数据量任务使用分片广播参数传递通过JobParam传递配置避免硬编码十二、推荐阅读XXL-JOB官方文档XXL-JOB GitHubCron表达式在线生成器