首页
学习
活动
专区
圈层
工具
发布

想扔掉笨重的 XXL-JOB?试试这个基于 Nacos 的优雅调度方案

这种项目我现在看到 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,我现在确实会多问一句:

这套东西,到底是在解决业务问题,还是在增加一个以后要值班维护的问题?

小系统的调度,我更喜欢把事情控制在这几十行代码里。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OmXqJsDWPOZfL1Ue40HNnrjg0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。
领券