什么是任務調度
我們可以先思考一下下面業務場景的解決方案:
某電商系統需要在每天上午10點,下午3點,晚上8點發放一批優惠券。
某銀行系統需要在信用卡到期還款日的前三天進行短信提醒。
某財務系統需要在每天凌晨0:10結算前一天的財務數據,統計匯總。
12306會根據車次的不同,而設置某幾個時間點進行分批放票。
某網站為了實現天氣實時展示,每隔5分鍾就去天氣服務器獲取最新的實時天氣信息。
以上場景就是任務調度所需要解決的問題。
任務調度是指系統為了自動完成特定任務,在約定的特定時刻去執行任務的過程。有了任務調度即可解放更多的人力由系統自動去執行任務。
任務調度如何實現?
多線程方式實現:
學過多線程的同學,可能會想到,我們可以開啟一個線程,每sleep一段時間,就去檢查是否已到預期執行時間。
以下代碼簡單實現了任務調度的功能:
public static void main(String[] args) {
//任務執行間隔時間
final long timeInterval = 1000;
Runnable runnable = new Runnable() {
public void run() {
while (true) {
//TODO:something
try {
Thread.sleep(timeInterval);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread thread = new Thread(runnable);
thread.start();
}
上面的代碼實現了按一定的間隔時間執行任務調度的功能。
Jdk也為我們提供了相關支持,如:Timer、ScheduledExecutor,下邊我們了解下。
Timer方式實現:
public static void main(String[] args){
Timer timer = new Timer();
timer.schedule(new TimerTask(){
@Override
public void run() {
//TODO:something
}
}, 1000, 2000); //1秒后開始調度,每2秒執行一次
}
Timer 的優點在於簡單易用,每個Timer對應一個線程,因此可以同時啟動多個Timer並行執行多個任務,同一個Timer中的任務是串行執行。
ScheduledExecutor方式實現:
public static void main(String [] agrs){
ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
service.scheduleAtFixedRate(
new Runnable() {
@Override
public void run() {
//TODO:something
System.out.println("todo something");
}
}, 1,
2, TimeUnit.SECONDS);
}
Java 5 推出了基於線程池設計的 ScheduledExecutor,其設計思想是,每一個被調度的任務都會由線程池中一個線程去執行,因此任務是並發執行的,相互之間不會受到干擾。
Timer 和 ScheduledExecutor 都僅能提供基於開始時間與重復間隔的任務調度,不能勝任更加復雜的調度需求。比如:設置每月第一天凌晨1點執行任務、復雜調度任務的管理、任務間傳遞數據等等。
Quartz 是一個功能強大的任務調度框架,它可以滿足更多更復雜的調度需求,Quartz 設計的核心類包括 Scheduler, Job 以及 Trigger。其中:Job 負責定義需要執行的任務,Trigger 負責設置調度策略,Scheduler 將二者組裝在一起,並觸發任務開始執行。Quartz支持簡單的按時間間隔調度、還支持按日歷調度方式,通過設置CronTrigger表達式(包括:秒、分、時、日、月、周、年)進行任務調度。
第三方Quartz方式實現:
public static void main(String [] agrs) throws SchedulerException {
//創建一個Scheduler
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
//創建JobDetail
JobBuilder jobDetailBuilder = JobBuilder.newJob(MyJob.class);
jobDetailBuilder.withIdentity("jobName","jobGroupName");
JobDetail jobDetail = jobDetailBuilder.build();
//創建觸發的CronTrigger 支持按日歷調度
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity("triggerName", "triggerGroupName")
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?"))
.build();
//創建觸發的SimpleTrigger 簡單的間隔調度
/SimpleTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity("triggerName","triggerGroupName")
.startNow()
.withSchedule(SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(2)
.repeatForever())
.build();/
scheduler.scheduleJob(jobDetail,trigger);
scheduler.start();
}
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext){
System.out.println("todo something");
}
}
通過以上內容我們學習了什么是任務調度,任務調度所解決的問題,以及任務調度的多種實現方式。
2.什么是分布式任務調度
什么是分布式?
當前軟件的架構正在逐步轉變為分布式架構,將單體結構分為若干服務,服務之間通過網絡交互來完成用戶的業務處理,如下圖:電商系統為分布式架構,由訂單服務、商品服務、用戶服務等組成:
分布式系統具體如下基本特點:
分布性:每個部分都可以獨立部署,服務之間交互通過網絡進行通信,比如:訂單服務、商品服務。
伸縮性:每個部分都可以集群方式部署,並可針對部分結點進行硬件及軟件擴容,具有一定的伸縮能力。
高可用:每個部分都可以集群部分,保證高可用。
什么是分布式調度?
通常任務調度的程序是集成在應用中的,比如:優惠卷服務中包括了定時發放優惠卷的的調度程序,結算服務中包括了定期生成報表的任務調度程序,由於采用分布式架構,一個服務往往會部署多個冗余實例來運行我們的業務,在這種分布式系統環境下運行任務調度,我們稱之為分布式任務調度,如下圖:
分布式調度要實現的目標:
不管是任務調度程序集成在應用程序中,還是單獨構建的任務調度系統,如果采用分布式調度任務的方式就相當於將任務調度程序分布式構建,這樣就可以具有分布式系統的特點,並且提高任務的調度處理能力:
1.並行任務調度
並行任務調度實現靠多線程,如果有大量任務需要調度,此時光靠多線程就會有瓶頸了,因為一台計算機CPU的處理能力是有限的。
如果將任務調度程序分布式部署,每個結點還可以部署為集群,這樣就可以讓多台計算機共同去完成任務調度,我們可以將任務分割為若干個分片,由不同的實例並行執行,來提高任務調度的處理效率。
2.高可用
若某一個實例宕機,不影響其他實例來執行任務。
3.彈性擴容
當集群中增加實例就可以提高並執行任務的處理效率。
4.任務管理與監測
對系統中存在的所有定時任務進行統一的管理及監測。讓開發人員及運維人員能夠時刻了解任務執行情況,從而做出快速的應急處理響應。
5.避免任務重復執行
當任務調度以集群方式部署,同一個任務調度可能會執行多次,比如在上面提到的電商系統中到點發優惠券的例子,就會發放多次優惠券,對公司造成很多損失,所以我們需要控制相同的任務在多個運行實例上只執行一次,考慮采用下邊的方法:
分布式鎖,多個實例在任務執行前首先需要獲取鎖,如果獲取失敗那么久證明有其他服務已經再運行,如果獲取成功那么證明沒有服務在運行定時任務,那么就可以執行。
ZooKeeper選舉,利用ZooKeeper對Leader實例執行定時任務,其他業務已經使用了ZK,那么執行定時任務的時候判斷自己是否是Leader,如果不是則不執行,如果是則執行業務邏輯,這樣也能達到我們的目的。