扔掉笨重的XXL-JOB;基于Nacos的优雅调度方案
Nacos 负责发现节点,Spring Scheduler 负责触发,节点之间自己选一个人干活
order-service
├── 10.20.4.17:8080
├── 10.20.4.21:8080
└── 10.20.4.33:8080
三台机器都能收到定时触发,但真正执行之前,先去 Nacos 看一下当前有哪些健康节点。
排序。
排第一的执行。
其他节点直接返回。
@Component
@RequiredArgsConstructor
public class JobOwner {
private final NamingService namingService;
private final Environment env;
public boolean mine(String serviceName) {
try {
List<Instance> nodes =
namingService.selectInstances(serviceName, true);
if (nodes.isEmpty()) {
return false;
}
String winner = nodes.stream()
.map(this::nodeKey)
.sorted()
.findFirst()
.orElse("");
return winner.equals(localNode());
} catch (Exception e) {
return false;
}
}
private String nodeKey(Instance node) {
return node.getIp() + ":" + node.getPort();
}
private String localNode() {
String ip = env.getProperty("spring.cloud.client.ip-address");
String port = env.getProperty("server.port", "8080");
return ip + ":" + port;
}
}
@Component
@RequiredArgsConstructor
public class OrderRepairJob {
private final JobOwner jobOwner;
private final OrderRepairService repairService;
@Scheduled(cron = "0 */5 * * * ?")
public void repair() {
if (!jobOwner.mine("order-service")) {
return;
}
repairService.fixPendingOrders();
}
}
到这里,其实已经能解决大部分“多实例重复跑任务”的问题。
比如 10.20.4.17 当前排序第一,那它执行。
17 挂了以后,Nacos 健康实例列表里没它了,下一轮自然变成 21 执行。
不需要再维护一个调度中心,也不用专门配置 executor
Nacos 节点选主,不等于严格的分布式锁。
比如节点刚好发生上下线、网络抖动,两个实例获取到的健康节点列表存在短暂差异,就可能出现同一个时间窗口执行两次。
这种场景我一般再补一道数据库幂等。
建一张很小的执行记录表:
create table job_fire_guard (
task_key varchar(80) not null,
fire_slot varchar(32) not null,
created_at datetime not null,
primary key (task_key, fire_slot)
);
真正跑任务之前,抢一下当前时间窗口
@Transactionalpublic boolean acquire(String task, String slot) { try {
jdbcTemplate.update("
insert into job_fire_guard(task_key, fire_slot, created_at)
values (?, ?, now())
", task, slot);
return true;
} catch (DuplicateKeyException e) {
return false;
}
}
@Scheduled(cron = "0 */5 * * * ?")
public void repair() {
if (!jobOwner.mine("order-service")) {
return;
}
String slot = LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyyMMddHHmm"));
if (!fireGuard.acquire("order-repair", slot)) {
return;
}
repairService.fixPendingOrders();
}

浙公网安备 33010602011771号