原文:Quertz 官方文档
最佳实践
生产环境
跳过更新检查
Quartz 包含一个连接服务器检查是否有新版本下载的特性。
这个检查是异步的而且不影响 Quartz 的启动和初始化,这个连接不能完成也不会有什么毛病。当检查发现有更新,Quartz 的日志会反映有可用的更新。
你可以通过 Quartz 配置“org.quartz.scheduler.skipUpdateCheck: true”或者系统配置“org.terracotta.quartz.skipUpdateCheck=true”(可以在系统环境变量设置或者在 Java 命令行里添加 -D)来关闭这个更新检查。我们推荐在生成环境关闭这个更新提示。
JobDataMap
JobDataMap 只存储基本数据类型(如 String 类型)
从长远来看 JobDataMap 只存储基本数据类型(如 String 类型) 可以避免数据序列化的问题。
使用混合的 JobDataMap
Job 执行期间可以便利地在 JobExecutionContext 里找到 JobDataMap
JobExecutionContext 合并了在 JobDetail 里的 JobDataMap 和在 Trigger 里的JobDataMap ,后者的值会覆盖前者所有同名的示例。
当你有一个存储在调度器给多个触发器定期重复地使用的 Job 时,在 Trigger 里存储的 JobDataMap 会很有用,即使你想为每个独立的触发器的任务提供不同的数据输入。
基于以上几点,我们推荐如下最近实践:
在 Job.execute(..) 方法里面的代码一般需要取回来自
JobExecutionContext 里的 JobDataMap 的值而不是直接拿 JobDetail 里的。
触发器
使用触发器工具类
触发器工具类:
- 提供一个更简单的方式创建触发器(调度器)
- 有多个方法去创建带有调度器的触发器以满足特定的场景,如通过某个类型(如 SimpleTrigger, CronTrigger, 等)直接地实例化触发器然后调用 setter 方法配置它们。
- 提供一个简单的方式创建日期 (开始/结束日期)
- 提供分析触发器的帮助(如计算未来的调度时间)
JDBC 任务存储
永远不要直接往 QRTZ 表写数据
不通过 API 而直接往数据库(通过 SQL )写入调度数据会造成以下问题:
- 造成数据污染 (已删除的数据, 爬取的数据)
- 造成任务在到达触发时间的时候触发器没有执行看起来像是“消失了”一样
- 造成任务在到达触发时间的时候触发器没有“落地”执行
- 可能发生死锁
- 其它诡异问题和数据污染
永远不要在同一个数据库里出现和其他实例同名的非集群调度器
如果你在同一个数据库集里指定多个调度器实例,而存在一个或多个实例没有配置成集群,可能会发生以下的情况:
- 造成数据污染 (已删除的数据, 爬取的数据)
- 造成任务在到达触发时间的时候触发器没有执行看起来像是“消失了”一样
- 造成任务在到达触发时间的时候触发器没有“落地”执行
- 可能发生死锁
- 其它诡异问题和数据污染
确保有足够的数据源连接数
我们推荐数据源最大连接数最少设置为 Quartz 线程池的 worker 数目的 3 倍。如果你的应用同样频繁地调用调度器的API你也许需要额外的连接。如果你使用 JobStoreCMT ,不受管理的数据源最大连接数最少是 4。
Daylight Savings Time(DST)
避免任务调度发生在 DST 前后
NOTE: 详细的 DST 发生时间和当地时钟拨快或拨慢的幅度请查看这里:https://secure.wikimedia.org/wikipedia/en/wiki/Daylight_saving_time_around_the_world.
SimpleTriggers 不受 DST 影响,因为它们总是在几毫秒之内调度完毕,并相隔几毫秒重复执行。
因为 CronTriggers 调度需要制定小时/分钟/秒,他们可能会在
DST 过渡期发生一些诡异的情况。
举个例子,在美国时区调度器在 DST 时间附近,如果使用了 CronTrigger 并且调度时间在 1:00 AM - 2:00 AM 之间,可能会发生以下问题:
- 1:05 AM 可能出现两次!- 对于 CronTrigger 可能发生重复调度
- 2:05 AM 可能不会来临! - 对于 CronTrigger 可能丢失调度
再次申明,具体的时间和调整的数量因地方不同而异。
其他基于日历时间流动的触发器类型(与之对应的是特定时间间隔),例如 CalenderIntervalTrigger ,同样会受到相似的影响 - 但并不是丢失或重复调度,而是可能会在一小时内停止自身调度。
任务
多条件待命
长时间运行的任务可能会阻止其他任务运行(如果所有在线程池里的线程都繁忙的话)。
如果你认为在执行任务的工作线程方法需要调用 Thread.sleep() 方法,这通常是任务还没准备就绪去执行它自身其余工作的标志,因为它需要等待一些条件(例如一些可用数据)达成。
一个更好的解决办法是释放工作线程(退出任务)然后允许其他同类任务在这个线程上执行。这个任务可以自身重新调度或者其他同类任务在它之前退出
抛出异常
某个任务的执行方法可能包含处理各种可能异常的 try-catch 代码块
如果一个任务抛出一个异常,Quartz 通常会立即重新执行它(通常它也会再次抛出同样的异常)。更好的办法是让这个任务或者其他任务接收所有它可能发生的异常并处理它们并重新发起调度。从而绕过这个问题。
重试和幂等
被标记为“可恢复”的进行中的任务会在调度器失败后被自动重新执行。这意味着某些任务的“工作”将会执行两次。
这意味着这个任务的代码需要用某种方式实现幂等。
监听器(TriggerListener, JobListener, SchedulerListener)
监听器里的代码保持简洁高效
演算大量的工作是不可取的,因为执行任务(或者结束触发器,跑去调度另一个任务,等等)的线程在监听器里将会很繁忙。
异常处理
每个监听器都会包含处理各种可能异常的 try-catch 代码块。
如果一个监听器抛出了异常,可能会造成其他监听器没被通知甚至组织任务的执行等后果。
通过应用暴露调度器的功能
谨慎对待安全问题!
一些使用者通过应用界面暴露了 Quartz 的调度功能。这是很有用的,但同时也可能存在巨大的风险。
确保你没有错误地允许用户定义他们想要的任务类型和参数类型。举个例子,Quartz 预置了一个任务 org.quartz.jobs.NativeJob ,这是个能直接执行它定义的本地(操作系统)系统命令。 恶意用户可能会使用它来控制,破坏你的系统。
同样地,其他任务例如 SendEmailJob 会被用于恶意使用。
运行用户定义他们想要的任务很容易让你的系统受到像Command Injection Attacks里面定义的各种攻击一样脆弱。
网友评论