@Scheduled单机跑跑还行,多实例部署了就全乱了——哪个实例执行?重复执行怎么办?数据怎么分片并行处理?XXL-JOB 就是专治这些的:调度中心统一管任务,执行器集群自动注册,分片广播把大数据量拆成 N 份给 N 台机器并行跑。这篇文章从 Docker 部署、Spring Boot 3.x 整合,到分片广播、任务参数、失败处理、父子任务链,全部用一个完整流程串下来。
安装
可以去官网下载源码,然后编译部署,这里我只介绍使用 docker 部署
docker 安装
先去 github 官网找到 xxl_job 的 SQL 文件,注意,要去找对应版本的 sql 文件,不要找 master 下的。
在 MySQL 数据库中执行,目前最新版要求 MySQL 要 8.0+ 以上,执行完后会创建一个新的数据库,名为xxl_job
下载之前可以去 docker hub 搜索 xxl-job-admin 查看最新版
dockerpull m.daocloud.io/docker.io/xuxueli/xxl-job-admin:3.4.2dockerrun-d\--nameidle-store-xxl-job-admin\-p8095:8080\-eTZ=Asia/Shanghai\-eJAVA_OPTS="-Xms256m -Xmx512m"\-ePARAMS="--spring.datasource.url=jdbc:mysql://host.docker.internal:3306/xxl_job?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false \ --spring.datasource.username=root \ --spring.datasource.password=Hliu1026. \ --xxl.job.accessToken=idle_store_xxl_job_2026"\-v/Users/ice/Desktop/cola/code/docker/xxl-job/logs:/data/applogs\xuxueli/xxl-job-admin:3.4.2之后访问 http://locahost:8095,可以看到控制台,默认用户名 admin,账号密码 123456
Spring Boot 3.x 整合
配置之前我们先来到控制台,它这里的执行器管理里面有一个执行器默认案例,我们编辑一下这个
改成我们自己想要的名字,选择自动注册就可以。
然后在项目中,在 pom.xml 中引入依赖
<dependency><groupId>com.xuxueli</groupId><artifactId>xxl-job-core</artifactId><version>3.4.2</version></dependency>版本和下载的 XXL-JOB 保持一致
之后进行配置,在 application.yaml 配置文件中配置内容
xxl:job:adminAddresses:http://localhost:8095# xxl-job 调度中心部署根地址,如调度中心集群部署存在多个地址则用逗号分隔accessToken:idle_store_xxl_job_2026# 和 xxl-job 上创建的 token 保持一致appName:xxl-job-executor-idle-store# 和 xxl-job 上创建的执行器名称保持一致ip:# 执行器 ip, 为空表示自动获取 ipport:9999# 执行器端口号logPath:./log/xxl-job/jobhandler# 执行器日志存放路径logRetentiondays:30# 日志保留时间(单位:天)然后创建两个配置类,一个读取配置,一个初始化执行器
@ConfigurationProperties(prefix=XxlJobProperties.PREFIX)@Component@DatapublicclassXxlJobProperties{publicstaticfinalStringPREFIX="xxl.job";privateStringadminAddresses;privateStringaccessToken;privateStringappName;privateStringip;privateintport;privateStringlogPath;privateintlogRetentionDays=30;}@Configuration@Slf4jpublicclassXxlJobConfig{@ResourceprivateXxlJobPropertiesxxlJobProperties;/** * 初始化执行器 */@BeanpublicXxlJobSpringExecutorxxlJobExecutor(){log.info(">>>>>>>>>>> xxl-job config init.");XxlJobSpringExecutorxxlJobSpringExecutor=newXxlJobSpringExecutor();xxlJobSpringExecutor.setAdminAddresses(xxlJobProperties.getAdminAddresses());xxlJobSpringExecutor.setAppname(xxlJobProperties.getAppName());xxlJobSpringExecutor.setIp(xxlJobProperties.getIp());xxlJobSpringExecutor.setPort(xxlJobProperties.getPort());xxlJobSpringExecutor.setAccessToken(xxlJobProperties.getAccessToken());xxlJobSpringExecutor.setLogPath(xxlJobProperties.getLogPath());xxlJobSpringExecutor.setLogRetentionDays(xxlJobProperties.getLogRetentionDays());returnxxlJobSpringExecutor;}}配置完后,启动项目
如果成功注册的话,在控制台就能查看到了 ip + 端口
写第一个任务
之后来到任务管理
这里面就可以配置我们执行器要执行的任务了,这里选中后进行编辑
配置完之后,点击保存,回到我们的服务中,创建/job包存放定时任务,比如我创建一个CreateTableXxlJob类,定义如下
@ComponentpublicclassCreateTableXxlJob{@XxlJob("createTableJobHandler")publicvoidcreateTableJobHandler()throwsException{XxlJobHelper.log("# 初始化明日增量数据表...");}}这里的createTableJobHandler对应前面的JobHandler。通过XxlJobHelper.log打印日志,这样在管理后台能看到日志信息
在控制台中我们可以自己运行定时任务
可以选择运行一次,或者启动(根据我们调度类型执行,比如我这里就是5秒执行一次)。
我们选择,启动,然后查询日志
如果你有梯子,可能会查询日志失败,需要改一下配置文件中的ip,改为本机IP,比如我这里
ip:172.25.64.42
控制台配置任务
调度类型
| 类型 | 说明 | 示例 |
|---|---|---|
| 无 | 不自动调度,只能手动触发 | 临时任务 |
| CRON | 按 Cron 表达式周期性触发 | 0 0 2 * * ?(每天凌晨 2 点) |
| 固定速度 | 固定间隔,秒为单位 | 60(每 60 秒一次) |
任务配置
运行模式:任务以 JobHandler 方式维护在执行器端;需要结合 “JobHandler” 属性匹配执行器中任务;
JobHandler :对应执行器中开发的任务类方法上@XxlJob注解自定义的 value 值;
路由策略
多实例怎么分配任务
| 策略 | 行为 |
|---|---|
| FIRST | 固定选第一台 |
| ROUND | 轮询 |
| RANDOM | 随机 |
| FAILOVER | 心跳检测,选第一个活着的 |
| SHARDING_BROADCAST(分片广播) | 所有机器都执行,传分片参数 |
调度过期策略
- 忽略:调度过期后,忽略过期的任务,从当前时间开始重新计算下次触发时间;
- 立即执行一次:调度过期后,立即执行一次,并从当前时间开始重新计算下次触发时间;
阻塞处理策略
任务堆积怎么办?
调度过于密集执行器来不及处理时的处理策略;
- 单机串行(默认):调度请求进入单机执行器后,调度请求进入FIFO队列并以串行方式运行;
- 丢弃后续调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,本次请求将会被丢弃并标记为失败;
- 覆盖之前调度:调度请求进入单机执行器后,发现执行器存在运行的调度任务,将会终止运行中的调度任务并清空队列,然后运行本地调度任务;
任务超时时间
支持自定义任务超时时间,任务运行超时将会主动中断任务;
失败重试次数
支持自定义任务失败重试次数,当任务失败时将会按照预设的失败重试次数主动进行重试;
分配广播
分片广播是 XXL-JOB 最值钱的功能。比如凌晨 2 点需要处理全量用户数据——10 万条,部署了 3 台实例,每台各分 3.3 万条并行处理:
@ComponentpublicclassDataAlignXxlJob{@XxlJob("dataAlignJobHandler")publicvoiddataAlignJobHandler(){// 拿到分片参数intshardIndex=XxlJobHelper.getShardIndex();// 当前分片:0, 1, 2intshardTotal=XxlJobHelper.getShardTotal();// 总分片数:3XxlJobHelper.log("当前分片: {}/{}",shardIndex,shardTotal);// 按分片取数据:每台机器只取自己那一段// SELECT * FROM user WHERE id % #{shardTotal} = #{shardIndex}List<User>users=userMapper.selectByShard(shardIndex,shardTotal);for(Useruser:users){// 处理逻辑...}XxlJobHelper.handleSuccess("分片 "+shardIndex+" 处理完成,共 "+users.size()+" 条");}}用XxlJobHelper.getShardIndex()和getShardTotal()拿到分片信息,SQL 里用WHERE id % shardTotal = shardIndex做数据分片。3 台机器各处理 1/3,提速 3 倍。
控制台把这个任务的路由策略改成SHARDING_BROADCAST即可。
任务参数
控制台传参,动态调整逻辑,控制台 “任务参数” 里填的内容,代码里能拿到
@XxlJob("dataAlignJobHandler")publicvoiddataAlignJobHandler(){Stringparam=XxlJobHelper.getJobParam();// "batchSize=500,startId=10000"// 解析参数,动态调整}适合控制每次处理的批量大小、起止 ID 这些需要临时调整的参数——改控制台不用改代码。
成功/失败标记
@XxlJob("myJobHandler")publicvoidmyJobHandler(){try{// 业务逻辑XxlJobHelper.handleSuccess("处理完成,共 1000 条");}catch(Exceptione){XxlJobHelper.handleFail("处理失败: "+e.getMessage());}}不手动调的话默认算成功。handleSuccess和handleFail的参数会显示在控制台日志最前面,方便快速看到执行结果。
父子任务——任务链
控制台配置 “子任务ID” 后,当前任务执行成功时自动触发子任务。适合有先后依赖的场景——比如先同步数据 → 重建索引:
任务 A:每天凌晨 2 点同步数据(CRON: 0 0 2 * * ?) ↓ 执行成功后自动触发 任务 B:重建 Elasticsearch 索引(调度类型:无,只被 A 触发)