詳解當當網的分布式作業框架elastic-job
原文 http://www.infoq.com/cn/articles/dangdang-distributed-work-framework-elastic-job
作業的必要性以及存在的問題
1. 為什么需要作業?
作業即定時任務。一般來說,系統可使用消息傳遞代替部分使用作業的場景。兩者確有相似之處。可互相替換的場景,如隊列表。將待處理的數據放入隊列 表,然后使用頻率極短的定時任務拉取隊列表的數據并處理。這種情況使用消息中間件的推送模式可更好的處理實時性數據。而且基于數據庫的消息存儲吞吐量遠遠 小于基于文件的順序追加消息存儲。
(點擊放大圖像)
但在某些場景下則不能互換:
a) 時間驅動 OR 事件驅動:內部系統一般可以通過事件來驅動,但涉及到外部系統,則只能使用時間驅動。如:抓取外部系統價格。每小時抓取,由于是外部系統,不能像內部系統一樣發送事件觸發事件。
b) 批量處理 OR 逐條處理:批量處理堆積的數據更加高效,在不需要實時性的情況下比消息中間件更有優勢。而且有的業務邏輯只能批量處理,如:電商公司與快遞公司結算,一個 月結算一次,并且根據送貨的數量有提成。比如,當月送貨超過1000則額外給快遞公司多1%的快遞費。
c) 非實時性 OR 實時性:雖然消息中間件可以做到實時處理數據,但有的情況并不需要。如:VIP用戶降級,如果超過1年無購買行為,則自動降級。這類需求沒有強烈的時間要求,不需要按照時間精確的降級VIP用戶。
d) 系統內部 OR 系統解耦:作業一般封裝在系統內部,而消息中間件可用于系統間解耦。
2. 當前常見的作業系統存在哪些問題?
a) Quartz:Java事實上的定時任務標準。但Quartz關注點在于定時任務而非數據,并無一套根據數據處理而定制化的流程。雖然Quartz可以基于數據庫實現作業的高可用,但缺少分布式并行調度的功能。
b) TBSchedule:阿里早期開源的分布式任務調度系統。代碼略陳舊,使用timer而非線程池執行任務調度。眾所周知,timer在處理異常狀況時是 有缺陷的。而且TBSchedule作業類型較為單一,只能是獲取/處理數據一種模式。還有就是文檔缺失比較嚴重。
c) Crontab:Linux系統級的定時任務執行器。缺乏分布式和集中管理功能。
綜上所述,當前存在的作業系統缺少分布式、并行調度、彈性擴容縮容、集中管理、定制化流程型任務等功能,所以需要一個新的作業系統完善這些功能。
解決思路
修改開源產品和基于開源產品重新搭建,是兩種可行性較高的方案。
修改開源產品可控性較低,且一般都是針對于某些特定功能,所以我們采用將成熟的開源產品作為積木,在其之上重新封裝,搭建一個嶄新的產品,并命名為elastic-job。
elastic-job主要的設計理念是無中心化的分布式定時調度框架,思路來源于Quartz的基于數據庫的高可用方案。但數據庫沒有分布式協調功能,所以在高可用方案的基礎上增加了彈性擴容和數據分片的思路,以便于更大限度的利用分布式服務器的資源。
1. 主要功能
a) 分布式:重寫Quartz基于數據庫的分布式功能,改用Zookeeper實現注冊中心。
b) 并行調度:采用任務分片方式實現。將一個任務拆分為n個獨立的任務項,由分布式的服務器并行執行各自分配到的分片項。
c) 彈性擴容縮容:將任務拆分為n個任務項后,各個服務器分別執行各自分配到的任務項。一旦有新的服務器加入集群,或現有服務器下線,elastic-job將在保留本次任務執行不變的情況下,下次任務開始前觸發任務重分片。
d) 集中管理:采用基于Zookeeper的注冊中心,集中管理和協調分布式作業的狀態,分配和監聽。外部系統可直接根據Zookeeper的數據管理和監控elastic-job。
e) 定制化流程型任務:作業可分為簡單和數據流處理兩種模式,數據流又分為高吞吐處理模式和順序性處理模式,其中高吞吐處理模式可以開啟足夠多的線程快速的處 理數據,而順序性處理模式將每個分片項分配到一個獨立線程,用于保證同一分片的順序性,這點類似于kafka的分區順序性。
2. 其他功能
a) 失效轉移:彈性擴容縮容在下次作業運行前重分片,但本次作業執行的過程中,下線的服務器所分配的作業將不會重新被分配。失效轉移功能可以在本次作業運行中用空閑服務器抓取孤兒作業分片執行。同樣失效轉移功能也會犧牲部分性能。
b) Spring命名空間支持:elastic-job可以不依賴于spring直接運行,但是也提供了自定義的命名空間方便與spring集成。
c) 運維平臺:提供web控制臺用于管理作業。
3. 非功能需求
a) 穩定性:在服務器無波動的情況下,并不會重新分片;即使服務器有波動,下次分片的結果也會根據服務器IP和作業名稱哈希值算出穩定的分片順序,盡量不做大的變動。
b) 高性能:同一服務器的批量數據處理采用自動切割并多線程并行處理。
c) 靈活性:所有在功能和性能之間的權衡,都可通過配置開啟/關閉。如:elastic-job會將作業運行狀態的必要信息更新到注冊中心。如果作業執行頻度 很高,會造成大量Zookeeper寫操作,而分布式Zookeeper同步數據可能引起網絡風暴。因此為了考慮性能問題,可以犧牲一些功能,而換取性能 的提升。
d) 冪等性:elastic-job可犧牲部分性能用以保證同一分片項不會同時在兩個服務器上運行。
e) 容錯性:作業服務器和Zookeeper斷開連接則立即停止作業運行,用于防止分片已經重新分配,而腦裂的服務器仍在繼續執行,導致重復執行。
實現方案及開發理念
1. elastic-job的具體模塊的底層及如何實現
elastic-job采用去中心化設計,主要分為注冊中心,數據分片,分布式協調,定時任務處理和定制化流程型任務等模塊。
a) 去中心化
去中心化指elastic-job并無調度中心這一概念,每個運行在集群中的作業服務器都是對等的,節點之間通過注冊中心進行分布式協調。但 elastic-job有主節點的概念,主節點用于處理一些集中式任務,如分片,清理運行時信息等,并無調度功能,定時調度都是由作業服務器自行觸發。
下面對比一下各自的優缺點:
中心化 |
去中心化 |
|
實現難度 |
高 |
低 |
部署難度 |
高 |
低 |
觸發時間統一控制 |
可以 |
不可以 |
觸發延遲 |
有 |
無 |
異構語言支持 |
容易 |
困難 |
b) 注冊中心
注冊中心模塊目前直接使用zookeeper,用于記錄作業的配置,服務器信息以及作業運行狀態。Zookeeper雖然很成熟,但原理復雜,使 用較難,在海量數據支持的情況下也會有性能和網絡問題。目前elastic-job已經抽象出注冊中心的接口,下一步將會考慮支持多注冊中心,如 etcd,或由用戶自行實現注冊中心。無臨時節點和監聽機制的注冊中心需要自行實現定時心跳監測等功能。
c) 數據分片
數據分片是elastic-job中實現分布式的重要概念,將真實數據和邏輯分片對應,用于解耦作業框架和數據的關系。作業框架只負責將分片合理 的分配給相關的作業服務器,而作業服務器需要根據所分配的分片匹配數據進行處理。服務器分片目前都存儲在注冊中心中,各個服務器根據自己的IP地址拉取分 片。
d) 分布式協調
分布式協調模塊用于處理作業服務器的動態擴容縮容。一旦集群中有服務器發生變化,分布式協調將自動監測并將變化結果通知仍存活的作業服務器。協調時將會涉及主節點選舉,重分片等操作。目前使用的Zookeeper的臨時節點和監聽器實現主動檢查和通知功能。
e) 定時任務處理
定時任務處理根據cron表達式定時觸發任務,目前有防止任務同時觸發,錯過任務重出發等功能。主要還是使用Quartz本身的定時調度功能,為了便于控制,每個任務都使用獨立的線程池。
f) 定制化流程型任務
定制化流程型任務將定時任務分為多種流程,有不經任何修飾的簡單任務;有用于處理數據的fetchData/processData的數據流任務;以后還將增加消息流任務,文件任務,工作流任務等。用戶能以插件的形式擴展并貢獻代碼。
2. 部署和使用
將使用elastic-job框架的jar/war連接同一個基于Zookeeper的注冊中心即可。
3. 對開源產品的開發理念
elastic-job的開源主要是為了反饋社區。開源短短兩個月,我們收到了很多朋友的反饋和支持,非常感謝。技術類開源項目和一般的業務型項目不同,更需要對代碼和質量的控制,我們總結出以下幾點:
a) 用心寫代碼,用代碼講故事。代碼是項目的唯一核心和產出,任何一行的代碼都需要用心思考優雅性,可讀性,合理性。
a) 代碼整潔干凈到極致。只有代碼漂亮整潔,其他開源愛好者才愿意閱讀代碼,進而找出項目中的bug和貢獻高質量代碼。
b) 極簡代碼, 高度復用,無重復代碼和配置。Java生態圈的特點是高質量的開源產品極多。我們盡量考慮復用輪子,比如項目中大量用到lombok簡化代碼;但也不會無 原則的使用開源產品,我們傾向于把開源產品分為積木類和大廈類。項目中一般只考慮使用積木類搭建屬于我們自己的大廈,而不會直接用其他已成型的大廈。
c) 單一需求可不考慮擴展性;兩個類似需求時再提煉。
d) 模塊抽象劃分合理。
e) 如無特殊理由, 測試需全覆蓋。elastic-job核心模塊的測試覆蓋率是95%以上。
f) 對質量的定義。代碼可讀性 > 代碼可測性 > 模塊解耦設計 > 功能正確性 > 性能 > 功能可擴展性。只有代碼可讀,可測試,可100%掌控,項目才可持續發展。功能有缺陷可以修復,性能不夠可以優化,而代碼不清晰則項目會漸漸變為黑盒。所 以對于框架類產品,我們認為質量 > 時間 > 成本。
g) 文檔清晰。
未來展望
目前的elastic-job定位是一個基于java的定時任務調度框架,未來想發展成為支持異構語言,高度靈活,可自定制的定時任務調度產品。
a) 異構語言支持。目前采用的無中心設計,難于支持多語言,考慮調度中心的可行性。
b) 監控體系有待提高,目前只能通過注冊中心做簡單的存活和數據積壓監控。未來需要做的監控部分有:
增加可監控維度,如作業運行時間等。
基于JMX的內部狀態監控。
基于歷史的全量數據監控,將所有監控數據通過flume等形式發到外部監控中心,提供實時分析功能。
c) 多種注冊中心支持。
c) 增加任務工作流,如任務依賴,初始化任務,清理任務等。
d) 失效轉移功能的實時性提升。
e) 更多作業類型支持,如文件,MQ等類型作業的支持。
f) 更多分片策略支持。
附錄:elastic-job的來歷
elastic-job原本是當當java應用框架ddframe的一部分,本名dd-job。
ddframe包括編碼規范,開發框架,技術規范,監控以及分布式組件。
當當希望將ddframe的各個模塊與公司環境解耦并開源以反饋社區。之前開源的Dubbo擴展版本DubboX即是dd-soa的核心模塊。而 本次介紹的elastic-job則是dd-job的開源部分,其中監控(但開源了監控方法)和ddframe核心接入等部分并未開源。
項目的開源地址: https://github.com/dangdangdotcom/elastic-job
作者簡介
張亮,當當網架構師,當當技術委員會成員。對架構設計、消息中間件、分布式等領域興趣濃厚。 目前主導當當應用框架ddframe研發和推廣以及技術白皮書撰寫。其中ddframe的分布式作業部分elastic-job已經正式開源。