这种项目我现在看到 XXL-JOB,第一反应不是“成熟稳定”,而是先看一眼:真有必要上这么重吗?
如果任务只是清理过期订单、同步状态、刷新缓存、跑个对账,没有复杂工作流,也不要求跨系统编排,我更愿意直接用:
Spring TaskScheduler + Nacos 配置中心 + 分布式锁。
项目里本来就在用 Nacos,没必要为了几个定时任务再养一套调度平台。
Spring 自己就提供了TaskScheduler这套调度抽象,可以通过代码动态注册任务,不一定非得把 Cron 写死在@Scheduled里。
比如订单服务有一个关闭超时订单的任务,我会把配置直接扔 Nacos:
jobs:
close-expired-order:
enabled: true
cron: "0 */2 * * * ?"
这里有个细节。
我一般不会这么写:
@Scheduled(cron = "0 */2 * * * ?")
public void closeOrder() {
// ...
}
开发环境没问题,等运营哪天说“两分钟改成五分钟”,你就得改代码、发版、重启。
这种定时任务我第一刀就会把 Cron 从代码里切出去。
Nacos 本身支持配置读取和配置监听,配置变更后客户端可以收到通知。
核心代码不用搞得很花:
@Component
public class OrderJobRegistry {
private final ThreadPoolTaskScheduler scheduler;
private final Map<String, ScheduledFuture<?>> running = new ConcurrentHashMap<>();
public OrderJobRegistry(ThreadPoolTaskScheduler scheduler) {
this.scheduler = scheduler;
}
public synchronized void reload(String jobCode,
boolean enabled,
String cron,
Runnable action) {
ScheduledFuture<?> old = running.remove(jobCode);
if (old != null) {
old.cancel(false);
}
if (!enabled) {
return;
}
ScheduledFuture<?> future =
scheduler.schedule(action, new CronTrigger(cron));
running.put(jobCode, future);
}
}
Nacos 配置发生变化后,重新调用一次reload()。
改 Cron,不重启。
临时停任务,也不用发版。
这一层做完,其实已经把 XXL-JOB 后台最常用的“修改执行周期、启停任务”解决掉了。
但还有个坑。
服务如果部署了 4 个实例,这段代码会执行 4 次。
这个地方我最烦那种“判断一下机器 IP,只让第一台跑”的写法。机器一扩缩容,或者 K8s Pod 重建,迟早给自己埋雷。
调度触发可以每台机器都有,真正执行前抢锁。
Nacos 3.x Java SDK 已经提供了LockService和NLock,同一个任务使用同一个 key,拿到锁的实例才真正干活。
代码我一般收在任务入口:
public void closeExpiredOrders() {
NLock lock = new NLock(
"job:order:close-expired",
90_000L
);
boolean acquired = false;
try {
acquired = lockService.lock(lock);
if (!acquired) {
return;
}
int changed = orderRepository.closeExpiredOrders();
log.info("expired order job finished, affected={}", changed);
} catch (Exception e) {
log.error("expired order job failed", e);
} finally {
if (acquired) {
try {
lockService.unLock(lock);
} catch (Exception e) {
log.warn("release job lock failed", e);
}
}
}
}
这样整个链路就比较干净了:
Nacos 改 Cron 应用监听配置 重新注册任务 到点触发 多实例抢锁 一台执行。
没有单独的调度中心,也没有 Executor 回调那一堆东西。
不过这里我得泼点冷水。
Nacos 3.x 的分布式锁目前官方仍然标记为实验性能力。官方文档也明确提醒生产使用前要充分验证,而且锁相关的运维、监听能力目前还不算完整。
所以生产项目我会分两种情况。
内部系统、普通补偿任务、允许幂等重跑的任务,可以评估直接用 Nacos Lock。
账务、结算、扣款这种任务,我不会拿实验特性赌。Nacos 继续负责 Cron 和启停,锁换 Redis、数据库或者现成的成熟分布式锁实现,调度骨架完全不用动。
还有一个容易被忽略的地方:锁的过期时间一定得覆盖正常任务执行时间。
任务正常跑 3 分钟,你锁只给 30 秒,30 秒之后另一台机器又拿到锁,那不是高可用,是同一个任务两台机器一起干。
所以任务本身最好也做幂等。
那 XXL-JOB 是不是就没用了?
当然不是。
如果已经有几十上百个任务,还要失败重试、执行记录、人工触发、分片、复杂调度管理,我不会为了“少部署一个组件”自己造半套调度平台。
但一个普通 Spring Boot 服务,手里就那么几个定时任务,本来已经接了 Nacos,还专门拉一整套 XXL-JOB,我现在确实会多问一句:
这套东西,到底是在解决业务问题,还是在增加一个以后要值班维护的问题?
小系统的调度,我更喜欢把事情控制在这几十行代码里。