定時任務
項目開發中會涉及很多需要定時執行的代碼,如每日凌晨對前一日的數據進行匯總,或者系統緩存的清理、對每日的數據進行分析和總結等需求,這些都是定時任務。單體系統和分布式系統的分布式任務有很大的區別,單體系統就一個任務執行類,非常簡單,分布式系統則要保證定時任務執行的唯一性,不能讓一個定時任務被執行多次。
實現定時任務的5種方式
Java定時任務目前主要有以下5種實現方式。
JDK自帶的實現方式,如JDK自帶的Timer和JDK 1.5+新增的ScheduledExecutor- Service;
elastic-job:功能完備的分布式定時任務框架;
Spring 3.0以后自帶的task:可以將它看成一個輕量級的任務調度;
使用Quartz實現定時任務;
分布式任務調度:可以使用國產組件XXL-Job實現。
下面分別講解不同的定時任務的實現。
實戰:基于JDK方式實現簡單定時
使用JDK方式實現定時任務有兩種方法:
(1)第一種是使用Timer類進行實現,Timer是JDK自帶的定時任務執行類,任何項目都可以直接使用Timer來實現定時任務,因此Timer的優點就是使用方便。但是Timer的缺點也很明顯,這是個單線程的實現,如果任務執行時間太長或者發生異常,則會影響其他任務執行。在開發和測試環境中可以用Timer類進行測試,強烈建議在生產環境中謹慎使用,使用Timer實現的定時任務代碼如下:
package com.example.springextenddemo.dingshi;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Timer;
import java.util.TimerTask;
public class TimerDemo {
//定義時間格式
private static DateTimeFormatter pattern =
DateTimeFormatter.ofPattern
("yyyy-MM-dd HH:MM:ss");
public static void main(String[] args) {
Timer timer = new Timer();
/**
* 從當前時刻開始,每1s執行一次,方法的入參單位為毫秒(ms,1000毫秒即
1s)
*/
timer.schedule(new MyTask(),0,1000);
}
/**
* 自定義任務實現
*/
private static class MyTask extends TimerTask {
@Override
public void run() {
LocalDateTime now = LocalDateTime.now();
System.out.println("這是定時任務,時間
是:"+pattern.format(now));
}
}
}
執行當前的main()方法,可以看到控制臺打印的定時任務日志如圖6.10所示。
圖6.10 Timer定時任務
Timer類設定定時任務有以下3種重載方法:
schedule(TimerTask task, long delay):延遲delay毫秒再執行任務;
schedule(TimerTask task, Date time):在特定的時間執行任務;
schedule(TimerTask task, long delay, long period):延遲delay毫秒執行并每隔period毫秒執行一次。
(2)使用JDK實現定時任務的第二種方式就是使用ScheduledExecutorService類。該類是Java 1.5后新增的定時任務接口,主要有以下幾種方法:
public interface ScheduledExecutorService extends ExecutorService {
public ScheduledFuture<?> schedule(Runnable command,
long delay, TimeUnit unit); public <V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay, TimeUnit unit);
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable
command,
long initialDelay,
long delay,
TimeUnit unit);
}
ScheduledExecutorService類的基本原理和Timer相似,下面使用ScheduledExecutor- Service實現和Timer一樣的定時任務功能:
package com.example.springextenddemo.dingshi;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorServiceDemo {
//時間格式
private static DateTimeFormatter pattern =
DateTimeFormatter.ofPattern
("yyyy-MM-dd HH:MM:ss");
public static void main(String[] args) {
ScheduledExecutorService service =
Executors.newScheduledThreadPool(1);
service.scheduleAtFixedRate(() -> { LocalDateTime now = LocalDateTime.now();
System.out.println("schedule 這是定時任務,時間是:" +
pattern.format(now));
}, 0, 1000, TimeUnit.MILLISECONDS);
}
}
執行main()方法,控制臺打印的日志如圖6.11所示,與上面的Timer實現了相同的效果。在開發過程中,如果只是簡單的定時任務,建議直接采用ScheduleExecutorsService類來處理,這是線程池技術,能夠實現線程的復用。
實戰:基于Spring Task實現定時任務
Spring Task的核心實現類位于spring-context包中,在Spring項目中可以直接使用該定時任務類。下面演示Spring Task定時任務的實現過程。添加一個新的類SpringTaskDemo,代碼如下:
package com.example.springextenddemo.dingshi;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@EnableScheduling //開啟定時任務
@Component
public class SpringTaskDemo {
private static DateTimeFormatter pattern =
DateTimeFormatter.ofPattern
("yyyy-MM-dd HH:MM:ss");
/**
* 每秒鐘執行一次
*/
@Scheduled(cron = "0/1 * * * * ?")
public void cron() {
LocalDateTime now = LocalDateTime.now();
System.out.println("spring task 這是定時任務,時間是:" +
pattern.format(now));
}
}
再次啟動SpringBoot項目,然后就可以自動啟動Spring Task了,定時任務執行結果如圖6.12所示。@EnableScheduling注解表示開啟SpringTask任務,如果不開啟,就沒有辦法執行定時任務。@Scheduled(cron = "0/1 * ** * ?")注解表示每分鐘執行一次,注解中的“0/1 * * * * ?”是cron表達式,cron表達式包括Seconds、Minutes、Hours、Day-of-Month、Month、Day-of-Week和Year(可選字段),它們之間以空隔分隔。讀者可根據要實現的業務完成cron表達式的拼接。cron中一些特殊字符的含義如表6.3所示。
@Scheduled注解支持非常多的參數,以幫助開發者快速完成定時任務的開發,這些參數如下:
cron:cron表達式,指定任務在特定的時間執行;
fixedDelay:上一次任務執行完成后隔多長時間再次執行,參數類型為long,單位為ms;
fixedDelayString:與fixedDelay的含義一樣,只是參數類型變為String;
fixedRate:按一定的頻率執行任務,參數類型為long,單位為ms;fixedRateString:與fixedRate的含義一樣,只是參數類型變為String;
initialDelay:第一次任務延遲多久再執行,參數類型為long,單位為ms;
initialDelayString:與initialDelay的含義一樣,只是參數類型變為String;
zone:時區,默認為當前時區,一般不用。
基于Spring Task強大的功能和便捷性,在開發Spring項目時,筆者推薦
使用Spring Task完成定時任務的需求。
實戰:基于Quartz實現定時調度
Quartz是一個由Java編寫的開源任務調度框架,其通過觸發器設置作業定時運行規則,控制作業的運行時間。Quartz還可以搭建成集群服務,其中,Quartz集群通過故障切換和負載平衡的功能,能給調度器帶來高可用性和伸縮性。我們一般用Quartz來執行定時任務,如定時發送信息、定時生成報表等。在分布式系統中,也可以使用Quartz完成任務調度的需求。
Quartz框架的核心組件包括調度器、觸發器和作業。調度器是作業的總指揮,觸發器是作業的操作者,作業為應用的功能模塊。
Quartz框架中的Job為任務接口,該接口只有一個方法voidexecute(JobExecution- Context context),自定義定時任務的類需要實現Job接口并重寫execute()方法,在該方法中完成定時業務邏輯。
JobExecutionContext類提供了調度上下文的各種信息。每次執行Job時均需要重新創建一個Job實例。
下面再介紹幾個Quartz常用的幾個類:JobDetail類用來描述Job的實現類及其他相關的靜態信息;Trigger類是定時任務的定時管理工具,一個Trigger只能對應一個定時任務,而一個定時任務卻可對應多個觸發器;
Scheduler類是定時任務的管理容器,是Quartz最上層的接口,它管理所有觸發器和定時任務,使它們協調工作,每個Scheduler都保存有JobDetail和Trigger的注冊信息,一個Scheduler類中可以注冊多個JobDetail和多個Trigger。
基于以上介紹,使用Quartz重新實現6.2.2小節的定時任務。
(1)在pom.xml中添加Quartz的依賴,其坐標如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
(2)修改log4j2.xml的日志記錄器,添加Quartz包的日志級別為INFO,不要打印DEBUG級別的日志。
<loggers>
<!--Spring和MyBatis的日志級別為INFO-->
<logger name="org.springframework" level="INFO"></logger>
<logger name="org.mybatis" level="INFO"></logger>
<logger name="org.quartz" level="INFO"></logger>
<!-- 自定義包設置為INFO,則可以看見輸出的日志不包含DEBUG輸出了 -->
<logger name="com.example.springextenddemo" level="INFO"/>
<root level="all">
<appender-ref ref="myAppender"/>
</root>
</loggers>
(3)自定義任務執行類,添加Quartz的任務類:
package com.example.springextenddemo.dingshi;import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class MyQuartzTask extends QuartzJobBean{
private static DateTimeFormatter pattern =
DateTimeFormatter.ofPattern
("yyyy-MM-dd HH:MM:ss");
@Override
public void executeInternal(JobExecutionContext context) throws
JobExecutionException {
LocalDateTime now = LocalDateTime.now();
System.out.println("quartz 這是定時任務,時間是:" +
pattern.format(now));
}
}
(4)添加Quzrtz的配置類,配置定時任務的執行時間和頻率。
package com.example.springextenddemo.dingshi;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QuartzConfig {
@Bean
public JobDetail testQuartz1() {
return JobBuilder.newJob(MyQuartzTask.class).withIdentity
("myQuartzTask") .storeDurably().build();
}
@Bean
public Trigger testQuartzTrigger1() {
//1s執行一次
SimpleScheduleBuilder scheduleBuilder =
SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(1)
.repeatForever();
return TriggerBuilder.newTrigger().forJob(testQuartz1())
.withIdentity("myQuartzTask")
.withSchedule(scheduleBuilder)
.build();
}
}
(5)啟動當前項目會自動加載定時任務,通過控制臺就能看到Quartz定時任務的執行情況,控制臺打印的日志如圖6.13所示。
至此,在項目中使用定時任務的例子便介紹完了,在開發中可以直接使用Timer或者ScheduledExecutorService進行定時任務的測試,在實際的生產環境中,應根據項目情況選擇使用Spring Task或者Quartz來實現需求。