中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

SpringBoot | 第二十二章:定時(shí)任務(wù)的使用

2018-09-17    來(lái)源:importnew

容器云強(qiáng)勢(shì)上線!快速搭建集群,上萬(wàn)Linux鏡像隨意使用

前言

上兩章節(jié),我們簡(jiǎn)單的講解了關(guān)于異步調(diào)用和異步請(qǐng)求相關(guān)知識(shí)點(diǎn)。這一章節(jié),我們來(lái)講講開(kāi)發(fā)過(guò)程也是經(jīng)常會(huì)碰見(jiàn)的定時(shí)任務(wù)。比如每天定時(shí)清理無(wú)效數(shù)據(jù)、定時(shí)發(fā)送短信、定時(shí)發(fā)送郵件、支付系統(tǒng)中的定時(shí)對(duì)賬等等,往往都會(huì)定義一些定時(shí)器,進(jìn)行此業(yè)務(wù)的開(kāi)發(fā)。所以,本章節(jié)介紹下在SpringBoot中定時(shí)任務(wù)如何使用及一點(diǎn)分布式定時(shí)服務(wù)的思考總結(jié)。

一點(diǎn)知識(shí)

JAVA開(kāi)發(fā)領(lǐng)域,目前可以通過(guò)以下幾種方式進(jìn)行定時(shí)任務(wù):

  • Timer:jdk中自帶的一個(gè)定時(shí)調(diào)度類(lèi),可以簡(jiǎn)單的實(shí)現(xiàn)按某一頻度進(jìn)行任務(wù)執(zhí)行。提供的功能比較單一,無(wú)法實(shí)現(xiàn)復(fù)雜的調(diào)度任務(wù)。
  • ScheduledExecutorService:也是jdk自帶的一個(gè)基于線程池設(shè)計(jì)的定時(shí)任務(wù)類(lèi)。其每個(gè)調(diào)度任務(wù)都會(huì)分配到線程池中的一個(gè)線程執(zhí)行,所以其任務(wù)是并發(fā)執(zhí)行的,互不影響。
  • Spring Task:Spring提供的一個(gè)任務(wù)調(diào)度工具,支持注解和配置文件形式,支持Cron表達(dá)式,使用簡(jiǎn)單但功能強(qiáng)大。
  • Quartz:一款功能強(qiáng)大的任務(wù)調(diào)度器,可以實(shí)現(xiàn)較為復(fù)雜的調(diào)度功能,如每月一號(hào)執(zhí)行、每天凌晨執(zhí)行、每周五執(zhí)行等等,還支持分布式調(diào)度,就是配置稍顯復(fù)雜。

題外話:對(duì)于Quartz,早前用過(guò)1.6版本的,更新到2.x及以上版本后基本沒(méi)怎么接觸了,原來(lái)還有倒騰過(guò)結(jié)合Kettle做了一些動(dòng)態(tài)的定時(shí)抽取數(shù)據(jù)啥的還編寫(xiě)過(guò)一個(gè)Cron表達(dá)式編輯器,現(xiàn)在基本忘記了。。等有機(jī)會(huì),再次深入學(xué)習(xí)后再來(lái)單獨(dú)分享一些關(guān)于的Quartz心得吧。

基于JDK方式實(shí)現(xiàn)簡(jiǎn)單定時(shí)

剛剛有介紹過(guò),基于JDK方式一共有兩種:TimerScheduledExecutorService。接下來(lái),就簡(jiǎn)單講解下這兩種方式。

Timer

Timer是jdk提供的java.util.Timer類(lèi)。

簡(jiǎn)單示例:

@GetMapping("/timer")
public String doTimer() {
    Timer timer = new Timer();
    timer.schedule(new TimerTask() {
        
        @Override
        public void run() {
            log.info("Timer定時(shí)任務(wù)啟動(dòng):" + new Date());
            
        }
    }, 1000,1000);//延遲1秒啟動(dòng),每1秒執(zhí)行一次
    return "timer";

啟動(dòng)后,訪問(wèn)即可看見(jiàn)控制臺(tái)周期性輸出信息了:

2018-08-18 21:30:35.171  INFO 13352 --- [        Timer-0] c.l.l.s.c.controller.TaskController      : Timer定時(shí)任務(wù)啟動(dòng):Sat Aug 18 21:30:35 CST 2018
2018-08-18 21:30:36.173  INFO 13352 --- [        Timer-0] c.l.l.s.c.controller.TaskController      : Timer定時(shí)任務(wù)啟動(dòng):Sat Aug 18 21:30:36 CST 2018
2018-08-18 21:30:37.173  INFO 13352 --- [        Timer-0] c.l.l.s.c.controller.TaskController      : Timer定時(shí)任務(wù)啟動(dòng):Sat Aug 18 21:30:37 CST 2018
2018-08-18 21:30:38.173  INFO 13352 --- [        Timer-0] c.l.l.s.c.controller.TaskController      : Timer定時(shí)任務(wù)啟動(dòng):Sat Aug 18 21:30:38 CST 2018
2018-08-18 21:30:39.174  INFO 13352 --- [        Timer-0] c.l.l.s.c.controller.TaskController      : Timer定時(shí)任務(wù)啟動(dòng):Sat Aug 18 21:30:39 CST 2018
......

相關(guān)API簡(jiǎn)單說(shuō)明:

1、在特定時(shí)間執(zhí)行任務(wù),只執(zhí)行一次

public void schedule(TimerTask task,Date time)

2、在特定時(shí)間之后執(zhí)行任務(wù),只執(zhí)行一次

public void schedule(TimerTask task,long delay)

3、指定第一次執(zhí)行的時(shí)間,然后按照間隔時(shí)間,重復(fù)執(zhí)行

public void schedule(TimerTask task,Date firstTime,long period)

4、在特定延遲之后第一次執(zhí)行,然后按照間隔時(shí)間,重復(fù)執(zhí)行

public void schedule(TimerTask task,long delay,long period)

5、第一次執(zhí)行之后,特定頻率執(zhí)行,與3同

public void scheduleAtFixedRate(TimerTask task,Date firstTime,long period)

6、在delay毫秒之后第一次執(zhí)行,后按照特定頻率執(zhí)行

public void scheduleAtFixedRate(TimerTask task,long delay,long period)

參數(shù):

  • delay: 延遲執(zhí)行的毫秒數(shù),即在delay毫秒之后第一次執(zhí)行
  • period:重復(fù)執(zhí)行的時(shí)間間隔

取消任務(wù)使用:timer.cancel()方法即可注銷(xiāo)任務(wù)。

此類(lèi)相對(duì)用的較少了,簡(jiǎn)單了解下。

ScheduledExecutorService

ScheduledExecutorService可以說(shuō)是Timer的替代類(lèi),因?yàn)?code>Timer不支持多線程,任務(wù)是串行的,而且也不捕獲異常,假設(shè)某個(gè)任務(wù)異常了,整個(gè)Timer就無(wú)法運(yùn)行了。

簡(jiǎn)單示例:

@GetMapping("/executor")
public String ScheduledExecutorService() {
    //
    ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
    service.scheduleAtFixedRate(new Runnable() {
        
        @Override
        public void run() {
            log.info("ScheduledExecutorService定時(shí)任務(wù)執(zhí)行:" + new Date());                
        }
    }, 1, 1, TimeUnit.SECONDS);//首次延遲1秒,之后每1秒執(zhí)行一次
    log.info("ScheduledExecutorService定時(shí)任務(wù)啟動(dòng):" + new Date());    
    return "ScheduledExecutorService!";        
}

啟動(dòng)后,可看見(jiàn)控制臺(tái)按設(shè)定的頻率輸出:

2018-08-18 22:03:24.840  INFO 6752 --- [nio-8080-exec-1] c.l.l.s.c.controller.TaskController      : ScheduledExecutorService定時(shí)任務(wù)啟動(dòng):Sat Aug 18 22:03:24 CST 2018
2018-08-18 22:03:25.841  INFO 6752 --- [pool-1-thread-1] c.l.l.s.c.controller.TaskController      : ScheduledExecutorService定時(shí)任務(wù)執(zhí)行:Sat Aug 18 22:03:25 CST 2018
2018-08-18 22:03:26.842  INFO 6752 --- [pool-1-thread-1] c.l.l.s.c.controller.TaskController      : ScheduledExecutorService定時(shí)任務(wù)執(zhí)行:Sat Aug 18 22:03:26 CST 2018
2018-08-18 22:03:27.841  INFO 6752 --- [pool-1-thread-2] c.l.l.s.c.controller.TaskController      : ScheduledExecutorService定時(shí)任務(wù)執(zhí)行:Sat Aug 18 22:03:27 CST 2018
2018-08-18 22:03:28.840  INFO 6752 --- [pool-1-thread-1] c.l.l.s.c.controller.TaskController      : ScheduledExecutorService定時(shí)任務(wù)執(zhí)行:Sat Aug 18 22:03:28 CST 2018
2018-08-18 22:03:29.840  INFO 6752 --- [pool-1-thread-3] c.l.l.s.c.controller.TaskController      : ScheduledExecutorService定時(shí)任務(wù)執(zhí)行:Sat Aug 18 22:03:29 CST 2018

可同時(shí)設(shè)置多個(gè)任務(wù),只需再次設(shè)置scheduleAtFixedRate即可。

常用方法說(shuō)明:

  • ScheduleAtFixedRate:
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit);

參數(shù)說(shuō)明:

  1. command:執(zhí)行線程
  2. initialDelay:初始化延時(shí)
  3. period:兩次開(kāi)始執(zhí)行最小間隔時(shí)間
  4. unit:計(jì)時(shí)單位
  • ScheduleWithFixedDelay:
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit);

參數(shù)說(shuō)明:

  1. command:執(zhí)行線程
  2. initialDelay:初始化延時(shí)
  3. delay:前一次執(zhí)行結(jié)束到下一次執(zhí)行開(kāi)始的間隔時(shí)間(間隔執(zhí)行延遲時(shí)間)
  4. unit:計(jì)時(shí)單位

其他的方法大家可自行谷歌下。

基于SpingTask實(shí)現(xiàn)定時(shí)任務(wù)

使用SpringTaskSpringBoot是很簡(jiǎn)單的,使用@Scheduled注解即可輕松搞定。

0.啟動(dòng)類(lèi),加入@EnableScheduling讓注解@Scheduled生效。

@SpringBootApplication
@EnableScheduling
@Slf4j
public class Chapter22Application {

    public static void main(String[] args) {
        SpringApplication.run(Chapter22Application.class, args);
        log.info("Chapter22啟動(dòng)!");
    }
}

1.編寫(xiě)一個(gè)調(diào)度類(lèi),系統(tǒng)啟動(dòng)后自動(dòng)掃描,自動(dòng)執(zhí)行。

@Component
@Slf4j
public class ScheduledTask {

    /**
     * 自動(dòng)掃描,啟動(dòng)時(shí)間點(diǎn)之后5秒執(zhí)行一次
     */
    @Scheduled(fixedRate=5000)
    public void getCurrentDate() {
        log.info("Scheduled定時(shí)任務(wù)執(zhí)行:" + new Date());
    }
}

2.啟動(dòng)后,控制臺(tái)可就看見(jiàn)每5秒一次輸出了:

2018-08-18 22:23:09.735  INFO 13812 --- [pool-1-thread-1] c.l.l.s.c.controller.ScheduledTask       : Scheduled定時(shí)任務(wù)執(zhí)行:Sat Aug 18 22:23:09 CST 2018
2018-08-18 22:23:14.734  INFO 13812 --- [pool-1-thread-1] c.l.l.s.c.controller.ScheduledTask       : Scheduled定時(shí)任務(wù)執(zhí)行:Sat Aug 18 22:23:14 CST 2018
2018-08-18 22:23:19.735  INFO 13812 --- [pool-1-thread-1] c.l.l.s.c.controller.ScheduledTask       : Scheduled定時(shí)任務(wù)執(zhí)行:Sat Aug 18 22:23:19 CST 2018
2018-08-18 22:23:24.735  INFO 13812 --- [pool-1-thread-1] c.l.l.s.c.controller.ScheduledTask       : Scheduled定時(shí)任務(wù)執(zhí)行:Sat Aug 18 22:23:24 CST 2018
2018-08-18 22:23:29.735  INFO 13812 --- [pool-1-thread-1] c.l.l.s.c.controller.ScheduledTask       : Scheduled定時(shí)任務(wù)執(zhí)行:Sat Aug 18 22:23:29 CST 2018
......

使用都是簡(jiǎn)單的,現(xiàn)在我們來(lái)看看注解@Scheduled的參數(shù)意思:

  1. fixedRate:定義一個(gè)按一定頻率執(zhí)行的定時(shí)任務(wù)
  2. fixedDelay:定義一個(gè)按一定頻率執(zhí)行的定時(shí)任務(wù),與上面不同的是,改屬性可以配合initialDelay, 定義該任務(wù)延遲執(zhí)行時(shí)間。
  3. cron:通過(guò)表達(dá)式來(lái)配置任務(wù)執(zhí)行時(shí)間

Cron表達(dá)式詳解

一個(gè)cron表達(dá)式有至少6個(gè)(也可能7個(gè))有空格分隔的時(shí)間元素。

依次順序如下表所示:

字段 允許值 允許的特殊字符
0~59 , – * /
0~59 , – * /
小時(shí) 0~23 , – * /
日期 1-31 , – * ? / L W C
月份 1~12或者JAN~DEC , – * /
星期 1~7或者SUN~SAT , – * ? / L C #
年(可選) 留空,1970~2099 , – * /

簡(jiǎn)單舉例:

  • 0/1 * * * * ?:每秒執(zhí)行一次
  • 0 0 2 1 * ? : 表示在每月的1日的凌晨2點(diǎn)調(diào)整任務(wù)
  • 0 0 10,14,16 ? :每天上午10點(diǎn),下午2點(diǎn),4點(diǎn)
  • 0 0 12 * * ? : 每天中午12點(diǎn)觸發(fā)
  • 0 15 10 ? * MON-FRI : 周一至周五的上午10:15觸發(fā)

更多表達(dá)式,可訪問(wèn):http://cron.qqe2.com/?進(jìn)行在線表達(dá)式編寫(xiě)。簡(jiǎn)單明了。

在線表達(dá)式編輯器

自定義線程池

從控制臺(tái)輸出可以看見(jiàn),多任務(wù)使用的是同一個(gè)線程?山Y(jié)合上章節(jié)的異步調(diào)用來(lái)實(shí)現(xiàn)不同任務(wù)使用不同的線程進(jìn)行任務(wù)執(zhí)行。

0.編寫(xiě)配置類(lèi),同時(shí)啟用@Async注解:

@Configuration
@EnableAsync
public class Config {
    /**
     * 配置線程池
     * @return
     */
    @Bean(name = "scheduledPoolTaskExecutor")
    public ThreadPoolTaskExecutor getAsyncThreadPoolTaskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(20);
        taskExecutor.setMaxPoolSize(200);
        taskExecutor.setQueueCapacity(25);
        taskExecutor.setKeepAliveSeconds(200);
        taskExecutor.setThreadNamePrefix("oKong-Scheduled-");
        // 線程池對(duì)拒絕任務(wù)(無(wú)線程可用)的處理策略,目前只支持AbortPolicy、CallerRunsPolicy;默認(rèn)為后者
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //調(diào)度器shutdown被調(diào)用時(shí)等待當(dāng)前被調(diào)度的任務(wù)完成
        taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        //等待時(shí)長(zhǎng)
        taskExecutor.setAwaitTerminationSeconds(60);
        taskExecutor.initialize();
        return taskExecutor;
    }
}

1.調(diào)度類(lèi)上加入@Async。

@Component
@Slf4j
public class ScheduledTask {

    /**
     * 自動(dòng)掃描,啟動(dòng)時(shí)間點(diǎn)之后5秒執(zhí)行一次
     */
    @Async("scheduledPoolTaskExecutor")
    @Scheduled(fixedRate=5000)
    public void getCurrentDate() {
        log.info("Scheduled定時(shí)任務(wù)執(zhí)行:" + new Date());
    }
}

再次啟動(dòng)程序,可看見(jiàn)控制臺(tái)輸出,任務(wù)已經(jīng)是不同線程下執(zhí)行了:

2018-08-18 22:47:13.313  INFO 14212 --- [ong-Scheduled-1] c.l.l.s.c.controller.ScheduledTask       : Scheduled定時(shí)任務(wù)執(zhí)行:Sat Aug 18 22:47:13 CST 2018
2018-08-18 22:47:13.343  INFO 14212 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2018-08-18 22:47:13.348  INFO 14212 --- [           main] c.l.l.s.chapter22.Chapter22Application   : Started Chapter22Application in 2.057 seconds (JVM running for 2.855)
2018-08-18 22:47:13.348  INFO 14212 --- [           main] c.l.l.s.chapter22.Chapter22Application   : Chapter22啟動(dòng)!
2018-08-18 22:47:18.308  INFO 14212 --- [ong-Scheduled-2] c.l.l.s.c.controller.ScheduledTask       : Scheduled定時(shí)任務(wù)執(zhí)行:Sat Aug 18 22:47:18 CST 2018

動(dòng)態(tài)添加定時(shí)任務(wù)

使用注解的方式,無(wú)法實(shí)現(xiàn)動(dòng)態(tài)的修改或者添加新的定時(shí)任務(wù)的,這個(gè)使用就需要使用編程的方式進(jìn)行任務(wù)的更新操作了?芍苯邮褂ThreadPoolTaskScheduler或者SchedulingConfigurer接口進(jìn)行自定義定時(shí)任務(wù)創(chuàng)建。

ThreadPoolTaskScheduler

ThreadPoolTaskSchedulerSpringTask的核心實(shí)現(xiàn)類(lèi),該類(lèi)提供了大量的重載方法進(jìn)行任務(wù)調(diào)度。這里簡(jiǎn)單示例下,具體的大家自行搜索下,用的少不太了解呀。

0.創(chuàng)建一個(gè)ThreadPoolTaskScheduler類(lèi)。

@Bean("taskExecutor")
public TaskScheduler taskExecutor() {
    ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
    executor.setPoolSize(20);
    executor.setThreadNamePrefix("oKong-taskExecutor-");
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    //調(diào)度器shutdown被調(diào)用時(shí)等待當(dāng)前被調(diào)度的任務(wù)完成
    executor.setWaitForTasksToCompleteOnShutdown(true);
    //等待時(shí)長(zhǎng)
    executor.setAwaitTerminationSeconds(60);
    return executor;
}

1.編寫(xiě)一個(gè)控制類(lèi),動(dòng)態(tài)設(shè)置定時(shí)任務(wù):

@Autowired
TaskScheduler taskScheduler;

@GetMapping("/poolTask")
public String threadPoolTaskScheduler() {

    taskScheduler.schedule(new Runnable() {
        
        @Override
        public void run() {
            log.info("ThreadPoolTaskScheduler定時(shí)任務(wù):" + new Date());
        }
    }, new CronTrigger("0/3 * * * * ?"));//每3秒執(zhí)行一次
    return "ThreadPoolTaskScheduler!";
}

2.啟動(dòng)后,訪問(wèn)接口,即可看見(jiàn)控制臺(tái)每3秒輸出一次:

2018-08-18 23:20:39.002  INFO 9120 --- [Kong-Executor-1] c.l.l.s.c.controller.TaskController      : ThreadPoolTaskScheduler定時(shí)任務(wù):Sat Aug 18 23:20:39 CST 2018
2018-08-18 23:20:42.000  INFO 9120 --- [Kong-Executor-1] c.l.l.s.c.controller.TaskController      : ThreadPoolTaskScheduler定時(shí)任務(wù):Sat Aug 18 23:20:42 CST 2018
2018-08-18 23:20:45.002  INFO 9120 --- [Kong-Executor-2] c.l.l.s.c.controller.TaskController      : ThreadPoolTaskScheduler定時(shí)任務(wù):Sat Aug 18 23:20:45 CST 2018
2018-08-18 23:20:48.001  INFO 9120 --- [Kong-Executor-1] c.l.l.s.c.controller.TaskController      : ThreadPoolTaskScheduler定時(shí)任務(wù):Sat Aug 18 23:20:48 CST 2018

SchedulingConfigurer

此類(lèi)十個(gè)接口,直接實(shí)現(xiàn)其configurerTasks方法即可。

0.編寫(xiě)配置類(lèi):

@Configuration
@Slf4j
public class ScheduleConfig implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setTaskScheduler(taskExecutor());
        taskRegistrar.getScheduler().schedule(new Runnable() {
            
            @Override
            public void run() {
                log.info("SchedulingConfigurer定時(shí)任務(wù):" + new Date());
            }
        }, new CronTrigger("0/3 * * * * ?"));//每3秒執(zhí)行一次
    }
    
    @Bean("taskExecutor")
    public TaskScheduler taskExecutor() {
        ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
        executor.setPoolSize(20);
        executor.setThreadNamePrefix("oKong-Executor-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //調(diào)度器shutdown被調(diào)用時(shí)等待當(dāng)前被調(diào)度的任務(wù)完成
        executor.setWaitForTasksToCompleteOnShutdown(true);
        //等待時(shí)長(zhǎng)
        executor.setAwaitTerminationSeconds(60);
        return executor;
    }

}

1.啟動(dòng)后,控制臺(tái)也可以看見(jiàn)每3秒輸出一次:

2018-08-18 23:24:39.001  INFO 868 --- [Kong-Executor-1] c.l.l.s.chapter22.config.ScheduleConfig  : SchedulingConfigurer定時(shí)任務(wù):Sat Aug 18 23:24:39 CST 2018
2018-08-18 23:24:42.001  INFO 868 --- [Kong-Executor-1] c.l.l.s.chapter22.config.ScheduleConfig  : SchedulingConfigurer定時(shí)任務(wù):Sat Aug 18 23:24:42 CST 2018
2018-08-18 23:24:45.000  INFO 868 --- [Kong-Executor-2] c.l.l.s.chapter22.config.ScheduleConfig  : SchedulingConfigurer定時(shí)任務(wù):Sat Aug 18 23:24:45 CST 2018

基于Quartz實(shí)現(xiàn)定時(shí)調(diào)度

由于本章節(jié)是基于SpringBoot 1.x版本的,所以沒(méi)有基于Quartzstarter配置,這里直接引入了Quartz相關(guān)依賴包來(lái)集成。

題外話:原本使用SpringMvc時(shí),一般上都是通過(guò)xml文件,配置其org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean類(lèi)進(jìn)行具體執(zhí)行任務(wù)的配置,指定執(zhí)行的對(duì)象和方法。然后通過(guò)設(shè)置CronTriggerFactoryBean或者SimpleTriggerFactoryBean設(shè)置定時(shí)器,最后通過(guò)org.springframework.scheduling.quartz.SchedulerFactoryBean加入調(diào)度的trigger。所以,我們就使用javaConfig方式進(jìn)行簡(jiǎn)單集成下。

0.加入pom依賴

<!-- quartz -->
<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.2.3</version>
</dependency>
<!-- spring集成quartz -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
</dependency>
<!-- 因?yàn)镾chedulerFactoryBean中依賴了org.springframework.transaction.PlatformTransactionManager,所以需依賴tx相關(guān)包,其實(shí)還是quartz有個(gè)分布式功能,是使用數(shù)據(jù)庫(kù)完成的。 -->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
</dependency>

1.編寫(xiě)配置類(lèi)。

@Configuration
@Slf4j
public class QuartzConfig {
    
    /**
     * 通過(guò)工廠類(lèi),創(chuàng)建job實(shí)例
     * @return
     */
    @Bean
    public MethodInvokingJobDetailFactoryBean customJobDetailFactoryBean() {
        
        MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean();
        //設(shè)置執(zhí)行任務(wù)的bean
        jobDetail.setTargetBeanName("quartzTask");
        //設(shè)置具體執(zhí)行的方法
        jobDetail.setTargetMethod("quartzTask");
        //同步執(zhí)行,上一任務(wù)未執(zhí)行完,下一任務(wù)等待
        //true 任務(wù)并發(fā)執(zhí)行
        //false 下一個(gè)任務(wù)必須等待上一任務(wù)完成
        jobDetail.setConcurrent(false);
        return jobDetail; 
    }
    
    /**
     * 通過(guò)工廠類(lèi)創(chuàng)建Trigger
     * @param jobDetailFactoryBean
     * @return
     * @throws ParseException 
     */
    @Bean(name = "cronTriggerBean")
    public Trigger cronTriggerBean(MethodInvokingJobDetailFactoryBean jobDetailFactoryBean) throws ParseException {
        CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
        cronTriggerFactoryBean.setJobDetail(jobDetailFactoryBean.getObject());
        cronTriggerFactoryBean.setCronExpression("0/3 * * * * ?");//每3秒執(zhí)行一次
        cronTriggerFactoryBean.setName("customCronTrigger");
        cronTriggerFactoryBean.afterPropertiesSet();
        return cronTriggerFactoryBean.getObject();

    }
    
    /**
     * 調(diào)度工廠類(lèi),自動(dòng)注入Trigger
     * @return
     */
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(Trigger... triggers) {
        SchedulerFactoryBean bean = new SchedulerFactoryBean();
        
        //也可以直接注入 ApplicationContext,利于 getBeansOfType獲取trigger
//        Map<String,Trigger> triggerMap = appContext.getBeansOfType(Trigger.class);
//        if(triggerMap != null) {
//            List<Trigger> triggers = new ArrayList<>(triggerMap.size());
//            //
//            triggerMap.forEach((key,trigger)->{
//                triggers.add(trigger);
//            });
//            bean.setTriggers(triggers.toArray(new Trigger[triggers.size()]));
//        }
        //這里注意 對(duì)應(yīng)的trigger 不能為null 不然會(huì)異常的
        bean.setTriggers(triggers);
        return bean;
    } 
    
    @Component("quartzTask")
    public class QuartzTask {
      
        public void quartzTask() {
            log.info("Quartz定時(shí)任務(wù):" + new Date());
        }
    }
}

2.啟動(dòng)后,可以看見(jiàn)控制臺(tái)以每3秒執(zhí)行一次輸出:

2018-08-18 23:42:03.019  INFO 772 --- [ryBean_Worker-2] c.l.l.s.chapter22.config.QuartzConfig    : Quartz定時(shí)任務(wù):Sun Aug 18 23:42:03 CST 2018
2018-08-18 23:42:06.002  INFO 772 --- [ryBean_Worker-3] c.l.l.s.chapter22.config.QuartzConfig    : Quartz定時(shí)任務(wù):Sun Aug 18 23:42:06 CST 2018
2018-08-18 23:42:09.002  INFO 772 --- [ryBean_Worker-4] c.l.l.s.chapter22.config.QuartzConfig    : Quartz定時(shí)任務(wù):Sun Aug 18 23:42:09 CST 2018

關(guān)于Quartz的詳細(xì)用法,再次不表了。好久沒(méi)有使用過(guò)了。有機(jī)會(huì)再來(lái)詳細(xì)闡述吧。

分布式調(diào)度服務(wù)淺談

在單機(jī)模式下,定時(shí)任務(wù)是沒(méi)什么問(wèn)題的。但當(dāng)我們部署了多臺(tái)服務(wù),同時(shí)又每臺(tái)服務(wù)又有定時(shí)任務(wù)時(shí),若不進(jìn)行合理的控制在同一時(shí)間,只有一個(gè)定時(shí)任務(wù)啟動(dòng)執(zhí)行,這時(shí),定時(shí)執(zhí)行的結(jié)果就可能存在混亂和錯(cuò)誤了。

這里簡(jiǎn)單的說(shuō)說(shuō)相關(guān)的解決方案吧,一家之言,希望大家能提出自己的見(jiàn)解,共同進(jìn)步!

  • 剝離所有定時(shí)任務(wù)到一個(gè)工程:此方案是最簡(jiǎn)單的,在定時(shí)任務(wù)相對(duì)較小,并發(fā)任務(wù)不多時(shí),可以使用此方案。簡(jiǎn)單也容易維護(hù)。當(dāng)定時(shí)任務(wù)牽扯的業(yè)務(wù)越來(lái)越多,越來(lái)越雜時(shí),維護(hù)量就成本增加了,工程會(huì)越來(lái)越臃腫,此方案就不實(shí)用了。
  • 利用Quartz集群方案:本身Quartz是支持通過(guò)數(shù)據(jù)庫(kù)實(shí)現(xiàn)集群的,以下是其集群架構(gòu)圖:

集群架構(gòu)圖集群架構(gòu)圖

其實(shí)現(xiàn)原理也相對(duì)簡(jiǎn)單:通過(guò)數(shù)據(jù)庫(kù)實(shí)現(xiàn)任務(wù)的持久化,保存定時(shí)任務(wù)的相關(guān)配置信息,以保證下次系統(tǒng)啟動(dòng)時(shí),定時(shí)任務(wù)能自動(dòng)啟動(dòng)。同時(shí),通過(guò)數(shù)據(jù)庫(kù)行鎖(for update)機(jī)制,控制一個(gè)任務(wù)只能被一個(gè)實(shí)例運(yùn)行,只有獲取鎖的實(shí)例才能運(yùn)行任務(wù),其他的只能等待,直到鎖被釋放。這種方式有些弊端,就是依賴了數(shù)據(jù)庫(kù),同時(shí)也需要保證各服務(wù)器之間的時(shí)間需要同步,不然也是會(huì)混亂的。

現(xiàn)在Quartz也有基于Redis的集群方案,有興趣的可以搜索下。

  • 分布式鎖:可通過(guò)使用Redis或者ZooKeeper實(shí)現(xiàn)一個(gè)分布式鎖的機(jī)制,使得只有獲取到鎖的實(shí)例方能運(yùn)行定時(shí)任務(wù),避免任務(wù)重復(fù)執(zhí)行?刹榭聪麻_(kāi)源的基于Redis實(shí)現(xiàn)的分布式鎖項(xiàng)目:redisson。github地址:https://github.com/redisson/redisson有興趣的同學(xué)可以了解下。
  • 統(tǒng)一調(diào)度中心:

可構(gòu)建一個(gè)純粹的定時(shí)服務(wù),只有定時(shí)器相關(guān)配置,比如定時(shí)時(shí)間,定時(shí)調(diào)度的api接口或者http服務(wù),甚至是統(tǒng)一注冊(cè)中心下的服務(wù)類(lèi),如dubbo服務(wù)等。而具體的任務(wù)執(zhí)行操作都在各自業(yè)務(wù)方系統(tǒng)中,調(diào)度中心只負(fù)責(zé)接口的調(diào)用,具體實(shí)現(xiàn)還是在業(yè)務(wù)方。這種方案相對(duì)來(lái)說(shuō)比較通用,實(shí)現(xiàn)起來(lái)也簡(jiǎn)單。就是需要業(yè)務(wù)方進(jìn)行約定編程,或者對(duì)外提供一個(gè)api接口。

當(dāng)然,為了實(shí)現(xiàn)定時(shí)任務(wù)的自動(dòng)發(fā)現(xiàn)和注冊(cè)功能,還是需要規(guī)范一套規(guī)則來(lái)實(shí)現(xiàn)自動(dòng)注冊(cè)功能。簡(jiǎn)單來(lái)說(shuō),以Dubbo服務(wù)為例,可以定義一個(gè)定時(shí)任務(wù)接口類(lèi),調(diào)度中心只需要獲取所有實(shí)現(xiàn)此接口的服務(wù),同時(shí)通過(guò)服務(wù)的相關(guān)配置(調(diào)度時(shí)間、失敗策略等)進(jìn)行相關(guān)定時(shí)操作;蛘呔帉(xiě)一個(gè)服務(wù)注冊(cè)與發(fā)現(xiàn)的客戶端,通過(guò)Spring獲取到實(shí)現(xiàn)此接口的所有實(shí)現(xiàn)類(lèi),上送到調(diào)度中心。

而且,統(tǒng)一調(diào)度中心,還可以對(duì)所有的定時(shí)任務(wù)的調(diào)度情況進(jìn)行有效監(jiān)控,日志記錄等,也可以約定接口,讓定時(shí)任務(wù)回傳定時(shí)結(jié)果,做到全局把控的目的。

以上就是對(duì)分布式調(diào)度的一點(diǎn)理解,有錯(cuò)誤的地方還望指正,有更好的方案也希望能分享下。

參考資料

  1. https://www.cnblogs.com/yank/p/3955322.html
  2. https://blog.csdn.net/tsyj810883979/article/details/8481621
  3. https://www.cnblogs.com/javahr/p/8318728.html
  4. http://www.quartz-scheduler.org/documentation/quartz-2.1.x/quick-start.html
  5. https://spring.io/guides/gs/scheduling-tasks/

總結(jié)

本章節(jié)主要是講解了通過(guò)不同的方式實(shí)現(xiàn)定時(shí)任務(wù)。對(duì)于定時(shí)任務(wù)而言,本身是門(mén)大學(xué)問(wèn),一倆篇文章是講不完的。像SpringTaskQuartz都是很強(qiáng)大的調(diào)度器,兩者很相似,像如何實(shí)現(xiàn)任務(wù)的動(dòng)態(tài)修改調(diào)度周期,動(dòng)態(tài)停止相關(guān)任務(wù),調(diào)度任務(wù)的監(jiān)控,這些本文章都沒(méi)有涉及。還希望有相關(guān)需求的同學(xué)自行搜索相關(guān)資料了。

最后

目前互聯(lián)網(wǎng)上很多大佬都有SpringBoot系列教程,如有雷同,請(qǐng)多多包涵了。本文是作者在電腦前一字一句敲的,每一步都是自己實(shí)踐的。若文中有所錯(cuò)誤之處,還望提出,謝謝。

標(biāo)簽: 服務(wù)器 谷歌 互聯(lián)網(wǎng) 數(shù)據(jù)庫(kù) 搜索

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請(qǐng)與原作者聯(lián)系。

上一篇:Java序列化的狀態(tài)

下一篇:內(nèi)存屏障和 volatile 語(yǔ)義