做者簡介:
張亮,噹噹網架構師、噹噹技術委員會成員、消息中間件組負責人。對架構設計、分佈式、優雅代碼等領域興趣濃厚。目前主導噹噹應用框架ddframe研發,並負責推廣及撰寫技術白皮書。
1、爲何須要做業(定時任務)?
做業即定時任務。通常來講,系統可以使用消息傳遞代替部分使用做業的場景。二者確有類似之處。可互相替換的場景,如隊列表。將待處理的數據放入隊列表,而後使用頻率極短的定時任務拉取隊列表的數據並處理。這種狀況使用消息中間件的推送模式可更好的處理實時性數據。並且基於數據庫的消息存儲吞吐量遠遠小於基於文件的順序追加消息存儲。
但在某些場景下則不能互換:
時間驅動 OR 事件驅動:內部系統通常能夠經過事件來驅動,但涉及到外部系統,則只能使用時間驅動。如:抓取外部系統價格。每小時抓取,因爲是外部系統,不能像內部系統同樣發送事件觸發事件。
批量處理 OR 逐條處理:批量處理堆積的數據更加高效,在不須要實時性的狀況下比消息中間件更有優點。並且有的業務邏輯只能批量處理,如:電商公司與快遞公司結算,一個月結算一次,而且根據送貨的數量有提成。好比,當月送貨超過1000則額外給快遞公司多1%優惠。
非實時性 OR 實時性:雖然消息中間件能夠作到實時處理數據,但有的狀況並不須要如的實時。如:VIP用戶降級,若是超過1年無購買行爲,則自動降級。這類需求沒有強烈的時間要求,不須要按照時間精確的降級VIP用戶。
系統內部 OR 系統解耦。做業通常封裝在系統內部,而消息中間件可用於系統間解耦。
2、噹噹以前在使用什麼做業系統?
噹噹以前使用的做業系統比較散亂,各自爲戰,大體分爲如下4種:
Quartz:Java事實上的定時任務標準。但Quartz關注點在於定時任務而非數據,並沒有一套根據數據處理而定製化的流程。雖然Quartz能夠基於數據庫實現做業的高可用,但缺乏分佈式並行執行做業的功能。
TBSchedule:阿里早期開源的分佈式任務調度系統。代碼略陳舊,使用timer而非線程池執行任務調度。衆所周知,timer在處理異常情況時是有缺陷的。並且TBSchedule做業類型較爲單一,只能是獲取/處理數據一種模式。還有就是文檔缺失比較嚴重。
Crontab:Linux系統級的定時任務執行器。缺少分佈式和集中管理功能。
Perl:遺留系統使用,目前已不符合公司的Java化戰略。
3、elastic-job的來歷
elastic-job本來是噹噹Java應用框架ddframe的一部分,本名dd-job。
ddframe包括編碼規範,開發框架,技術規範,監控以及分佈式組件。ddframe規劃分爲4個演進階段,目前處於第2階段。三、4階段涉及的技術組件不表明當當沒有使用,只是ddframe還未統一規劃。
ddframe由各類模塊組成,均已dd-開頭,如dd-container,dd-soa,dd-rdb,dd-job等。噹噹但願將ddframe的各個模塊與公司環境解耦並開源以反饋社區。以前開源的Dubbo擴展版本DubboX便是dd-soa的核心模塊。而本次介紹的elastic-job則是dd-job的開源部分,其中監控(但開源了監控方法)和ddframe核心接入等部分並未開源。
4、elastic-job包含的功能
分佈式:最重要的功能,若是任務不能在分佈式的環境下執行,那麼直接使用Quartz就能夠了。
任務分片:是elastic-job中最重要也是最難理解的概念。任務的分佈式執行,須要將一個任務拆分爲n個獨立的任務項,而後由分佈式的服務器分別執行某一個或幾個分片項。
彈性擴容縮容:將任務拆分爲n個任務項後,各個服務器分別執行各自分配到的任務項。一旦有新的服務器加入集羣,或現有服務器下線,elastic-job將在保留本次任務執行不變的狀況下,下次任務開始前觸發任務重分片。舉例說明:有3臺服務器,分爲10個片。則分片項分配以下:{server1: [0,1,2], server2: [3,4,5], server3: [6,7,8,9]}。若是一臺服務器崩潰,則分片項分配以下:{server1: [0,1,2,3,4], server2: [5,6,7,8,9]}。若是新增一臺服務器,則分片項分配以下:{server1: [0,1], server2: [2,3] , server3: [4,5,6] , server4: [7,8,9]}。
穩定性:在服務器無波動的狀況下,並不會從新分片;即便服務器有波動,下次分片的結果也會根據服務器IP和做業名稱哈希值算出穩定的分片順序,儘可能不作大的變更。
高性能:elastic-job會將做業運行狀態的必要信息更新到註冊中心,但爲了考慮性能問題,能夠犧牲一些功能,而換取性能的提高。
冪等性:elastic-job可犧牲部分性能用以保證同一分片項不會同時在兩個服務器上運行。
失效轉移:彈性擴容縮容在下次做業運行前重分片,但本次做業執行的過程當中,下線的服務器所分配的做業將不會從新被分配。失效轉移功能能夠在本次做業運行中用空閒服務器抓取孤兒做業分片執行。一樣失效轉移功能也會犧牲部分性能。
狀態監控:監控做業的運行狀態,能夠監控數據處理功能和失敗次數,做業運行時間等。是冪等性,失效轉移必須的功能。
多做業模式:做業可分爲簡單和數據流處理兩種模式,數據流又分爲高吞吐處理模式和順序性處理模式,其中高吞吐處理模式能夠開啓足夠多的線程快速的處理數據,而順序性處理模式將每一個分片項分配到一個獨立線程,用於保證同一分片的順序性,這點相似於kafka的分區順序性。
其餘一些功能,如錯過任務重執行,單機並行處理,容錯處理,Spring命名空間支持,運維平臺等。
5、elastic-job的部署和使用
將使用elastic-job框架的jar/war鏈接同一個基於Zookeeper的註冊中心便可。
做業框架執行數據並不限於數據庫,且做業框架自己是不對數據進行關聯的。做業能夠用於處理數據,文件,API等任何操做。
使用elastic-job所須要關注的僅僅是將業務處理邏輯和框架所分配的分片項匹配並處理,如:若是分片項是1,則獲取id以1結尾的數據處理。因此若是是處理數據的話,最佳實踐是將做業分片項規則和數據中間層規則對應。
經過上面的部署圖能夠看出來,做業分片只是個邏輯概念,分片和實際數據其實框架是不作任何匹配關係的。而根據分片項和實際業務如何關聯,是成功使用elastic-job的關鍵所在。爲了避免讓代碼寫起來很無聊,看起來像if(shardingItem == 1) {do xxx} else if (shardingItem == 2) {do xxx},elastic-job提供了自定義參數,可將分片項序號和實際業務作映射。好比設置爲1=北京,2=上海。那麼代碼中能夠經過北京或是上海的枚舉,從業務中的北京倉庫或上海倉庫取數據。elastic-job更多的仍是關注做業調度和分佈式分配,處理數據仍是交由數據中間層更好些。
誠如剛纔所說,最佳實踐是將做業分片項規則和數據中間層規則對應,省去做業分片時,再次適配數據中間層的分片邏輯。
6、對開源產品的開發理念
爲了讓感興趣的人放心使用,我想分享一下咱們對開源產品的開發理念。
用心寫代碼。代碼是項目的惟一核心和產出,任何一行的代碼都須要用心思考優雅性,可讀性,合理性。優雅性看似簡單的幾個字,其實實現的難度很是大。每一個人心中都有本身對代碼的理解,而elastic-job也好,ddframe也好,都不是出自一人之手。對代碼優雅性的權衡,是比較難把控的。後面幾項,能夠理解爲對第一項的補充,或具體的實現思路。
代碼整潔乾淨到極致。簡單點說就是重度代碼潔癖患者。只有代碼漂亮整潔,其餘開源愛好者才願意閱讀代碼,進而找出項目中的bug和貢獻高質量代碼。
極簡代碼, 高度複用,無重複代碼和配置。Java生態圈的特色是高質量的開源產品極多。咱們儘可能考慮複用輪子,好比項目中大量用到lombok簡化代碼;但也不會無原則的使用開源產品,咱們傾向於把開源產品分爲積木類和大廈類。項目中通常只考慮使用積木類搭建屬於咱們本身的大廈,而不會直接用其餘已成型的大廈。java系的公司有兩種不一樣的聲音,擁抱開源,或徹底不使用開源。咱們的見解是既然選擇使用java,就應該遵循java的理念,去擁抱java這些年累積的成熟東西。java相比其餘新興語言,在語法上可能沒什麼優點,但在廣度上仍是少有其餘生態圈可比擬。
單一需求可不考慮擴展性;兩個相似需求時再提煉。爲了避免盲目追求所謂的極致,咱們用這條規則,儘可能提高交付的速度。
模塊抽象劃分合理。這點也很難用標準衡量。以elastic-job舉例:elastic-job核心代碼分爲4塊,core,spring,console和example;分別用於放置核心,spring支持,控制檯和代碼示例。在項目級別上作拆分。而core中將包分爲api,exception,plugin和internal。用於放置對外發布的接口、異常,向最終用戶提供的可擴展插件以及封裝好的內部實現。內部實現的任何改動,都不會影響對外接口的變更,用戶自定義的插件,也不會影響內部代碼的穩定性。
如無特殊理由, 測試需全覆蓋。elastic-job核心模塊的測試覆蓋率是95%以上。雖然單元測試覆蓋率在分佈式的複雜環境中並沒有太大說服力,但至少證實項目中不多出現低級邏輯錯誤。
對質量的定義。代碼可讀性 > 代碼可測性 > 模塊解耦設計 > 功能正確性 > 性能 > 功能可擴展性。只有代碼可讀,可測試,可100%掌控,項目纔可持續發展。功能有缺陷能夠修復,性能不夠能夠優化,而代碼不清晰則項目會漸漸變爲黑盒。因此對於框架類產品,咱們認爲質量 > 時間 > 成本。
文檔清晰。
7、將來展望
監控體系有待提升,目前只能經過註冊中心作簡單的存活和數據積壓監控。將來須要作的監控部分有:
1. 增長可監控維度,如做業運行時間等。
2. 基於JMX的內部狀態監控。
3. 基於歷史的全量數據監控,將全部監控數據經過flume等形式發到外部監控中心,提供實時分析功能。
增長任務工做流,如任務依賴,初始化任務,清理任務等。
失效轉移功能的實時性提高。
更多做業類型支持,如文件,MQ等類型做業的支持。
更多分片策略支持。
項目的開源地址:https://github.com/dangdangdotcom/elastic-job 但願你們多關注,共同貢獻代碼。
Q&A
Q1:請問失效轉移中如何判斷失效?對任務自己實現有什麼限制?
失效轉移目前經過Zookeeper監聽分片項臨時節點判斷。elastic-job會通過註冊中心會話過時時間才能感知任務掛掉。失效轉移有兩種形式:一、任務掛掉,elastic-job會找空閒的做業服務器(多是未分配任務的,也多是完成執行本次任務執行的)執行。二、若是當時沒有空閒服務器,則將在某服務器完成分配的任務時抓取未分配的分片項。
Q2:Zookeeper的做用是保存任務信息嗎,若是Zookeeper掛了會影響任務執行嗎?
Zookeeper目前的znode分四類,config,servers,execution,leader。config用於保存分佈式做業的全局控制,如,分多少片,要不要執行misfire,cron表達式。servers用於註冊做業服務器狀態和分片信息。execution以分片的維度存儲做業運行時狀態。leader用於存儲主節點。elastic-job做業執行是無中心化的,但主節點起到協調的做用,如:重分片、清理上次運行時信息等。
Q3:在任務處理上能夠與spring batch集成嗎?
spring batch以前關注過,但目前elastic-job尚未集成。elastic-job的spring支持是自定義了job的命名空間,更簡化了基於spring的配置,而且可使用spring注入的bean。spring batch也是很好的做業框架,包括spring-quartz也很不錯,但分佈式功能並不成熟。因此在這之上改動難度比較大,並且elastic-job更但願作一個不依賴於spring,而是能融入spring的綠色產品。
Q4:針對簡單和數據流,可以說說具體分片是怎麼處理的嗎?
簡單的做業就是未通過任何業務邏輯的封裝,只是提供了一個execute方法,定時觸發,可是增長了分佈式分片功能。能夠簡單理解爲quartz的分佈式版本。quartz雖然能夠支持基於數據庫的分佈式高可用,但不能分片。也就是說,兩臺服務器,只能一主一備,不能同時負載均衡的運行。數據流類型做業參照了阿里以前開源的TBSchedule,將數據處理分爲fetchData和processData。先將數據從數據庫,文件系統,或其餘數據源取出來,而後processData集中處理,能夠逐條處理,能夠批量處理(這塊將來將加上)。processData是多線程執行的,數據流類型做業可再細分爲兩種,一種是高吞吐,一種是順序性。高吞吐能夠開啓任意多的線程並行執行數據處理,而順序執行會根據每一個分片項一個線程,保證分片項之中的數據有序,這點參照了kafka的實現。數據流類型做業有isStreaming這個參數,用於控制是否流式不停歇的處理數據,相似永動機,只要有數據,則一直處理。但這種做業不適合每次fetchData都對數據庫形成壓力很大的場景。
Q5:請問如何實現一個任務僅僅只在一個節點執行一次?
目前的冪等性,是在execution的znode中增長了對分片項狀態的註冊,若是狀態是運行中,即便有別的服務器要運行這個分片項,elastic-job也會拒絕運行,而是等待這個狀態變爲非運行的狀態。每一個做業分片項啓動時會更新狀態。服務器沒有波動的狀況下,是不存在一個分片被分到兩個服務器的狀況。但一旦服務器波動,在分片的瞬間有可能出現這種狀況。關於分片,實際上是比較複雜的實現。目前分片是發現服務器波動,或修改分片總數,將記錄一個狀態,而非直接分片。分片將在下次做業觸發時執行,只有主節點能夠分片,分片中從節點都將阻塞。無調度中心式分佈式做業最大的一個問題是,沒法保證主節點做業必定先於其餘從節點觸發。因此頗有可能從節點先觸發執行,而使用舊分片;而後主節點才從新分片,將形成此次做業分片可能不一致。這就須要execution節點來保證冪等性。下次執行時,只要無服務器波動,以前錯誤的分片天然會修正。
Q6:若是Zookeeper掛了,是否所有的任務都掛了不能運行包括已經運行過一次的,若是又恢復了,任務能正常運行嗎,仍是業務應用服務也要從新啓動?
其實Zookeeper是不太容易掛的。畢竟Zookeeper是分佈式高可用,通常不會是單臺。目前elastic-job作到的容錯是,連不上Zookeeper的做業服務器將馬上中止執行做業,防止主節點已從新分片,而腦裂的服務器還在執行。也就是說,Zookeeper掛掉,全部做業都將中止。而做業服務器一旦與Zookeeper恢復鏈接,做業也將恢復運行。因此Zookeeper掛掉不會影響數據,而Zookeeper恢復,做業會繼續跑,不用重啓。
Q7:能夠具體到業務層面嗎?好比有個任務,是同樣發送100w的用戶郵件,這時候應該怎麼分片?針對分佈式數據庫的分頁在我們這裏又是怎麼處理的?
100W用戶的郵件,我的認爲能夠按照用戶id取模,好比分紅100個分片,將整個userid % 100,而後每一個分片發送userid結尾是取摸結果的郵件。詳細來講:分片1發送以01結尾的userid的郵件,…,分片99發送以99結尾的userid的郵件。分佈式數據庫的分頁,理論上來講,不是做業框架處理的範疇,應由數據中間層處理。順便說下,ddframe的數據中間層部分,sharding-JDBC將於明年初開源。經過修改JDBC驅動實現分庫分表。非MyCat或cobar這種中間件方式;也非基於hibernate或mybatis這種ORM方式。sharding-JDBC相對輕量級,也更加容易適配各類數據庫和ORM
Q8:ddframe是由不少組件組成?支持多語言嗎?
ddframe是不少組件的總稱。分爲核心模塊,分佈式組件模塊和監控對接模塊等。核心模塊能夠理解爲spring-boot這種可快速啓動,快速搭建項目的東西。
分佈式組件包括SOA調用的Dubbox,基於分佈式做業的elastic-job,還有剛纔提到的sharding-JDBC,以及近期暫無開源計劃的緩存、MQ、NoSQL等模塊。
監控模塊估計之後也不會開源,和公司自己的業務場景綁定太緊,不是不想開源,是沒法開源。主要分爲日誌中心,流量分析和系統關係調用圖。監控部分目前也還在作,不是很強大。
多語言方面,SOA模塊支持,Dubbox的REST擴展就是爲了支持其餘語言的調用。剩下的暫時不行。好比sharing-JDBC,主要是基於java的JDBC,若是多語言,中間層是個更好的方法。
ddframe的模塊名字都是dd-*,dd-soa,dd-rdb,dd-job,dd-log之類。elastic-job,sharding-JDBC等,是爲開源而從ddframe抽離並從新起的名字。
|