1. 为什么用 XxlJob?
Q:为什么项目中选择使用 XxlJob?相比其他定时任务方案有什么优势?
A: XxlJob 是一个轻量级分布式任务调度平台,选择它的核心原因如下:
| 对比维度 | Spring @Scheduled | Quartz | XxlJob |
|---|---|---|---|
| 集群/分布式 | ❌ 不支持 | ⚠️ 需数据库锁,性能差 | ✅ 原生支持 |
| 可视化管理 | ❌ 无 | ❌ 无 | ✅ Web 控制台 |
| 动态调整 | ❌ 需重启 | ⚠️ 需手动操作DB | ✅ 页面动态修改 |
| 任务分片 | ❌ 不支持 | ⚠️ 不支持 | ✅ 支持 |
| 失败重试 | ❌ 不支持 | ⚠️ 简单重试 | ✅ 策略丰富 |
| 调度日志 | ❌ 无 | ⚠️ 需自建 | ✅ 完整日志 |
总结优势:
- 简单易用:学习成本低,通过注解即可接入
- 分布式:天然支持集群部署,任务不会重复执行
- 可视化:提供 Web 控制台,方便管理任务、查看日志
- 弹性扩容:支持动态增减执行器实例
- 路由策略丰富:支持多种路由方式(轮询、随机、故障转移等)
- 完善的基础功能:失败重试、报警、任务分片、GLUE 模式等
2. XxlJob 的具体使用步骤
Q:在 Spring Boot 项目中集成和使用 XxlJob 的完整步骤是什么?
A:
第一步:引入依赖
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.4.0</version>
</dependency>
第二步:添加配置
在 application.yml 中配置:
xxl:
job:
admin:
addresses: http://127.0.0.1:8080/xxl-job-admin # 调度中心地址
executor:
appname: xxl-job-executor-sample # 执行器名称
port: 9999 # 执行器端口
logpath: /data/applogs/xxl-job # 日志路径
logretentiondays: 30 # 日志保留天数
第三步:编写配置类
@Configuration
public class XxlJobConfig {
@Value("${xxl.job.admin.addresses}")
private String adminAddresses;
@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);
return executor;
}
}
第四步:编写任务处理类
@Component
public class SampleXxlJob {
@XxlJob("demoJobHandler")
public void demoJobHandler() throws Exception {
XxlJobHelper.log("XXL-JOB, Hello World.");
for (int i = 0; i < 5; i++) {
XxlJobHelper.log("beat at:" + i);
TimeUnit.SECONDS.sleep(2);
}
}
}
第五步:在控制台配置任务
- 登录 XxlJob Admin 控制台(默认端口 8080)
- 新增执行器(AppName 与配置中一致)
- 新增任务,设置 JobHandler 为
demoJobHandler - 选择调度类型(CRON)、路由策略等
- 启动任务
3. XxlJob 的路由策略有几种?
Q:XxlJob 提供了哪些路由策略?分别适用于什么场景?
A: XxlJob 提供了以下路由策略:
| 策略名称 | 说明 | 适用场景 |
|---|---|---|
| 第一个 | 固定选择第一个机器 | 默认策略,简单场景 |
| 最后一个 | 固定选择最后一个机器 | — |
| 轮询(ROUND) | 按顺序依次分配 | 最常用,负载均衡 |
| 随机(RANDOM) | 随机选择 | 负载均衡 |
| 一致性哈希(CONSISTENT_HASH) | 根据 Job 参数哈希到固定机器 | 相同参数始终路由到同一台机器 |
| 最不经常使用(LFU) | 优先选择使用频率最低的机器 | 冷启动任务均衡 |
| 最近最久未使用(LRU) | 优先选择最长时间未使用的机器 | 资源利用率均衡 |
| 故障转移(FAILOVER) | 按照 LR 策略顺序检测,跳过故障机器 | 高可用场景 |
| 忙碌转移(BUSYOVER) | 按照 LR 策略顺序检测,跳过忙碌机器 | 避免任务堆积 |
| 分片广播(SHARDING_BROADCAST) | 广播到所有机器,每台处理不同分片 | 大数据量处理 |
面试重点 —— 分片广播:
// 获取总分片数和当前分片索引
int shardTotal = XxlJobHelper.getShardTotal(); // 总分片数
int shardIndex = XxlJobHelper.getShardIndex(); // 当前分片序号
// 根据 shardIndex 处理对应范围的数据
// 例如:查数据库时带上条件 id % shardTotal == shardIndex
3 台机器 → shardTotal=3,shardIndex 分别为 0、1、2,各自处理 1/3 的数据。
4. XxlJob 是如何防止任务重复执行的?
Q:在集群环境下,XxlJob 如何确保同一个任务不会被多个节点重复执行?
A: XxlJob 通过以下机制防止任务重复执行:
4.1 基于 MySQL 行锁的调度竞争
调度中心在触发任务时,通过数据库行锁保证只有一个调度线程能触发任务:
-- 核心思路(简化)
UPDATE xxl_job_lock SET lock_time = NOW() WHERE lock_name = 'schedule_lock';
- 多个 Admin 调度线程竞争同一把锁
- 抢到锁的线程负责扫描并触发任务
- 没抢到的线程直接跳过
4.2 路由策略保证单节点执行
对于非分片广播任务,路由策略(轮询、故障转移等)只会选择一个执行器节点去执行,从路由层面避免了重复。
4.3 执行器回调去重
- 执行器在处理任务时,会根据
jobId和触发时间判断是否已处理过 - 已处理过的任务不会重复执行
4.4 分片任务的防重
分片广播场景下:
- 每个分片节点获取不同的
shardIndex - 通过业务逻辑(如
id % shardTotal == shardIndex)保证每条数据只被一个节点处理 - 天然不存在重复执行问题
5. XxlJob 如何提升任务处理的效率?
Q:XxlJob 有哪些机制来提升任务处理的效率?
A:
5.1 任务分片(核心手段)
将大任务拆分成多个小任务,并行分配给多台机器执行:
@XxlJob("shardingJobHandler")
public void shardingJobHandler() throws Exception {
int shardTotal = XxlJobHelper.getShardTotal();
int shardIndex = XxlJobHelper.getShardIndex();
// 多台机器并行处理各自分片,效率线性提升
List<Data> dataList = queryData(shardTotal, shardIndex);
for (Data data : dataList) {
process(data);
}
}
例如:100 万条数据,10 台机器分片 → 每台只需处理 10 万条,效率提升 10 倍。
5.2 异步并行执行
在单个任务内部使用多线程并行处理:
@XxlJob("parallelJobHandler")
public void parallelJobHandler() throws Exception {
List<Task> tasks = getTasks();
// 使用线程池并行处理
CountDownLatch latch = new CountDownLatch(tasks.size());
ExecutorService pool = Executors.newFixedThreadPool(10);
for (Task task : tasks) {
pool.execute(() -> {
try {
doProcess(task);
} finally {
latch.countDown();
}
});
}
latch.await();
pool.shutdown();
}
5.3 其他优化手段
| 手段 | 说明 |
|---|---|
| 路由策略优化 | 使用故障转移/忙碌转移,避免将任务分配到故障或繁忙节点 |
| 失败重试 | 任务失败自动重试,避免人工介入,提高整体成功率 |
| 任务超时控制 | 设置合理的超时时间,避免任务卡死占用资源 |
| 取消超时任务 | Admin 主动终止超时未完成的任务,释放执行器资源 |
| 执行器自动发现 | 新增执行器节点后自动注册,无需手动配置,方便弹性扩容 |
6. XxlJob 的注解有哪些?
Q:XxlJob 常用的注解有哪些?分别怎么用?
A:
6.1 @XxlJob(核心注解)
标记一个方法为 XxlJob 任务处理器。
@XxlJob(value = "jobHandler名称", init = "初始化方法名", destroy = "销毁方法名")
public void myJobHandler() throws Exception {
// 任务逻辑
}
| 属性 | 说明 | 示例 |
|---|---|---|
value | JobHandler 名称,必填,需与控制台配置一致 | "demoJobHandler" |
init | 任务初始化方法名,任务线程初始化时调用 | "init" |
destroy | 任务销毁方法名,任务线程销毁时调用 | "destroy" |
6.2 完整示例(含 init/destroy)
@Component
public class MyXxlJob {
@XxlJob(value = "myJobHandler", init = "init", destroy = "destroy")
public void myJobHandler() throws Exception {
XxlJobHelper.log("任务执行中...");
String param = XxlJobHelper.getJobParam(); // 获取控制台传递的参数
// 业务逻辑...
}
private void init() {
XxlJobHelper.log("初始化资源,如数据库连接、线程池等");
}
private void destroy() {
XxlJobHelper.log("释放资源,如关闭连接、销毁线程池等");
}
}
6.3 XxlJobHelper 常用方法(非注解但常考)
虽然不是注解,但在任务方法中频繁使用:
XxlJobHelper.getJobParam(); // 获取任务参数
XxlJobHelper.getJobId(); // 获取任务ID
XxlJobHelper.getShardTotal(); // 获取总分片数
XxlJobHelper.getShardIndex(); // 获取当前分片索引
XxlJobHelper.log("日志内容"); // 记录任务日志(可在控制台查看)
XxlJobHelper.handleSuccess("成功信息"); // 标记任务成功
XxlJobHelper.handleFail("失败原因"); // 标记任务失败
XxlJobHelper.handleMsg("自定义信息"); // 设置任务执行结果信息
面试注意:XxlJob 只有
@XxlJob一个注解,结构非常简洁。但需要熟悉XxlJobHelper工具类的使用。