[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 22. Zabbix 通知设定 - SMTP - Mail

>>:  【Day 24】Pointer - Practice 1

[Day 7] 实作 Request Data Validation 及 Global Exception Handler

昨天提到如何使用 kotlinx.serialization 处理 request/response...

Day17_控制项(A9存取控制)

=___="再次证明,每天都要发文,原来也不容易。来到了第17天了~要撑住 ▉A.9存取控...

D28 Selenium 测试网页与参数

昨天用Selenium IDE 测试网页但是一值找不到方法设随机变数 今天找了python套件sel...

成为工具人应有的工具包-11 IE PassView

IE PassView 今天来认识这个酷东西 IE PassView IE PassView 是一个...

day 14 - drone 的go-test & golangci-lint

自己的产出自己负责!每次交付专案之前我都会想到这句话, 是不是该再检查一下 程序码已经成功的buil...