什麼是任務調度
咱們能夠先思考一下下面業務場景的解決方案:服務器
- 某電商系統須要在天天上午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方式實現:ide
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,若是不是則不執行,若是是則執行業務邏輯,這樣也能達到咱們的目的。