当前位置: 首页 > news >正文

定时任务Quartz原理详解

Quartz 工作原理

一、定时任务框架对比

特性维度**Spring Scheduler **QuartzXXL-JobElastic-JobPowerJob
核心定位Spring生态内嵌、轻量级功能强大、成熟可靠的任务调度库轻量级、易用的分布式任务调度平台基于Quartz的弹性分布式解决方案云原生、功能强大的分布式任务调度平台
依赖与集成与Spring框架无缝集成,无需额外依赖可独立使用,也可与Spring集成需要部署独立的调度中心执行器基于Zookeeper,已进入维护模式需要部署独立的调度中心执行器
分布式支持不支持,仅单机适用基于数据库,支持集群部署,但存在锁竞争支持,调度中心集群 + 执行器集群支持,基于Zookeeper注册中心,弹性分片支持,无中心化架构,支持无限水平扩展
任务分片不支持需要自行开发实现支持,是核心功能之一支持,是核心功能之一支持,功能强大,支持MapReduce式分片
任务类型固定速率、固定延迟、Cron表达式Cron表达式、简单触发器BEAN模式(内置)、GLUE模式(脚本)等简单型、数据流型、脚本型普通处理器工作流(DAG)、MapReduce、脚本等
可视化管控需自行开发或使用第三方UI提供完善的管理控制台,操作简单提供lite版控制台提供强大的管理控制台,功能丰富
调度方式基于注解,代码侵入式基于API和配置调度中心集中调度基于Zookeeper的事件通知调度中心集中调度
故障转移支持,集群中节点失败后任务由其他节点接管支持支持支持,且有更完善的健康检测机制
执行日志需自行收集需自行收集控制台提供日志查询控制台提供日志查询控制台提供日志查询,且性能优秀
适用场景简单的、单机的、周期性后台任务复杂的、集群的、需要持久化的企业级任务调度大部分互联网公司的分布式定时任务场景,易上手,运维方便高并发、大数据量的弹性分布式任务(目前社区不活跃)大规模分布式、复杂工作流场景,对稳定性和功能有极高要求

二、Quartz核心概念

1. Job(任务)

Job 是指需要被调度执行的具体任务逻辑,它是一个接口,用户需要实现该接口中的execute(JobExecutionContext context)方法,在该方法中编写具体的任务代码。例如,一个定时清理日志文件的任务,其逻辑就写在execute方法中。

需要注意的是,每次 Quartz 调度 Job 时,都会创建一个新的 Job 实例(除非配置了 Job 的持久化和复用策略),这是因为 Job 实例可能会被并发执行,避免了线程安全问题。

2. JobDetail(任务详情)

JobDetail 用于描述 Job 的详细信息,它封装了 Job 的相关配置,如 Job 的名称、组名、描述、是否持久化、是否可恢复等。Quartz 通过 JobDetail 来识别和管理 Job,而不是直接使用 Job 实例。

在创建 JobDetail 时,需要指定 Job 的实现类,同时可以通过JobDataMap向 Job 传递数据。JobDataMap是一个键值对集合,可以存储任意类型的数据,在 Job 的execute方法中,可以通过JobExecutionContext获取JobDataMap中的数据。

3. Trigger(触发器)

Trigger 用于定义 Job 的执行时间规则,它决定了 Job 何时被调度执行。Quartz 提供了多种类型的 Trigger,以满足不同的调度需求,常见的有以下两种:

  • SimpleTrigger:适用于简单的时间调度需求,如在指定时间执行一次任务,或者在指定时间开始,按照固定的时间间隔重复执行任务(如每隔 10 分钟执行一次)。它可以设置重复次数、重复间隔等属性。

  • CronTrigger:适用于复杂的时间调度需求,如每周一至周五的上午 9 点执行任务,每月最后一天的下午 5 点执行任务等。它通过 Cron 表达式来定义执行时间规则,Cron 表达式由秒、分、时、日、月、周、年(可选)七个部分组成,每个部分都有特定的取值范围和通配符。

4. Scheduler(调度器)

Scheduler 是 Quartz 的核心调度器,它负责管理 JobDetail 和 Trigger 的关联关系,并按照 Trigger 定义的时间规则调度 Job 的执行。Scheduler 是一个单例对象,在使用 Quartz 时,需要先创建 Scheduler 实例,然后将 JobDetail 和 Trigger 注册到 Scheduler 中,Scheduler 会根据 Trigger 的配置自动调度 Job 执行。

Scheduler 具有多种状态,如启动(STARTED)、暂停(PAUSED)、关闭(SHUTDOWN)等,只有在 Scheduler 处于启动状态时,才会执行任务调度。

5. JobExecutionContext(任务执行上下文)

JobExecutionContext 是 Job 执行时的上下文对象,它包含了 Job 执行所需的各种信息,如 JobDetail、Trigger、Scheduler、JobDataMap 等。在 Job 的execute方法中,可以通过 JobExecutionContext 获取这些信息,以便在任务执行过程中使用。例如,通过context.getJobDetail().getJobDataMap()可以获取 JobDetail 中存储的 JobDataMap 数据;通过context.getTrigger().getNextFireTime()可以获取 Trigger 下一次触发的时间。

6. JobDataMap

JobDataMap 是一个用于存储 Job 相关数据的键值对集合,它可以在创建 JobDetail 或 Trigger 时进行设置。JobDataMap 中的数据可以在 Job 的execute方法中通过 JobExecutionContext 获取,方便在任务执行过程中传递和使用数据。

JobDataMap 支持多种数据类型的存储,如基本数据类型(int、long、String 等)、自定义对象等。需要注意的是,如果存储的是自定义对象,该对象需要实现序列化接口(Serializable),以便在 Quartz 进行持久化操作时能够正常存储和恢复对象数据。

三、Quartz 工作流程

Quartz 的工作流程主要包括以下几个关键步骤,这些步骤环环相扣,共同实现了任务的自动调度执行。

1. 初始化 Scheduler

在使用 Quartz 进行任务调度之前,首先需要初始化 Scheduler。初始化过程主要包括以下操作:

  • 加载 Quartz 的配置文件(如 quartz.properties),配置文件中包含了 Scheduler 的相关配置信息,如线程池大小、持久化方式、触发器处理规则等。

  • 创建 SchedulerFactory 实例,SchedulerFactory 是用于创建 Scheduler 实例的工厂类,它根据配置文件中的配置信息创建相应的 Scheduler 实例。

  • 通过 SchedulerFactory 的getScheduler()方法获取 Scheduler 实例,此时 Scheduler 处于关闭状态,需要调用start()方法启动 Scheduler,使其进入启动状态,具备任务调度能力。

2. 创建 JobDetail 和 Trigger

根据实际的任务需求,创建对应的 JobDetail 和 Trigger:

  • 创建 JobDetail:通过JobBuilder类构建 JobDetail 实例,在构建过程中,指定 Job 的实现类、Job 的名称、组名、JobDataMap 等信息。例如:
JobDetail jobDetail = JobBuilder.newJob(MyJob.class).withIdentity("myJob", "jobGroup").usingJobData("param1", "value1").usingJobData("param2", 123).build();

其中,MyJob是实现了 Job 接口的自定义任务类;withIdentity方法用于设置 Job 的名称和组名,名称和组名的组合构成了 Job 的唯一标识;usingJobData方法用于向 JobDataMap 中添加数据。

  • 创建 Trigger:根据任务的调度需求,选择合适的 Trigger 类型(SimpleTrigger 或 CronTrigger),通过相应的 Builder 类(SimpleScheduleBuilder 或 CronScheduleBuilder)构建 Trigger 实例。例如,创建一个 SimpleTrigger,在当前时间 10 秒后执行一次任务:
SimpleTrigger simpleTrigger = TriggerBuilder.newTrigger().withIdentity("mySimpleTrigger", "triggerGroup").startAt(new Date(System.currentTimeMillis() + 10000)).withSchedule(SimpleScheduleBuilder.simpleSchedule()).build();

再如,创建一个 CronTrigger,每天上午 9 点执行任务:

CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("myCronTrigger", "triggerGroup").withSchedule(CronScheduleBuilder.cronSchedule("0 0 9 * * ?")).build();

3. 将 JobDetail 和 Trigger 注册到 Scheduler

通过 Scheduler 的scheduleJob(JobDetail jobDetail, Trigger trigger)方法,将创建好的 JobDetail 和 Trigger 注册到 Scheduler 中。在注册过程中,Scheduler 会验证 JobDetail 和 Trigger 的合法性,如 Job 的实现类是否存在、Trigger 的时间规则是否有效等。如果验证通过,Scheduler 会将 JobDetail 和 Trigger 的关联关系存储起来,并根据 Trigger 的配置信息,将 Trigger 加入到相应的触发器调度队列中。

4. Scheduler 调度任务执行

Scheduler 启动后,会不断地从触发器调度队列中获取已到触发时间的 Trigger,并按照以下流程调度 Job 执行:

  • Trigger 触发检查:Scheduler 内部会有一个线程(TriggerChecker 线程)定期检查触发器调度队列中的 Trigger,判断 Trigger 是否到达触发时间。对于 SimpleTrigger,会根据其开始时间、重复间隔和重复次数计算下一次触发时间;对于 CronTrigger,会根据 Cron 表达式计算下一次触发时间。当 Trigger 到达触发时间时,TriggerChecker 线程会将该 Trigger 标记为待执行状态,并将其放入到任务执行队列中。

  • 任务执行线程池获取线程:Scheduler 内部维护了一个任务执行线程池(ThreadPool),线程池中的线程用于执行具体的 Job 任务。当任务执行队列中有待执行的 Trigger 时,线程池会从队列中取出 Trigger,并为其分配一个空闲的线程。

  • 创建 Job 实例并执行:任务执行线程获取到 Trigger 后,会根据 Trigger 关联的 JobDetail 信息,通过反射的方式创建 Job 实例。然后,线程会创建 JobExecutionContext 实例,将 JobDetail、Trigger、Scheduler、JobDataMap 等信息封装到 JobExecutionContext 中,并调用 Job 实例的execute(JobExecutionContext context)方法,执行具体的任务逻辑。

  • 任务执行结果处理:Job 执行完成后,任务执行线程会根据 Job 的执行结果(成功或失败)进行相应的处理。如果 Job 执行成功,对于需要重复执行的 Trigger(如 SimpleTrigger 设置了重复次数,CronTrigger 有下一次触发时间),Scheduler 会计算 Trigger 的下一次触发时间,并将其重新加入到触发器调度队列中,等待下一次触发;如果 Job 执行失败,Scheduler 会根据配置的失败处理策略(如重试机制)进行处理,如重新执行 Job、暂停 Trigger 等。同时,Scheduler 会记录 Job 的执行日志,包括执行时间、执行结果、异常信息等,以便后续的监控和排查问题。

四、Quartz 线程模型

Quartz 的线程模型是其高效调度任务的关键,合理的线程模型设计能够保证任务的及时执行和系统的稳定性。Quartz 的线程模型主要包括以下几种线程:

1. TriggerChecker 线程

TriggerChecker 线程是 Scheduler 内部的一个核心线程,其主要职责是定期检查触发器调度队列中的 Trigger,判断 Trigger 是否到达触发时间。TriggerChecker 线程的检查间隔可以通过 Quartz 的配置文件进行配置(默认情况下,检查间隔为 1 秒)。当 TriggerChecker 线程发现某个 Trigger 到达触发时间时,会将该 Trigger 标记为待执行状态,并将其放入到任务执行队列中,等待任务执行线程池中的线程进行处理。

2. 任务执行线程池(ThreadPool)中的线程

任务执行线程池是 Scheduler 用于执行 Job 任务的线程池,线程池中的线程数量可以通过配置文件进行设置(默认情况下,线程池大小为 10)。线程池采用了线程池的经典设计模式,通过预先创建一定数量的线程,避免了每次执行任务时创建和销毁线程的开销,提高了任务执行的效率。

当任务执行队列中有待执行的 Trigger 时,线程池会从队列中取出 Trigger,并为其分配一个空闲的线程。线程在执行 Job 任务时,会按照前面提到的流程创建 Job 实例、构建 JobExecutionContext,并调用 Job 的execute方法执行任务。任务执行完成后,线程会释放资源,回到线程池中,等待下一次任务的分配。

线程池的大小需要根据实际的业务需求和系统资源情况进行合理配置。如果线程池的线程数量过少,可能会导致大量任务排队等待执行,影响任务的及时执行;如果线程池的线程数量过多,会占用过多的系统资源(如内存、CPU),可能会导致系统性能下降。

3. 其他辅助线程

除了 TriggerChecker 线程和任务执行线程池中的线程外,Quartz 还会创建一些其他的辅助线程,用于处理特定的功能,如:

  • 持久化线程:如果 Quartz 采用了持久化的方式存储 JobDetail 和 Trigger 信息(如使用数据库进行持久化),则会创建持久化线程,用于将 JobDetail 和 Trigger 的信息写入数据库,以及从数据库中读取相关信息。

  • 监听线程:Quartz 支持事件监听机制,用户可以通过实现相应的监听器接口(如 JobListener、TriggerListener、SchedulerListener)来监听 Job 的执行、Trigger 的触发以及 Scheduler 的状态变化等事件。为了处理这些监听事件,Quartz 会创建监听线程,当相应的事件发生时,监听线程会调用监听器中的相应方法,通知监听器进行处理。

五、Quartz 持久化

在默认情况下,Quartz 将 JobDetail 和 Trigger 的信息存储在内存中,这种方式具有较高的性能,但存在一个明显的缺点:当 Quartz 服务重启或服务器宕机时,存储在内存中的 JobDetail 和 Trigger 信息会丢失,导致任务无法继续执行。为了解决这个问题,Quartz 提供了持久化功能,能够将 JobDetail 和 Trigger 的信息存储到持久化介质中(如数据库),以便在 Quartz 服务重启或服务器宕机后,能够从持久化介质中恢复 JobDetail 和 Trigger 信息,保证任务的正常执行。

1. 持久化原理

Quartz 的持久化功能主要通过 JDBCJobStore 实现,JDBCJobStore 是 Quartz 提供的一个用于将 JobDetail 和 Trigger 信息存储到数据库中的 JobStore 实现类。其持久化原理如下:

  • 数据库表结构:Quartz 在数据库中创建了一系列的表,用于存储不同类型的信息,如 qrtz_job_details 表用于存储 JobDetail 的信息,qrtz_triggers 表用于存储 Trigger 的信息,qrtz_cron_triggers 表用于存储 CronTrigger 的特定信息,qrtz_simple_triggers 表用于存储 SimpleTrigger 的特定信息,qrtz_fired_triggers 表用于存储已触发的 Trigger 信息,qrtz_scheduler_state 表用于存储 Scheduler 的状态信息等。

  • 数据存储过程:当用户将 JobDetail 和 Trigger 注册到 Scheduler 中时,JDBCJobStore 会将 JobDetail 和 Trigger 的信息转换为 SQL 语句,插入到相应的数据库表中。例如,将 JobDetail 的名称、组名、Job 类名、JobDataMap 等信息插入到 qrtz_job_details 表中;将 Trigger 的名称、组名、关联的 Job 名称和组名、触发时间规则等信息插入到 qrtz_triggers 表中,同时根据 Trigger 的类型,将其特定信息插入到对应的表中(如 CronTrigger 的 Cron 表达式插入到 qrtz_cron_triggers 表中)。

  • 数据读取和恢复过程:当 Quartz 服务启动时,JDBCJobStore 会从数据库中读取 Scheduler 的状态信息(如是否处于启动状态),以及所有已注册的 JobDetail 和 Trigger 信息,并将这些信息加载到内存中,恢复 Scheduler 的状态和任务调度信息。在任务调度过程中,当 Trigger 触发时,JDBCJobStore 会更新数据库中相应 Trigger 的状态信息(如将 Trigger 的下一次触发时间更新为下一次计算出的时间);当 Job 执行完成后,JDBCJobStore 会更新数据库中 Job 的执行记录信息(如执行时间、执行结果等)。

2. 持久化配置

要启用 Quartz 的持久化功能,需要进行以下配置:

  • 添加数据库驱动依赖:在项目的依赖管理文件(如 Maven 的 pom.xml)中添加相应数据库的驱动依赖,如 MySQL 的驱动依赖:
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version>
</dependency>
  • 配置 Quartz 的 JobStore:在 Quartz 的配置文件(quartz.properties)中,将 JobStore 的实现类配置为 JDBCJobStore,并配置数据库连接信息。例如:
# 配置JobStore实现类
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
# 配置数据库驱动
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# 配置数据库连接URL
org.quartz.jobStore.dataSource = myDS
org.quartz.dataSource.myDS.driver = com.mysql.cj.jdbc.Driver
org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3306/quartz_db?useSSL=false&serverTimezone=UTC
org.quartz.dataSource.myDS.user = root
org.quartz.dataSource.myDS.password = 123456
# 配置数据库连接池大小
org.quartz.dataSource.myDS.maxConnections = 10
# 配置表前缀
org.quartz.jobStore.tablePrefix = QRTZ_
# 配置是否使用集群(如果需要在集群环境下使用Quartz,需要将此属性设置为true)
org.quartz.jobStore.isClustered = false

其中,JobStoreTX表示使用 JDBC 事务来管理 JobStore 的操作,保证数据的一致性;StdJDBCDelegate是 Quartz 提供的一个标准 JDBC 委托类,用于适配不同的数据库;tablePrefix用于指定 Quartz 在数据库中创建的表的前缀,默认前缀为QRTZ_isClustered用于配置是否使用集群模式,如果在集群环境下使用 Quartz,需要将此属性设置为true,以实现多个 Scheduler 实例之间的协同工作。

3. 持久化注意事项

在使用 Quartz 的持久化功能时,需要注意以下几点:

  • 数据库表创建:在启用持久化功能之前,需要手动在数据库中创建 Quartz 所需的表。Quartz 提供了不同数据库的表创建脚本,这些脚本可以在 Quartz 的安装包或 Maven 依赖包中找到(如在quartz-core依赖包的org/quartz/impl/jdbcjobstore目录下)。根据所使用的数据库类型,选择相应的脚本执行即可创建所需的表。

  • 序列化问题:如果 JobDataMap 中存储了自定义对象,该对象必须实现Serializable接口,否则在将 JobDetail 和 Trigger 信息存储到数据库时,会抛出序列化异常。这是因为 JDBCJobStore 在存储 JobDataMap 数据时,会将其序列化为二进制数据后存储到数据库的相应字段中。

  • 集群环境配置:如果在集群环境下使用 Quartz 的持久化功能,需要将isClustered属性设置为true,同时确保所有集群节点使用同一个数据库,以便实现多个 Scheduler 实例之间的信息共享和协同工作。在集群环境中,Quartz

http://www.dtcms.com/a/498959.html

相关文章:

  • Rethinking SSIM-Based Optimization in Neural Field Training
  • rocketmq和kafka的区别之顺序消费
  • 套路有*道龙激光-乐多刀销*游戏程序系统方案
  • Angular 2 数据显示
  • 如何快速做单页面网站怎么查网站建设是哪家公司
  • 外国网站备案网站板块设置
  • 从 ClickHouse 到 StarRocks 存算分离: 携程 UBT 架构升级实践
  • 云手机 三角洲行动跑刀
  • Java 反射机制深度解析:从对象创建到私有成员操作
  • c++|表达最值的更好方法|clamp
  • Altium Designer(AD24)File文件功能总结
  • 【EE初阶 - 网络原理】应用层协议(下)
  • Pyinstaller - Python桌面应用打包的首选工具
  • PHP编程语言选择
  • 太原市做网站专业团队广告语
  • 桂林设计单位资质升级网站手机怎么建网站
  • k8s问题详解1:k8s集群上传文件过大导致413 Request Entity Too Large(请求文件实体过大)
  • 计算机毕业设计:Python农业数据可视化分析系统 气象数据 农业生产 粮食数据 播种数据 爬虫 Django框架 天气数据 降水量(源码+文档)✅
  • 怎么做一个链接网站东莞直播app软件开发定制
  • VSCode - 设置MSYS2终端
  • MATLAB学习文档(二十五)
  • 告别“静态”VI手册:InDesign与AE打造可交互的动态品牌规范
  • 推广网站有那些个人网站免费域名注册
  • Windows EXCEPTION_RECORD 结构深度解析
  • LangGraph学习笔记(四):langgraph本地的流式输出
  • Phoenix Code:一款专为Web开发打造的开源文本编辑器
  • 网络营销的基本特征有哪七个wordpress宝塔优化
  • pyvideotrans问题
  • Large-scale CelebFaces Attributes (CelebA) 数据集生态:核心详解、免费下载与三大扩展应用全景​
  • 【BuildFlow 筑流】品牌命名与项目定位说明