分布式任务调度设计
问题
如何设计一个分布式任务调度系统?
答案
方案对比
| 方案 | 原理 | 优缺点 |
|---|---|---|
| Spring @Scheduled | 单机 Cron | 简单,但不支持集群、不可视化 |
| Quartz | 数据库锁实现集群 | 重量级,配置复杂 |
| XXL-Job | 调度中心 + 执行器 | 可视化、集群、推荐 |
| ElasticJob | ZooKeeper 协调 | 弹性扩容、分片 |
XXL-Job 架构
任务幂等设计
分布式任务幂等执行
@XxlJob("dailyStatisticsJob")
public void dailyStatistics() {
String date = LocalDate.now().minusDays(1).toString();
String lockKey = "job:daily_stats:" + date;
// 分布式锁保证只执行一次
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 2, TimeUnit.HOURS);
if (!Boolean.TRUE.equals(locked)) {
log.info("任务已被其他节点执行, date={}", date);
return;
}
try {
// 执行统计逻辑
statisticsService.calculate(date);
} catch (Exception e) {
redisTemplate.delete(lockKey); // 执行失败释放锁,允许重试
throw e;
}
}
分片任务
大数据量分片处理
@XxlJob("batchProcessJob")
public void batchProcess() {
int shardIndex = XxlJobHelper.getShardIndex(); // 当前分片序号
int shardTotal = XxlJobHelper.getShardTotal(); // 总分片数
// 每个执行器处理自己分片的数据
List<Order> orders = orderMapper.selectByMod(shardIndex, shardTotal);
// SQL: SELECT * FROM orders WHERE id % #{shardTotal} = #{shardIndex}
for (Order order : orders) {
processOrder(order);
}
}
常见面试问题
Q1: 如何防止任务重复执行?
答案:
- 调度中心保证同一任务同一时间只调度一次
- 执行器端通过分布式锁做幂等
- 数据库唯一索引兜底
Q2: 任务执行失败怎么办?
答案:
- 重试策略:XXL-Job 支持配置重试次数
- 告警通知:失败后邮件/钉钉告警
- 补偿机制:人工触发重新执行
Q3: 分片任务的好处?
答案:
将大任务拆分给多个执行器并行处理,提升吞吐量。执行器扩缩容时自动重新分配分片。