iT邦幫忙

2021 iThome 鐵人賽

DAY 21
0
Software Development

Wow ! There is no doubt about Learn Spring framework in a month.系列 第 21

[Day - 21] - 規律的一天從Spring Scheduled 開始

Abstract

大家每天都是新的開始,都有24H小時給你規劃,系統跟人類一樣都是有自己的週期性計畫,人類需要休息進行處理其他工作,系統也是一樣需要有一段時間休息避開顛峰時間,故我們會先部分週期性的工作放在系統較不繁忙的時候去執行這些任務,此時我們已有許多週期性工作的套件,如:Quartz Scheduler、jcrontab或Fulcrum Scheduler等相關套件,而Spring 核心框架中以提供Spring Scheduling定期任務週期套件,無論是平均時間執行或定期時間點執行,以下將提供五種範例進行深入介紹與分析,讓我們深入看下去吧。

Principle Introduction

Spring Scheduled模組目的用於編製排程任務使用,開發者只要進行啟動排成註冊開關註解(@EnableScheduling),Spring Scheduled核心模組將會自動掃描專案內排成註解模式(@Scheduled),其註解支援的FixedRateTask、CronTask及FixedDelayTask三種排程性任務註解,第一項fixedRate,目的在於定期執行任務,即時下一次調度任務時,前項任務依舊在執行,仍然可以繼續調度任務。第二項fixedDelay,用於任務執行完時,等待再次執行的時間,並再次進行調度。第三項cron job,源於Unix OS一項實用性功能,可依照開發者所配置的年月日時間點進行執行該任務,其週期性可分為每年、每月、每周某幾日及每日進行調度其任務,可彈性化給每個開發者依照其任務性質進行調度觸發,小編以下提供四種範例給各位開發者作參考。

範例一、任務完成時,等待十秒鐘再次執行,這邊配置延遲觸發(initialDelay),意思在於容器啟動後,並延遲一秒在進行觸發任務。

    @Scheduled(initialDelay=1000L,fixedDelay = 10000L)
    public void triggerFixedDelaySeaFoodMethod () {
        logger.info("triggerFixedDelaySeaFoodMethod scheduler");
       SeaFood seaFood = new SeaFood()
                .setId("C-0011")
                .setDescription("Is a medium-sized burrowing crab that is named for its Busy claws.")
                .setName("Busy crabs");
        SEA_FOOD_CACHE_TAIWAN.asMap().putIfAbsent(seaFood.getId(),seaFood);
    }

範例一、透過運作時間點可看出,確切在容器啟動後一秒後進行觸發,並在任務完成後每十秒觸發一次。

14:17:56.486  INFO 10383 --- [           main] sw.spring.sample.ApplicationBoot         : Started ApplicationBoot in 2.632 seconds (JVM running for 3.118)
14:17:57.479  INFO 10383 --- [   scheduling-1] s.s.sample.scheduled.SeaFoodScheduler    : triggerFixedDelaySeaFoodMethod scheduler
14:18:07.518  INFO 10383 --- [   scheduling-1] s.s.sample.scheduled.SeaFoodScheduler    : triggerFixedDelaySeaFoodMethod scheduler
14:18:17.522  INFO 10383 --- [   scheduling-1] s.s.sample.scheduled.SeaFoodScheduler    : triggerFixedDelaySeaFoodMethod scheduler
14:18:27.523  INFO 10383 --- [   scheduling-1] s.s.sample.scheduled.SeaFoodScheduler    : triggerFixedDelaySeaFoodMethod scheduler

範例二、定期每十秒觸發一次任務。

    @Scheduled(initialDelay=1000L,fixedRate=10000L)
    public void triggerFixedRateSeaFoodMethod () {
        logger.info("triggerFixedRateSeaFoodMethod scheduler");
        SeaFood seaFood = new SeaFood()
                .setId("C-0012")
                .setDescription("Is a medium-sized burrowing crab that is named for its Bubby claws.")
                .setName("Bubby crabs");
        SEA_FOOD_CACHE_TAIWAN.asMap().putIfAbsent(seaFood.getId(),seaFood);
    }

範例二、透過運作時間點可看出,確切在容器啟動後一秒後進行觸發,並每十秒觸發一次。

14:21:18.960  INFO 10399 --- [           main] sw.spring.sample.ApplicationBoot         : Started ApplicationBoot in 3.017 seconds (JVM running for 3.479)
14:21:19.957  INFO 10399 --- [   scheduling-1] s.s.sample.scheduled.SeaFoodScheduler    : triggerFixedRateSeaFoodMethod scheduler
14:21:29.961  INFO 10399 --- [   scheduling-1] s.s.sample.scheduled.SeaFoodScheduler    : triggerFixedRateSeaFoodMethod scheduler
14:21:39.957  INFO 10399 --- [   scheduling-1] s.s.sample.scheduled.SeaFoodScheduler    : triggerFixedRateSeaFoodMethod scheduler
14:21:49.974  INFO 10399 --- [   scheduling-1] s.s.sample.scheduled.SeaFoodScheduler    : triggerFixedRateSeaFoodMethod scheduler

範例三、透過application.properties進行配置任務週期時間,並觸發其相關任務

    @Scheduled(initialDelay = 1000L,fixedRateString="${sea.food.company.scheduled.fixedRate}")
    public void triggerFixedRateSeaFoodMethodByConfig(){
        logger.info("triggerFixedRateSeaFoodMethodByConfig scheduler");
        SeaFood seaFood = new SeaFood()
                .setId("F-0012")
                .setDescription("Is a medium-sized burrowing crab that is named for its Shock Fish.")
                .setName("Shock Fish");
        SEA_FOOD_CACHE_TAIWAN.asMap().putIfAbsent(seaFood.getId(),seaFood);
    }

    @Scheduled(fixedDelayString ="${sea.food.company.scheduled.fixedDelay}")
    public void triggerFixedDeleySeaFoodMethodByConfig () {
        logger.info("triggerFixedDeleySeaFoodMethodByConfig scheduler");
        SeaFood seaFood = new SeaFood()
                .setId("F-0013")
                .setDescription("Is a medium-sized burrowing crab that is named for its Baby Fish.")
                .setName("Baby Fish");
        SEA_FOOD_CACHE_TAIWAN.asMap().putIfAbsent(seaFood.getId(),seaFood);
    }

範例三、運作結果可看出與上述結果一致

14:22:39.004  INFO 10410 --- [           main] sw.spring.sample.ApplicationBoot         : Started ApplicationBoot in 2.346 seconds (JVM running for 2.799)
14:22:39.998  INFO 10410 --- [   scheduling-1] s.s.sample.scheduled.SeaFoodScheduler    : triggerFixedRateSeaFoodMethodByConfig scheduler
14:22:49.019  INFO 10410 --- [   scheduling-1] s.s.sample.scheduled.SeaFoodScheduler    : triggerFixedDeleySeaFoodMethodByConfig scheduler
14:22:50.000  INFO 10410 --- [   scheduling-1] s.s.sample.scheduled.SeaFoodScheduler    : triggerFixedRateSeaFoodMethodByConfig scheduler
14:22:59.021  INFO 10410 --- [   scheduling-1] s.s.sample.scheduled.SeaFoodScheduler    : triggerFixedDeleySeaFoodMethodByConfig scheduler
14:23:00.002  INFO 10410 --- [   scheduling-1] s.s.sample.scheduled.SeaFoodScheduler    : triggerFixedRateSeaFoodMethodByConfig scheduler
14:23:09.022  INFO 10410 --- [   scheduling-1] s.s.sample.scheduled.SeaFoodScheduler    : triggerFixedDeleySeaFoodMethodByConfig scheduler
14:23:09.999  INFO 10410 --- [   scheduling-1] s.s.sample.scheduled.SeaFoodScheduler    : triggerFixedRateSeaFoodMethodByConfig scheduler
14:23:19.022  INFO 10410 --- [   scheduling-1] s.s.sample.scheduled.SeaFoodScheduler    : triggerFixedDeleySeaFoodMethodByConfig scheduler
14:23:19.999  INFO 10410 --- [   scheduling-1] s.s.sample.scheduled.SeaFoodScheduler    : triggerFixedRateSeaFoodMethodByConfig scheduler

範例四、每日定期觸發時間點配置

//application.properties配置 
sea.food.company.scheduled.cron=0 30 14 * * ?

//配置Cron job
    @Scheduled(cron="${sea.food.company.scheduled.cron}")
    public void triggerCronJobByConfig () {
        logger.info("triggerCronJobByConfig scheduler");
        SeaFood seaFood = new SeaFood()
                .setId("F-0014")
                .setDescription("Is a medium-sized burrowing crab that is named for its HAHA Fish.")
                .setName("HAHA Fish");
        SEA_FOOD_CACHE_TAIWAN.asMap().putIfAbsent(seaFood.getId(),seaFood);
    }

範例四、測試結果,以正確依照時間點進行觸發

14:26:28.643  INFO 10430 --- [           main] sw.spring.sample.ApplicationBoot         : Started ApplicationBoot in 2.612 seconds (JVM running for 3.089)
14:30:00.007  INFO 10430 --- [   scheduling-1] s.s.sample.scheduled.SeaFoodScheduler    : triggerCronJobByConfig scheduler


CRON 配置範例
"0 0 09 * * ?"    每天早上09:00觸發 
"0 00 12 ? * *"    每天中午12:00觸發 
"0 10 11 * * ?"    每天早上11:10觸發 
"0 20 09 * * ? *"    每天早上09:20觸發 
"0 11 11 * * ? 2021"    2021年的每天早上11:11觸發 
"0 * 15 * * ?"    每天從15:00開始到15:59分每分鐘一次觸發 
"0 0/10 15 * * ?"    每天從15:00開始到15:50分结束每10分鐘一次觸發 
"0 0/10 14,18 * * ?"    每天的下午14:00至14:50和18:00至18:50

                                 兩個時間内每5分鐘一次觸發 
"0 0-3 08 * * ?"    每天08:00至08:03每分鐘一次觸發 
"0 10,30 15 ? 3 MON"    三月的每周一的15:10和15:30觸發 
"0 15 11 ? * MON-FRI"    每周一、周二、周三、周四、周五的11:15觸發

透過以上範例,各位開發者可依照實境情況進行配置,必定會有不同的效果。

Structure

前面章節已有詳述過,Spring @EnableXXXXX 所有功能都是透過ImportSelector進行實作,這邊的啟動開關@EnableScheduling一樣也是透過@Import進行引入SchedulingConfiguration模組,並透過ScheduledAnnotationBeanPostProcessor進行解析與處理每一個符合特定型別的Bean,並透過ScheduledTaskRegistrar將所有快取中的Task配置於任務排程器中執行。透過Scheduling中的TaskScheduler任務執行調度元件,可將執行任務調度器分為三種,一為ThreadPoolTaskScheduler,基於執行緒池實現的任務執行器,依賴底層ScheduledThreadPoolExecutor元件進行實現。二為ConcurrentTaskScheduler,用於自定義ScheduledThreadPoolExecutor型別Bean,任務執行器就會配置為此種任務調度器(ConcurrentTaskScheduler),三為DefaultManagedTaskScheduler,通過JNDI配置此排程執行器,依賴ScheduledThreadPoolExecutor實踐,一般較少用到,以下架構圖提供大家參考。

圖一 Scheduling 註解模組工作流程
image

Sample Source

spring-sample-scheduled

Reference Url

使用spring @Scheduled 註解執行定時任務!

Open Source Job Schedulers in Java

通過原始碼理解Spring中@Scheduled的實現原理並且實現排程任務動態裝載


上一篇
[Day - 20] - Spring 一日優雅組態配置就上手
下一篇
[Day - 22] - 今晚我想來個Spring Async非同步的感覺
系列文
Wow ! There is no doubt about Learn Spring framework in a month.30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言