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

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

2018-09-17    來源:importnew

容器云強勢上線!快速搭建集群,上萬Linux鏡像隨意使用

前言

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

一點知識

JAVA開發(fā)領(lǐng)域,目前可以通過以下幾種方式進行定時任務(wù):

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

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

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

剛剛有介紹過,基于JDK方式一共有兩種:TimerScheduledExecutorService。接下來,就簡單講解下這兩種方式。

Timer

Timer是jdk提供的java.util.Timer類。

簡單示例:

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

啟動后,訪問即可看見控制臺周期性輸出信息了:

2018-08-18 21:30:35.171  INFO 13352 --- [        Timer-0] c.l.l.s.c.controller.TaskController      : Timer定時任務(wù)啟動: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定時任務(wù)啟動: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定時任務(wù)啟動: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定時任務(wù)啟動: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定時任務(wù)啟動:Sat Aug 18 21:30:39 CST 2018
......

相關(guān)API簡單說明:

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

public void schedule(TimerTask task,Date time)

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

public void schedule(TimerTask task,long delay)

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

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

4、在特定延遲之后第一次執(zhí)行,然后按照間隔時間,重復(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í)行的時間間隔

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

此類相對用的較少了,簡單了解下。

ScheduledExecutorService

ScheduledExecutorService可以說是Timer的替代類,因為Timer不支持多線程,任務(wù)是串行的,而且也不捕獲異常,假設(shè)某個任務(wù)異常了,整個Timer就無法運行了。

簡單示例:

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

啟動后,可看見控制臺按設(shè)定的頻率輸出:

2018-08-18 22:03:24.840  INFO 6752 --- [nio-8080-exec-1] c.l.l.s.c.controller.TaskController      : ScheduledExecutorService定時任務(wù)啟動: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定時任務(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定時任務(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定時任務(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定時任務(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定時任務(wù)執(zhí)行:Sat Aug 18 22:03:29 CST 2018

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

常用方法說明:

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

參數(shù)說明:

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

參數(shù)說明:

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

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

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

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

0.啟動類,加入@EnableScheduling讓注解@Scheduled生效。

@SpringBootApplication
@EnableScheduling
@Slf4j
public class Chapter22Application {

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

1.編寫一個調(diào)度類,系統(tǒng)啟動后自動掃描,自動執(zhí)行。

@Component
@Slf4j
public class ScheduledTask {

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

2.啟動后,控制臺可就看見每5秒一次輸出了:

2018-08-18 22:23:09.735  INFO 13812 --- [pool-1-thread-1] c.l.l.s.c.controller.ScheduledTask       : Scheduled定時任務(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定時任務(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定時任務(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定時任務(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定時任務(wù)執(zhí)行:Sat Aug 18 22:23:29 CST 2018
......

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

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

Cron表達式詳解

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

依次順序如下表所示:

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

簡單舉例:

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

更多表達式,可訪問:http://cron.qqe2.com/?進行在線表達式編寫。簡單明了。

在線表達式編輯器

自定義線程池

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

0.編寫配置類,同時啟用@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-");
        // 線程池對拒絕任務(wù)(無線程可用)的處理策略,目前只支持AbortPolicy、CallerRunsPolicy;默認為后者
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //調(diào)度器shutdown被調(diào)用時等待當前被調(diào)度的任務(wù)完成
        taskExecutor.setWaitForTasksToCompleteOnShutdown(true);
        //等待時長
        taskExecutor.setAwaitTerminationSeconds(60);
        taskExecutor.initialize();
        return taskExecutor;
    }
}

1.調(diào)度類上加入@Async

@Component
@Slf4j
public class ScheduledTask {

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

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

2018-08-18 22:47:13.313  INFO 14212 --- [ong-Scheduled-1] c.l.l.s.c.controller.ScheduledTask       : Scheduled定時任務(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啟動!
2018-08-18 22:47:18.308  INFO 14212 --- [ong-Scheduled-2] c.l.l.s.c.controller.ScheduledTask       : Scheduled定時任務(wù)執(zhí)行:Sat Aug 18 22:47:18 CST 2018

動態(tài)添加定時任務(wù)

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

ThreadPoolTaskScheduler

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

0.創(chuàng)建一個ThreadPoolTaskScheduler類。

@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)用時等待當前被調(diào)度的任務(wù)完成
    executor.setWaitForTasksToCompleteOnShutdown(true);
    //等待時長
    executor.setAwaitTerminationSeconds(60);
    return executor;
}

1.編寫一個控制類,動態(tài)設(shè)置定時任務(wù):

@Autowired
TaskScheduler taskScheduler;

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

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

2.啟動后,訪問接口,即可看見控制臺每3秒輸出一次:

2018-08-18 23:20:39.002  INFO 9120 --- [Kong-Executor-1] c.l.l.s.c.controller.TaskController      : ThreadPoolTaskScheduler定時任務(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定時任務(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定時任務(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定時任務(wù):Sat Aug 18 23:20:48 CST 2018

SchedulingConfigurer

此類十個接口,直接實現(xiàn)其configurerTasks方法即可。

0.編寫配置類:

@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定時任務(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)用時等待當前被調(diào)度的任務(wù)完成
        executor.setWaitForTasksToCompleteOnShutdown(true);
        //等待時長
        executor.setAwaitTerminationSeconds(60);
        return executor;
    }

}

1.啟動后,控制臺也可以看見每3秒輸出一次:

2018-08-18 23:24:39.001  INFO 868 --- [Kong-Executor-1] c.l.l.s.chapter22.config.ScheduleConfig  : SchedulingConfigurer定時任務(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定時任務(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定時任務(wù):Sat Aug 18 23:24:45 CST 2018

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

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

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

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>
<!-- 因為SchedulerFactoryBean中依賴了org.springframework.transaction.PlatformTransactionManager,所以需依賴tx相關(guān)包,其實還是quartz有個分布式功能,是使用數(shù)據(jù)庫完成的。 -->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
</dependency>

1.編寫配置類。

@Configuration
@Slf4j
public class QuartzConfig {
    
    /**
     * 通過工廠類,創(chuàng)建job實例
     * @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 下一個任務(wù)必須等待上一任務(wù)完成
        jobDetail.setConcurrent(false);
        return jobDetail; 
    }
    
    /**
     * 通過工廠類創(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)度工廠類,自動注入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()]));
//        }
        //這里注意 對應(yīng)的trigger 不能為null 不然會異常的
        bean.setTriggers(triggers);
        return bean;
    } 
    
    @Component("quartzTask")
    public class QuartzTask {
      
        public void quartzTask() {
            log.info("Quartz定時任務(wù):" + new Date());
        }
    }
}

2.啟動后,可以看見控制臺以每3秒執(zhí)行一次輸出:

2018-08-18 23:42:03.019  INFO 772 --- [ryBean_Worker-2] c.l.l.s.chapter22.config.QuartzConfig    : Quartz定時任務(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定時任務(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定時任務(wù):Sun Aug 18 23:42:09 CST 2018

關(guān)于Quartz的詳細用法,再次不表了。好久沒有使用過了。有機會再來詳細闡述吧。

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

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

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

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

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

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

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

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

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

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

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

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

參考資料

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

最后

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

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

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

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

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