天天看點

定時任務的使用

參考:http://www.importnew.com/29864.html

上兩章節,我們簡單的講解了關于異步調用和異步請求相關知識點。這一章節,我們來講講開發過程也是經常會碰見的定時任務。比如每天定時清理無效資料、定時發送短信、定時發送郵件、支付系統中的定時對賬等等,往往都會定義一些定時器,進行此業務的開發。是以,本章節介紹下在

SpringBoot

中定時任務如何使用及一點分布式定時服務的思考總結。

一點知識

JAVA

開發領域,目前可以通過以下幾種方式進行定時任務:

  • Timer:jdk中自帶的一個定時排程類,可以簡單的實作按某一頻度進行任務執行。提供的功能比較單一,無法實作複雜的排程任務。
  • ScheduledExecutorService:也是jdk自帶的一個基于線程池設計的定時任務類。其每個排程任務都會配置設定到線程池中的一個線程執行,是以其任務是并發執行的,互不影響。
  • Spring Task:

    Spring

    提供的一個任務排程工具,支援注解和配置檔案形式,支援

    Cron

    表達式,使用簡單但功能強大。
  • Quartz:一款功能強大的任務排程器,可以實作較為複雜的排程功能,如每月一号執行、每天淩晨執行、每周五執行等等,還支援分布式排程,就是配置稍顯複雜。
題外話:對于

Quartz

,早前用過1.6版本的,更新到2.x及以上版本後基本沒怎麼接觸了,原來還有倒騰過結合

Kettle

做了一些動态的定時抽取資料啥的還編寫過一個

Cron

表達式編輯器,現在基本忘記了。。等有機會,再次深入學習後再來單獨分享一些關于的

Quartz

心得吧。

基于JDK方式實作簡單定時

剛剛有介紹過,基于

JDK

方式一共有兩種:

Timer

ScheduledExecutorService

。接下來,就簡單講解下這兩種方式。

Timer

Timer

是jdk提供的

java.util.Timer

類。

簡單示例:
1 2 3 4 5 6 7 8 9 10 11 12

@GetMapping

(

"/timer"

)

public

String doTimer() {

Timer timer =

new

Timer();

timer.schedule(

new

TimerTask() {

@Override

public

void

run() {

log.info(

"Timer定時任務啟動:"

+

new

Date());

}

},

1000

,

1000

);

//延遲1秒啟動,每1秒執行一次

return

"timer"

;

啟動後,通路即可看見控制台周期性輸出資訊了:

1 2 3 4 5 6

2018

-

08

-

18

21

:

30

:

35.171

INFO

13352

--- [ Timer-

] c.l.l.s.c.controller.TaskController : Timer定時任務啟動:Sat Aug

18

21

:

30

:

35

CST

2018

2018

-

08

-

18

21

:

30

:

36.173

INFO

13352

--- [ Timer-

] c.l.l.s.c.controller.TaskController : Timer定時任務啟動:Sat Aug

18

21

:

30

:

36

CST

2018

2018

-

08

-

18

21

:

30

:

37.173

INFO

13352

--- [ Timer-

] c.l.l.s.c.controller.TaskController : Timer定時任務啟動:Sat Aug

18

21

:

30

:

37

CST

2018

2018

-

08

-

18

21

:

30

:

38.173

INFO

13352

--- [ Timer-

] c.l.l.s.c.controller.TaskController : Timer定時任務啟動:Sat Aug

18

21

:

30

:

38

CST

2018

2018

-

08

-

18

21

:

30

:

39.174

INFO

13352

--- [ Timer-

] c.l.l.s.c.controller.TaskController : Timer定時任務啟動:Sat Aug

18

21

:

30

:

39

CST

2018

......

相關API簡單說明:

1、在特定時間執行任務,隻執行一次

1

public

void

schedule(TimerTask task,Date time)

2、在特定時間之後執行任務,隻執行一次

1

public

void

schedule(TimerTask task,

long

delay)

3、指定第一次執行的時間,然後按照間隔時間,重複執行

1

public

void

schedule(TimerTask task,Date firstTime,

long

period)

4、在特定延遲之後第一次執行,然後按照間隔時間,重複執行

1

public

void

schedule(TimerTask task,

long

delay,

long

period)

5、第一次執行之後,特定頻率執行,與3同

1

public

void

scheduleAtFixedRate(TimerTask task,Date firstTime,

long

period)

6、在delay毫秒之後第一次執行,後按照特定頻率執行

1

public

void

scheduleAtFixedRate(TimerTask task,

long

delay,

long

period)

參數:

  • delay: 延遲執行的毫秒數,即在delay毫秒之後第一次執行
  • period:重複執行的時間間隔

取消任務使用:

timer.cancel()

方法即可登出任務。

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

ScheduledExecutorService

ScheduledExecutorService

可以說是

Timer

的替代類,因為

Timer

不支援多線程,任務是串行的,而且也不捕獲異常,假設某個任務異常了,整個

Timer

就無法運作了。
簡單示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14

@GetMapping

(

"/executor"

)

public

String ScheduledExecutorService() {

//

ScheduledExecutorService service = Executors.newScheduledThreadPool(

10

);

service.scheduleAtFixedRate(

new

Runnable() {

@Override

public

void

run() {

log.info(

"ScheduledExecutorService定時任務執行:"

+

new

Date());

}

},

1

,

1

, TimeUnit.SECONDS);

//首次延遲1秒,之後每1秒執行一次

log.info(

"ScheduledExecutorService定時任務啟動:"

+

new

Date());

return

"ScheduledExecutorService!"

;

}

啟動後,可看見控制台按設定的頻率輸出:

1 2 3 4 5 6

2018

-

08

-

18

22

:

03

:

24.840

INFO

6752

--- [nio-

8080

-exec-

1

] c.l.l.s.c.controller.TaskController : ScheduledExecutorService定時任務啟動: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定時任務執行: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定時任務執行: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定時任務執行: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定時任務執行: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定時任務執行:Sat Aug

18

22

:

03

:

29

CST

2018

可同時設定多個任務,隻需再次設定

scheduleAtFixedRate

即可。

常用方法說明:
  • ScheduleAtFixedRate:
1

public

ScheduledFuture<?> scheduleAtFixedRate(Runnable command,

long

initialDelay,

long

period,TimeUnit unit);

參數說明:

  1. command:執行線程
  2. initialDelay:初始化延時
  3. period:兩次開始執行最小間隔時間
  4. unit:計時機關
  • ScheduleWithFixedDelay:
1

public

ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,

long

initialDelay,

long

delay,TimeUnit unit);

參數說明:

  1. command:執行線程
  2. initialDelay:初始化延時
  3. delay:前一次執行結束到下一次執行開始的間隔時間(間隔執行延遲時間)
  4. unit:計時機關
其他的方法大家可自行谷歌下。

基于SpingTask實作定時任務

使用

SpringTask

SpringBoot

是很簡單的,使用

@Scheduled

注解即可輕松搞定。

0.啟動類,加入

@EnableScheduling

讓注解

@Scheduled

生效。

1 2 3 4 5 6 7 8 9 10

@SpringBootApplication

@EnableScheduling

@Slf4j

public

class

Chapter22Application {

public

static

void

main(String[] args) {

SpringApplication.run(Chapter22Application.

class

, args);

log.info(

"Chapter22啟動!"

);

}

}

1.編寫一個排程類,系統啟動後自動掃描,自動執行。

1 2 3 4 5 6 7 8 9 10 11 12

@Component

@Slf4j

public

class

ScheduledTask {

@Scheduled

(fixedRate=

5000

)

public

void

getCurrentDate() {

log.info(

"Scheduled定時任務執行:"

+

new

Date());

}

}

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

1 2 3 4 5 6

2018

-

08

-

18

22

:

23

:

09.735

INFO

13812

--- [pool-

1

-thread-

1

] c.l.l.s.c.controller.ScheduledTask : Scheduled定時任務執行: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定時任務執行: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定時任務執行: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定時任務執行: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定時任務執行:Sat Aug

18

22

:

23

:

29

CST

2018

......

使用都是簡單的,現在我們來看看注解

@Scheduled

的參數意思:

  1. fixedRate:定義一個按一定頻率執行的定時任務
  2. fixedDelay:定義一個按一定頻率執行的定時任務,與上面不同的是,改屬性可以配合

    initialDelay

    , 定義該任務延遲執行時間。
  3. cron:通過表達式來配置任務執行時間

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 * * * * ?:每秒執行一次
  • 0 0 2 1 * ? : 表示在每月的1日的淩晨2點調整任務
  • 0 0 10,14,16 ? :每天上午10點,下午2點,4點
  • 0 0 12 * * ? : 每天中午12點觸發
  • 0 15 10 ? * MON-FRI : 周一至周五的上午10:15觸發

更多表達式,可通路:cron.qqe2.com/ 進行線上表達式編寫。簡單明了。

自定義線程池

從控制台輸出可以看見,多任務使用的是同一個線程。可結合上章節的異步調用來實作不同任務使用不同的線程進行任務執行。

0.編寫配置類,同時啟用

@Async

注解:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

@Configuration

@EnableAsync

public

class

Config {

@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-"

);

// 線程池對拒絕任務(無線程可用)的處理政策,目前隻支援AbortPolicy、CallerRunsPolicy;預設為後者

taskExecutor.setRejectedExecutionHandler(

new

ThreadPoolExecutor.CallerRunsPolicy());

//排程器shutdown被調用時等待目前被排程的任務完成

taskExecutor.setWaitForTasksToCompleteOnShutdown(

true

);

//等待時長

taskExecutor.setAwaitTerminationSeconds(

60

);

taskExecutor.initialize();

return

taskExecutor;

}

}

1.排程類上加入

@Async

1 2 3 4 5 6 7 8 9 10 11 12 13

@Component

@Slf4j

public

class

ScheduledTask {

@Async

(

"scheduledPoolTaskExecutor"

)

@Scheduled

(fixedRate=

5000

)

public

void

getCurrentDate() {

log.info(

"Scheduled定時任務執行:"

+

new

Date());

}

}

再次啟動程式,可看見控制台輸出,任務已經是不同線程下執行了:

1 2 3 4 5

2018

-

08

-

18

22

:

47

:

13.313

INFO

14212

--- [ong-Scheduled-

1

] c.l.l.s.c.controller.ScheduledTask : Scheduled定時任務執行: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定時任務執行:Sat Aug

18

22

:

47

:

18

CST

2018

動态添加定時任務

使用注解的方式,無法實作動态的修改或者添加新的定時任務的,這個使用就需要使用程式設計的方式進行任務的更新操作了。可直接使用

ThreadPoolTaskScheduler

或者

SchedulingConfigurer

接口進行自定義定時任務建立。

ThreadPoolTaskScheduler

ThreadPoolTaskScheduler

SpringTask

的核心實作類,該類提供了大量的重載方法進行任務排程。這裡簡單示例下,具體的大家自行搜尋下,用的少不太了解呀。

0.建立一個

ThreadPoolTaskScheduler

類。

1 2 3 4 5 6 7 8 9 10 11 12

@Bean

(

"taskExecutor"

)

public

TaskScheduler taskExecutor() {

ThreadPoolTaskScheduler executor =

new

ThreadPoolTaskScheduler();

executor.setPoolSize(

20

);

executor.setThreadNamePrefix(

"oKong-taskExecutor-"

);

executor.setRejectedExecutionHandler(

new

ThreadPoolExecutor.CallerRunsPolicy());

//排程器shutdown被調用時等待目前被排程的任務完成

executor.setWaitForTasksToCompleteOnShutdown(

true

);

//等待時長

executor.setAwaitTerminationSeconds(

60

);

return

executor;

}

1.編寫一個控制類,動态設定定時任務:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

@Autowired

TaskScheduler taskScheduler;

@GetMapping

(

"/poolTask"

)

public

String threadPoolTaskScheduler() {

taskScheduler.schedule(

new

Runnable() {

@Override

public

void

run() {

log.info(

"ThreadPoolTaskScheduler定時任務:"

+

new

Date());

}

},

new

CronTrigger(

"0/3 * * * * ?"

));

//每3秒執行一次

return

"ThreadPoolTaskScheduler!"

;

}

2.啟動後,通路接口,即可看見控制台每3秒輸出一次:

1 2 3 4

2018

-

08

-

18

23

:

20

:

39.002

INFO

9120

--- [Kong-Executor-

1

] c.l.l.s.c.controller.TaskController : ThreadPoolTaskScheduler定時任務: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定時任務: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定時任務: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定時任務:Sat Aug

18

23

:

20

:

48

CST

2018

SchedulingConfigurer

此類十個接口,直接實作其

configurerTasks

方法即可。

0.編寫配置類:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

@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定時任務:"

+

new

Date());

}

},

new

CronTrigger(

"0/3 * * * * ?"

));

//每3秒執行一次

}

@Bean

(

"taskExecutor"

)

public

TaskScheduler taskExecutor() {

ThreadPoolTaskScheduler executor =

new

ThreadPoolTaskScheduler();

executor.setPoolSize(

20

);

executor.setThreadNamePrefix(

"oKong-Executor-"

);

executor.setRejectedExecutionHandler(

new

ThreadPoolExecutor.CallerRunsPolicy());

//排程器shutdown被調用時等待目前被排程的任務完成

executor.setWaitForTasksToCompleteOnShutdown(

true

);

//等待時長

executor.setAwaitTerminationSeconds(

60

);

return

executor;

}

}

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

1 2 3

2018

-

08

-

18

23

:

24

:

39.001

INFO

868

--- [Kong-Executor-

1

] c.l.l.s.chapter22.config.ScheduleConfig : SchedulingConfigurer定時任務: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定時任務: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定時任務:Sat Aug

18

23

:

24

:

45

CST

2018

基于Quartz實作定時排程

由于本章節是基于

SpringBoot 1.x

版本的,是以沒有基于

Quartz

starter

配置,這裡直接引入了

Quartz

相關依賴包來內建。

題外話

:原本使用

SpringMvc

時,一般上都是通過

xml

檔案,配置其

org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean

類進行具體執行任務的配置,指定執行的對象和方法。然後通過設定

CronTriggerFactoryBean

或者

SimpleTriggerFactoryBean

設定定時器,最後通過

org.springframework.scheduling.quartz.SchedulerFactoryBean

加入排程的

trigger

。是以,我們就使用

javaConfig

方式進行簡單內建下。

0.加入pom依賴

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

<!-- 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相關包,其實還是quartz有個分布式功能,是使用資料庫完成的。 -->

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-tx</artifactId>

</dependency>

1.編寫配置類。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71

@Configuration

@Slf4j

public

class

QuartzConfig {

@Bean

public

MethodInvokingJobDetailFactoryBean customJobDetailFactoryBean() {

MethodInvokingJobDetailFactoryBean jobDetail =

new

MethodInvokingJobDetailFactoryBean();

//設定執行任務的bean

jobDetail.setTargetBeanName(

"quartzTask"

);

//設定具體執行的方法

jobDetail.setTargetMethod(

"quartzTask"

);

//同步執行,上一任務未執行完,下一任務等待

//true 任務并發執行

//false 下一個任務必須等待上一任務完成

jobDetail.setConcurrent(

false

);

return

jobDetail;

}

@Bean

(name =

"cronTriggerBean"

)

public

Trigger cronTriggerBean(MethodInvokingJobDetailFactoryBean jobDetailFactoryBean)

throws

ParseException {

CronTriggerFactoryBean cronTriggerFactoryBean =

new

CronTriggerFactoryBean();

cronTriggerFactoryBean.setJobDetail(jobDetailFactoryBean.getObject());

cronTriggerFactoryBean.setCronExpression(

"0/3 * * * * ?"

);

//每3秒執行一次

cronTriggerFactoryBean.setName(

"customCronTrigger"

);

cronTriggerFactoryBean.afterPropertiesSet();

return

cronTriggerFactoryBean.getObject();

}

@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()]));

// }

//這裡注意 對應的trigger 不能為null 不然會異常的

bean.setTriggers(triggers);

return

bean;

}

@Component

(

"quartzTask"

)

public

class

QuartzTask {

public

void

quartzTask() {

log.info(

"Quartz定時任務:"

+

new

Date());

}

}

}

2.啟動後,可以看見控制台以每3秒執行一次輸出:

1 2 3

2018

-

08

-

18

23

:

42

:

03.019

INFO

772

--- [ryBean_Worker-

2

] c.l.l.s.chapter22.config.QuartzConfig : Quartz定時任務: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定時任務: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定時任務:Sun Aug

18

23

:

42

:

09

CST

2018

關于

Quartz

的詳細用法,再次不表了。好久沒有使用過了。有機會再來詳細闡述吧。

分布式排程服務淺談

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

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

  • 剝離所有定時任務到一個工程:此方案是最簡單的,在定時任務相對較小,并發任務不多時,可以使用此方案。簡單也容易維護。當定時任務牽扯的業務越來越多,越來越雜時,維護量就成本增加了,工程會越來越臃腫,此方案就不實用了。
  • 利用

    Quartz

    叢集方案:本身

    Quartz

    是支援通過資料庫實作叢集的,以下是其叢集架構圖:

叢集架構圖

其實作原理也相對簡單:通過資料庫實作任務的持久化,儲存定時任務的相關配置資訊,以保證下次系統啟動時,定時任務能自動啟動。同時,通過資料庫

行鎖(for update)

機制,控制一個任務隻能被一個執行個體運作,隻有擷取鎖的執行個體才能運作任務,其他的隻能等待,直到鎖被釋放。這種方式有些弊端,就是依賴了資料庫,同時也需要保證各伺服器之間的時間需要同步,不然也是會混亂的。

現在

Quartz

也有基于

Redis

的叢集方案,有興趣的可以搜尋下。

  • 分布式鎖:可通過使用

    Redis

    或者

    ZooKeeper

    實作一個分布式鎖的機制,使得隻有擷取到鎖的執行個體方能運作定時任務,避免任務重複執行。可檢視下開源的基于

    Redis

    實作的分布式鎖項目:

    redisson

    。github位址:github.com/redisson/re…有興趣的同學可以了解下。
  • 統一排程中心:

可建構一個

純粹

的定時服務,隻有

定時器

相關配置,比如

定時時間

定時排程的api接口

或者

http

服務,甚至是統一注冊中心下的服務類,如dubbo服務等。而具體的任務執行操作都在各自業務方系統中,排程中心

隻負責接口的調用

,具體實作還是在業務方。這種方案相對來說比較通用,實作起來也簡單。就是需要業務方進行約定程式設計,或者對外提供一個api接口。

當然,為了實作定時任務的自動發現和注冊功能,還是需要規範一套規則來實作自動注冊功能。簡單來說,以

Dubbo

服務為例,可以定義一個

定時任務接口類

,排程中心隻需要擷取所有實作此接口的服務,同時通過服務的相關配置(排程時間、失敗政策等)進行相關定時操作。或者編寫一個服務注冊與發現的用戶端,通過

Spring

擷取到實作此接口的所有實作類,上送到排程中心。

而且,統一排程中心,還可以對所有的定時任務的排程情況進行有效監控,日志記錄等,也可以約定接口,讓定時任務回傳定時結果,做到全局把控的目的。

轉載于:https://juejin.im/post/5c94b4a7e51d4536e85c39ab