4 Quartz 集成到 Spring
Spring-quartz 工程
Spring 在 spring-context-support.jar 中直接提供了對 Quartz 的支持
可以在配置文件中把 JobDetail、Trigger、Scheduler 定義成 Bean。
4.1 定義 Job
<bean name="myJob1" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="name" value="my_job_1"/>
<property name="group" value="my_group"/>
<property name="jobClass" value="com.gupaoedu.quartz.MyJob1"/>
<property name="durability" value="true"/>
</bean>
4.2 定義 Trigger
<bean name="simpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
<property name="name" value="my_trigger_1"/>
<property name="group" value="my_group"/>
<property name="jobDetail" ref="myJob1"/>
<property name="startDelay" value="1000"/>
<property name="repeatInterval" value="5000"/>
<property name="repeatCount" value="2"/>
</bean>
4.3 定義 Scheduler
<bean name="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="simpleTrigger"/>
<ref bean="cronTrigger"/>
</list>
</property>
</bean>
既然可以在配置文件配置,當然也可以用@Bean 注解配置。在配置類上加上@Configuration 讓 Spring 讀取到。
public class QuartzConfig {
@Bean
public JobDetail printTimeJobDetail(){
return JobBuilder.newJob(MyJob1.class)
.withIdentity("gupaoJob")
.usingJobData("gupao", "職位更好的你")
.storeDurably()
.build();
}
@Bean
public Trigger printTimeJobTrigger() {
CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/5 * * * * ?");
return TriggerBuilder.newTrigger()
.forJob(printTimeJobDetail())
.withIdentity("quartzTaskService")
.withSchedule(cronScheduleBuilder)
.build();
}
}
5 動態(tài)調(diào)度的實現(xiàn)
springboot-quartz 工程
傳統(tǒng)的 Spring 方式集成,由于任務(wù)信息全部配置在 xml 文件中,如果需要操作任務(wù)或者修改任務(wù)運行頻率,只能重新編譯、打包、部署、重啟,如果有緊急問題需要處理,會浪費很多的時間。
有沒有可以動態(tài)調(diào)度任務(wù)的方法?比如停止一個 Job?啟動一個 Job?修改 Job 的觸發(fā)頻率?
讀取配置文件、寫入配置文件、重啟 Scheduler 或重啟應(yīng)用明顯是不可取的。
對于這種頻繁變更并且需要實時生效的配置信息,我們可以放到哪里?
ZK、Redis、DB tables。
并且,我們可以提供一個界面,實現(xiàn)對數(shù)據(jù)表的輕松操作。
5.1 配置管理
這里我們用最簡單的數(shù)據(jù)庫的實現(xiàn)。
問題 1:建一張什么樣的表?參考 JobDetail 的屬性
CREATE TABLE `sys_job`(
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID', `job_name` varchar(512) NOT NULL COMMENT '任務(wù)名稱', `job_group` varchar(512) NOT NULL COMMENT '任務(wù)組名', `job_cron` varchar(512) NOT NULL COMMENT '時間表達式', `job_class_path` varchar(1024) NOT NULL COMMENT '類路徑,全類型', `job_data_map` varchar(1024) DEFAULT NULL COMMENT '傳遞 map 參數(shù)', `job_status` int(2) NOT NULL COMMENT '狀態(tài):1 啟用 0 停用', `job_describe` varchar(1024) DEFAULT NULL COMMENT '任務(wù)功能描述', PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=25 DEFAULT CHARSET=utf8;
5.2 數(shù)據(jù)操作與任務(wù)調(diào)度
操作數(shù)據(jù)表非常簡單,SSM 增刪改查。
但是在修改了表的數(shù)據(jù)之后,怎么讓調(diào)度器知道呢?
調(diào)度器的接口:Scheduler
在我們的需求中,我們需要做的事情:
1、 新增一個任務(wù)
2、 刪除一個任務(wù)
3、 啟動、停止一個任務(wù)
4、 修改任務(wù)的信息(包括調(diào)度規(guī)律)
5.3 容器啟動與 Service 注入
5.3.1 容器啟動
因為任務(wù)沒有定義在 ApplicationContext.xml 中,而是放到了數(shù)據(jù)庫中,Spring Boot 啟動時,怎么讀取任務(wù)信息?
或者,怎么在 Spring 啟動完成的時候做一些事情?
創(chuàng)建一個類,實現(xiàn) CommandLineRunner 接口,實現(xiàn) run 方法。
從表中查出狀態(tài)是 1 的任務(wù),然后構(gòu)建。
5.3.2 Service 類注入到 Job 中
Spring Bean 如何注入到實現(xiàn)了 Job 接口的類中?
例如在 TestTask3 中,需要注入 ISysJobService,查詢數(shù)據(jù)庫發(fā)送郵件。
如果沒有任何配置,注入會報空指針異常。
原因:
因為定時任務(wù) Job 對象的實例化過程是在 Quartz 中進行的,而 Service Bean 是由Spring 容器管理的,Quartz 察覺不到 Service Bean 的存在,所以無法將 Service Bean裝配到 Job 對象中。
分析:
Quartz 集成到 Spring 中,用到 SchedulerFactoryBean,其實現(xiàn)了 InitializingBean方法,在唯一的方法 afterPropertiesSet()在 Bean 的屬性初始化后調(diào)用。調(diào)度器用 AdaptableJobFactory 對 Job 對象進行實例化。所以,如果我們可以把這個 JobFactory 指定為我們自定義的工廠的話,就可以在 Job 實例化完成之后,把 Job納入到 Spring 容器中管理。
解決這個問題的步驟:
1、定義一個 AdaptableJobFactory,實現(xiàn) JobFactory 接口,實現(xiàn)接口定義的newJob 方法,在這里面返回 Job 實例
2、定義一個 MyJobFactory,繼承 AdaptableJobFactory。
使用 Spring 的 AutowireCapableBeanFactory,把 Job 實例注入到容器中。
@Component
public class MyJobFactory extends AdaptableJobFactory {
@Autowired
private AutowireCapableBeanFactory capableBeanFactory;
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
Object jobInstance = super.createJobInstance(bundle);
capableBeanFactory.autowireBean(jobInstance);
return jobInstance;
}
}
3、指定 Scheduler 的 JobFactory 為自定義的 JobFactory。
scheduler.setJobFactory(myJobFactory);
6 Quartz 集群部署
springboot-quartz 工程
6.1 為什么需要集群?
1、防止單點故障,減少對業(yè)務(wù)的影響
2、減少節(jié)點的壓力,例如在 10 點要觸發(fā) 1000 個任務(wù),如果有 10 個節(jié)點,則每個節(jié)點之需要執(zhí)行 100 個任務(wù)
6.2 集群需要解決的問題?
1、任務(wù)重跑,因為節(jié)點部署的內(nèi)容是一樣的,到 10 點的時候,每個節(jié)點都會執(zhí)行相同的操作,引起數(shù)據(jù)混亂。比如跑批,絕對不能執(zhí)行多次。
2、任務(wù)漏跑,假如任務(wù)是平均分配的,本來應(yīng)該在某個節(jié)點上執(zhí)行的任務(wù),因為節(jié)點故障,一直沒有得到執(zhí)行。
3、水平集群需要注意時間同步問題
4、Quartz 使用的是隨機的負載均衡算法,不能指定節(jié)點執(zhí)行
所以必須要有一種共享數(shù)據(jù)或者通信的機制。在分布式系統(tǒng)的不同節(jié)點中,我們可以采用什么樣的方式,實現(xiàn)數(shù)據(jù)共享?
兩兩通信,或者基于分布式的服務(wù),實現(xiàn)數(shù)據(jù)共享。
例如:ZK、Redis、DB。
在 Quartz 中,提供了一種簡單的方式,基于數(shù)據(jù)庫共享任務(wù)執(zhí)行信息。也就是說,一個節(jié)點執(zhí)行任務(wù)的時候,會操作數(shù)據(jù)庫,其他的節(jié)點查詢數(shù)據(jù)庫,便可以感知到了。
同樣的問題:建什么表?哪些字段?依舊使用系統(tǒng)自帶的 11 張表
6.3 集群配置與驗證
quartz.properties 配置。
四個配置:集群實例 ID、集群開關(guān)、數(shù)據(jù)庫持久化、數(shù)據(jù)源信息
注意先清空 quartz 所有表、改端口、兩個任務(wù)頻率改成一樣
驗證 1:先后啟動 2 個節(jié)點,任務(wù)是否重跑
驗證 2:停掉一個節(jié)點,任務(wù)是否漏
7 Quartz 調(diào)度原理
問題:
1、Job 沒有繼承 Thread 和實現(xiàn) Runnable,是怎么被調(diào)用的?通過反射還是什么?
2、任務(wù)是什么時候被調(diào)度的?是誰在監(jiān)視任務(wù)還是監(jiān)視 Trigger?
3、任務(wù)是怎么被調(diào)用的?誰執(zhí)行了任務(wù)?
4、任務(wù)本身有狀態(tài)嗎?還是觸發(fā)器有狀態(tài)?
看源碼的入口文章來源:http://www.zghlxwxcb.cn/news/detail-774879.html
Scheduler scheduler = factory.getScheduler();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
7.1 獲取調(diào)度器實例
7.1.1 讀取配置文件
public Scheduler getScheduler() throws SchedulerException {
if (cfg == null) {
// 讀取 quartz.properties 配置文件
initialize();
}
// 這個類是一個 HashMap,用來基于調(diào)度器的名稱保證調(diào)度器的唯一性
SchedulerRepository schedRep = SchedulerRepository.getInstance();
Scheduler sched = schedRep.lookup(getSchedulerName());
// 如果調(diào)度器已經(jīng)存在了
if (sched != null) {
// 調(diào)度器關(guān)閉了,移除
if (sched.isShutdown()) {
schedRep.remove(getSchedulerName());
} else {
// 返回調(diào)度器
return sched;
}
}
// 調(diào)度器不存在,初始化
sched = instantiate();
return sched;
}
instantiate()方法中做了初始化的所有工作:文章來源地址http://www.zghlxwxcb.cn/news/detail-774879.html
// 存儲任務(wù)信息的 JobStore
JobStore js = null;
// 創(chuàng)建線程池,默認是 SimpleThreadPool
ThreadPool tp = null;
// 創(chuàng)建調(diào)度器
QuartzScheduler qs = null;
// 連接數(shù)據(jù)庫的連接管理器
DBConnectionManager dbMgr = null;
// 自動生成 ID
// 創(chuàng)建線程執(zhí)行器,默認為 DefaultThreadExecutor
ThreadExecutor threadExecutor;
到了這里,關(guān)于分布式之任務(wù)調(diào)度學(xué)習(xí)二的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!