定時任務如何在分散式情況下完美排程呢
單機定式任務排程的問題
在很多應用系統中我們常常要定時執行一些任務。比如,訂單系統的超時狀態判斷、快取資料的定時更新、定式給使用者發郵件,甚至是一些定期計算的報表等等。常見的處理方式有執行緒的while(true) 和sleep組合、使用Timer定時器觸發任務又或者是使用quartz框架。貌似這些方法可以完美的解決方案,為什麼還需要分散式呢?主要有如下兩點原因:
1.高可用:單機版的定式任務排程只能在一臺機器上執行,如果程式或者系統出現異常就會導致功能不可用。雖然可以在單機程式實現的足夠穩定,但始終有機會遇到非程式引起的故障,而這個對於一個系統的核心功能來說是不可接受的。
2.單機處理極限:原本1分鐘內需要處理1萬個訂單,但是現在需要1分鐘內處理10萬個訂單;原來一個統計需要1小時,現在業務方需要10分鐘就統計出來。你也許會說,你也可以多執行緒、單機多程序處理。的確,多執行緒並行處理可以提高單位時間的處理效率,但是單機能力畢竟有限(主要是CPU、記憶體和磁碟),始終會有單機處理不過來的情況。
這個時候就需要分散式的定時任務來實現了。業內常用的分散式定式任務解決方案主要有quartz、淘寶的TBSchedule和噹噹的elastic-job。
quartz的叢集解決方案
quartz的單機版本大家應該比較熟悉,它的叢集方案是使用資料庫來實現的。叢集架構如下:
上圖三個節點在資料庫中都擁有同一份Job定義,如果某一個節點失效,那麼Job會在其他節點上執行。由於三個節點上的Job執行程式碼是一樣的,那麼怎麼保證只有在一臺機器上觸發呢?答案是使用了資料庫鎖。在quartz的叢集解決方案裡有張表scheduler_locks,quartz採用了悲觀鎖的方式對triggers表進行行加鎖,以保證任務同步的正確性。一旦某一個節點上面的執行緒獲取了該鎖,那麼這個Job就會在這臺機器上被執行,同時這個鎖就會被這臺機器佔用。同時另外一臺機器也會想要觸發這個任務,但是鎖已經被佔用了,就只能等待,直到這個鎖被釋放。之後會看trigger狀態,如果已經被執行了,則不會執行了。
簡單地說,quartz的分散式排程策略是以資料庫為邊界資源的一種非同步策略。各個排程器都遵守一個基於資料庫鎖的操作規則從而保證了操作的唯一性。同時多個節點的非同步執行保證了服務的可靠。但這種策略有自己的侷限性:叢集特性對於高CPU使用率的任務效果很好,但是對於大量的短任務,各個節點都會搶佔資料庫鎖,這樣就出現大量的執行緒等待資源。這種情況隨著節點的增加會越來越嚴重。
另外,quartz的分散式只是解決了高可用的問題,並沒有解決任務分片的問題,還是會有單機處理的極限。
TBSchedule
TBSchedule是一款非常優秀的高效能分散式排程框架,廣泛應用於阿里巴巴、淘寶、支付寶、京東、聚美、汽車之家、國美等很多網際網路企業的流程排程系統。tbschedule在時間排程方面雖然沒有quartz強大,但是它支援分片功能。和quartz不同的是,tbschedule使用ZooKeeper來實現任務排程的高可用和分片。
TBSchedule的分散式機制是通過靈活的Sharding方式實現的,分片的規則由客戶端決定,比如可以按所有資料的ID按10取模分片、按月份分片等等。TBSchedule的宿主伺服器可以進行動態擴容和資源回收,這個特點主要是因為它後端依賴的ZooKeeper,這裡的ZooKeeper對於TBSchedule來說是一個NoSQL,用於儲存策略、任務、心跳資訊資料,它的資料結構類似檔案系統的目錄結構,它的節點有臨時節點、持久節點之分。排程引擎啟動後,隨著業務量資料量的增多,當前Cluster可能不能滿足目前的處理需求,那麼就需要增加伺服器數量,一個新的伺服器上線後會在ZooKeeper中建立一個代表當前伺服器的一個唯一性路徑(臨時節點),並且新上線的伺服器會和ZooKeeper保持長連線,當通訊斷開後,節點會自動摘除。
TBSchedule會定時掃描當前伺服器的數量,重新進行任務分配。TBSchedule不僅提供了服務端的高效能排程服務,還提供了一個scheduleConsole的war包,隨著宿主應用的部署直接部署到伺服器,可以通過web的方式對排程的任務、策略進行監控管理,以及實時更新調整。
elastic-job
Elastic-Job噹噹開源的分散式排程解決方案,由兩個相互獨立的子專案Elastic-Job-Lite和Elastic-Job-Cloud組成。Elastic-Job-Lite定位為輕量級無中心化解決方案,使用jar包的形式提供分散式任務的協調服務。一般我們只要使用Elastic-Job-Lite就好。
Elastic-Job-Lite並沒有宿主程式,而是基於部署作業框架的程式在到達相應時間點時各自觸發排程。它的開發也比較簡單,引用Jar包實現一些方法即可,最後編譯成Jar包執行。Elastic-Job-Lite的分散式部署全靠ZooKeeper來同步狀態和原資料。實現高可用的任務只需將分片總數設定為1,並把開發的Jar包部署於多個伺服器上執行,任務將會以1主N從的方式執行。一旦本次執行任務的伺服器崩潰,其他執行任務的伺服器將會在下次作業啟動時選擇一個替補執行。如果開啟了失效轉移,那麼功能效果更好,可以保證在本次作業執行時崩潰,備機之一立即啟動替補執行。
Elastic-Job-Lite的任務分片也是通過ZooKeeper來實現,Elastic-Job並不直接提供資料處理的功能,框架只會將分片項分配至各個執行中的作業伺服器,開發者需要自行處理分片項與真實資料的對應關係。框架也預置了一些分片策略:平均分配演算法策略,作業名雜湊值奇偶數演算法策略,輪轉分片策略。同時也提供了自定義分片策略的介面。
另外Elastic-Job-Lite還提供了一個任務監控和管理介面:Elastic-Job-Lite-Console。它和Elastic-Job-Lite是兩個完全不關聯的應用程式,使用ZooKeeper來交換資料,管理人員可以通過這個介面檢視、監控和管理Elastic-Job-Lite的任務,必要的時候還能手動觸發任務。
elastic-job結合了quartz非常優秀的時間排程功能,並且利用ZooKeeper實現了靈活的分片策略。除此之外,還加入了大量實用的監控和管理功能,以及其開源社群活躍、文件齊全、程式碼優雅等優點,是分散式任務排程框架的推薦選擇。
Saturn
Saturn是唯品會在github開源的一款分散式任務排程產品。它是基於噹噹elastic-job來開發的,其上完善了一些功能和添加了一些新的feature。目前在github上開源大半年,470個star。Saturn的任務可以用多種語言開發比如python、Go、Shell、Java、Php。其在唯品會內部已經發部署350+個節點,每天任務排程4000多萬次。同時,管理和統計也是它的亮點。