DB-Scheduler:面向Java的持久化、集群友好型调度器
[LOADING...]
1. 简介
在本教程中,我们将了解 db-scheduler,它是 Quartz 的一个更简单的替代方案。我们将了解它是什么、如何使用它,以及我们可以用它做什么。
2. 什么是 DB-Scheduler?
DB-Scheduler 是一个支持集群的持久化调度库。它的设计目标是让我们能够轻松地将其集成到应用程序中。我们唯一的要求是 Java 17+ 以及一个 JDBC 连接,连接到包含一张用于存储和管理任务的表的数据库。
使用 db-scheduler,我们可以轻松支持:
- 一次性任务:在特定时间点被调度运行一次的任务
- 简单重复任务:按照静态定义的固定时间表运行的任务
- 动态重复任务:按照固定时间表运行,但可以在运行时动态调度的任务
db-scheduler 库会为我们处理所有事情。这包括确保这些任务能在服务集群中正确运行,既不会丢失,也不会被执行多次。
3. 设置
在使用 db-scheduler 之前,我们需要先在应用程序中进行设置。这包括将其添加到项目中并让它运行起来。
3.1. 依赖
在使用 db-scheduler 之前,我们需要在构建中包含 最新版本,在撰写本文时该版本为 16.12.0。
如果我们使用 Maven,可以将此依赖添加到 pom.xml 文件中:
这会引入我们在应用程序中使用它所需的全部依赖。唯一必需的依赖是 JSpecify 和 SLF4J,其他一切都是自包含的。
3.2. 数据库模式
为了正常工作,db-scheduler 需要在其所连接的数据库中有一张数据表。 它为大多数主流 RDBMS 引擎提供了 DDL 脚本,尽管它也可能在其他数据库上运行。例如,要与 PostgreSQL 一起使用,我们可以使用如下 DDL:
这会创建我们的 scheduled_tasks 表,表结构符合预期,可供 db-scheduler 使用。
3.3. 调度器
最后,在使用 db-scheduler 之前,我们确实需要设置一个 Scheduler 实例。这是管理所有任务调度的核心类。
我们通过提供一个指向包含 scheduled_tasks 表的数据库的 DataSource,以及一个包含我们要管理的所有任务的集合来创建实例。稍后我们将看到如何创建这些:
它带有一组合理的默认配置值:
- 每 10 秒轮询一次
- 每 5 分钟发送一次心跳
- 若任务错过 6 次心跳,则将其视为死亡
- 在 10 个工作线程上运行任务
还有更多。我们可以在创建调度器时配置所有这些值:
同一集群中的多个实例至少使用相同的心跳设置,这一点非常重要。 否则,db-scheduler 可能会错误地将某些任务视为死亡。
一旦我们有了调度器,就需要启动它:
此时,调度器将管理分配给它的所有任务,包括确保它们被正确调度,并在适当的时间运行它们。
4. 简单重复任务
最容易处理的任务是简单重复任务。对于这类任务,我们需要一个唯一的名称、任务运行的频率,以及任务本身:
如果这是一个调度器从未见过的新任务,它将被安排为立即运行。之后,调度器将按照提供的时间表运行该任务。在本例中,这意味着在上一轮执行完成 1 小时后运行。我们还可以使用 Daily 来让任务在每天特定时间运行,并使用 CronSchedule 来使用更复杂的 cron 表达式。
我们的任务本身以 VoidExecutionHandler<T> 实例的形式提供,它本身是一个 函数式接口,如果我们愿意,可以通过 lambda 表达式来实现。该接口的唯一方法接受两个参数:
TaskInstance<T> instance—— 有关此任务实例的详细信息。如果我们有多个调度使用同一个任务实现,此参数会很有用。ExecutionContext context—— 有关执行器本身的详细信息。如果我们想从任务内部访问调度器本身,此参数会很有用。
请注意,这里我们使用的是 VoidExecutionHandler 和 RecurringTask<Void>,这是因为我们的示例任务是无状态的。我们也可以支持有状态任务,这类任务具有初始状态,并可以随着任务的执行而更新。不过,这些内容超出了本文的讨论范围。
5. 一次性任务
一次性任务的工作方式略有不同。在这种情况下,我们需要区分任务本身和标识该任务的描述符:
这个任务定义接受我们在调度任务时提供的某些数据,并可针对这些数据执行相应操作。在本例中,数据是 String 类型,但它可以是 db-scheduler 能够序列化的任何类型。默认情况下,它使用 Java 序列化,但 db-scheduler 也支持其他机制,例如 Jackson 和 Gson。
然后,我们需要在调度器中注册该任务。不过,我们可以通过将任务传递给 Scheduler.create() 方法来注册,这样 db-scheduler 就能知道它,但不会立即开始运行:
至此,我们可以随时使用调度器和任务描述符来运行任务:
在这里,我们必须为任务提供唯一的实例 ID,并指明任务应该在何时运行。我们还提供了一些任务数据供其处理。调度器随后会确保该任务在所需时间正确运行。
6. 动态重复任务
动态重复任务是在运行时动态注册的任务,与一次性任务类似。不过,一旦注册,它们就会像简单重复任务一样,持续按照时间表运行。
我们创建并启动这些任务的方式与一次性任务完全相同,唯一区别是使用 Tasks.recurring() 而不是 Tasks.oneTime():
这将在指定时间启动我们的任务,然后在启动后按照给定的时间表运行。在本例中,任务会在当前时间 15 秒后首次运行,之后每 5 秒运行一次。
如果我们愿意,可以按需调度任意多个不同的实例,它们都会按照各自的时间表运行,而不会相互干扰。
7. 与 Spring Boot 配合使用
如果我们使用 Spring Boot,db-scheduler 提供了一个我们可以使用的 starter 依赖:
如果我们使用的是 Spring Boot 4.x,则需要使用 db-scheduler-spring-boot-4-starter。如果我们仍在使用 Spring Boot 3.x,则应使用 db-scheduler-spring-boot-starter。
这将自动创建并启动我们的 Scheduler 实例,作为一个 Spring bean。同时,它还会自动发现任何已创建为 Spring bean 的任务,并将它们注册到此调度器中。这意味着我们唯一需要做的就是创建任务本身。
如果我们希望以任何方式配置 Scheduler,可以按照标准方式使用属性进行配置:
使用 db-scheduler 的其他一切,包括创建和调度任务的方式,都与之前完全相同。 Starter 只是让我们在一开始便能更轻松地上手。
8. 结论
在本文中,我们快速了解了 db-scheduler,包括如何调度重复任务和一次性任务。我们还可以用它做更多事情。下次当你需要为应用程序管理计划任务时,为什么不试一试呢?
和往常一样,本文的所有代码都可以在 GitHub 上找到。
这篇文章《DB-Scheduler:面向 Java 的持久化、集群友好的调度器》最初发表于 Baeldung。