如何让快递更快?菜鸟自研定时任务调度引擎首次公开
阿里妹導(dǎo)讀:網(wǎng)上購(gòu)物的普及化帶動(dòng)了物流行業(yè)的迅猛發(fā)展,同時(shí)也帶來(lái)了極大的壓力和嚴(yán)峻的考驗(yàn),特別是在電商大促的時(shí)節(jié)。如何有效提高整個(gè)物流鏈路的時(shí)效體驗(yàn),給消費(fèi)者更好的體驗(yàn),這是菜鳥(niǎo)物流一直奮斗的目標(biāo)。
今天,我們來(lái)深入了解菜鳥(niǎo)的輕量級(jí)定時(shí)任務(wù)調(diào)度引擎設(shè)計(jì)系統(tǒng),學(xué)習(xí)如何在億級(jí)別包裹中快速定位運(yùn)輸超時(shí)的包裹。
在中國(guó)物流快速發(fā)展的今天,日均包裹量已經(jīng)突破1億,如何確保1億包裹在合理的時(shí)間之內(nèi)送達(dá)收件人,并且能夠在收件人反饋之前,及時(shí)處理那些沒(méi)有在合理時(shí)間內(nèi)運(yùn)輸?shù)陌?#xff0c;從而提高物流整個(gè)鏈路的時(shí)效體驗(yàn),已經(jīng)成為亟待解決的關(guān)鍵問(wèn)題。
要想解決問(wèn)題,首先要發(fā)現(xiàn)問(wèn)題!有效、及時(shí)地發(fā)現(xiàn)問(wèn)題離不開(kāi)對(duì)這1億包裹運(yùn)輸過(guò)程中每個(gè)環(huán)節(jié)實(shí)時(shí)監(jiān)控,而要實(shí)現(xiàn)這個(gè)場(chǎng)景,就需要一個(gè)能夠支撐起如此量級(jí)的實(shí)時(shí)定時(shí)調(diào)度系統(tǒng)。這就是本文的主題:輕量級(jí)定時(shí)任務(wù)調(diào)度引擎的設(shè)計(jì)。
傳統(tǒng)方案
針對(duì)上文提到的問(wèn)題場(chǎng)景,一般的做法是將定時(shí)任務(wù)寫(xiě)入數(shù)據(jù)庫(kù),通過(guò)一個(gè)線程定時(shí)查詢出將要到期的任務(wù),再執(zhí)行任務(wù)相關(guān)邏輯。該方案的優(yōu)點(diǎn)是實(shí)現(xiàn)簡(jiǎn)單,尤其適合單機(jī)或者業(yè)務(wù)量比較小的場(chǎng)景來(lái)。但是缺點(diǎn)也很明顯:在分布式且業(yè)務(wù)量較大的場(chǎng)景中會(huì)引入很多復(fù)雜性。首先,需要設(shè)計(jì)一套合理的分庫(kù)分表邏輯,以及集群任務(wù)負(fù)載邏輯。其次,即使做到這些,也會(huì)由于某些場(chǎng)景定時(shí)任務(wù)時(shí)間集中在某個(gè)時(shí)間點(diǎn),導(dǎo)致集群?jiǎn)喂?jié)點(diǎn)壓力過(guò)大。再次,需要合理的預(yù)估容量,否則后續(xù)線性存儲(chǔ)擴(kuò)容將會(huì)非常復(fù)雜。
我們發(fā)現(xiàn),上述方案的主要問(wèn)題其實(shí)是定時(shí)任務(wù)時(shí)間和任務(wù)存儲(chǔ)的耦合。如果能夠?qū)r(shí)間和存儲(chǔ)解耦,任務(wù)的存儲(chǔ)就等于是無(wú)狀態(tài)的,這樣對(duì)存儲(chǔ)的可選擇性范圍會(huì)大很多,對(duì)存儲(chǔ)的約束也大大降低!
下文將會(huì)介紹如何通過(guò)時(shí)間輪思想將定時(shí)任務(wù)的時(shí)間和任務(wù)本身進(jìn)行解耦,從而在設(shè)計(jì)和性能上得到很大的提升。
時(shí)間輪基本介紹
時(shí)間輪方案將現(xiàn)實(shí)生活中的時(shí)鐘概念引入到軟件設(shè)計(jì)中,主要思路是定義一個(gè)時(shí)鐘周期(比如時(shí)鐘的12小時(shí))和步長(zhǎng)(比如時(shí)鐘的一秒走一次),當(dāng)指針每走一步的時(shí)候,會(huì)獲取當(dāng)前時(shí)鐘刻度上掛載的任務(wù)并執(zhí)行,整體結(jié)構(gòu)如圖1。
從上圖可以看到,對(duì)于時(shí)間的計(jì)算是交給一個(gè)類(lèi)似時(shí)鐘的組件來(lái)做,而任務(wù)是通過(guò)一個(gè)指針或者引用去關(guān)聯(lián)某個(gè)刻度上到期的定時(shí)任務(wù),這樣就能夠?qū)⒍〞r(shí)任務(wù)的存儲(chǔ)和時(shí)間進(jìn)行解耦,時(shí)鐘組件難度不大,以何種方式存儲(chǔ)這些任務(wù)數(shù)據(jù),是時(shí)間輪方案的關(guān)鍵。
時(shí)間輪定時(shí)任務(wù)的存儲(chǔ)
我們發(fā)現(xiàn),阿里巴巴MQ(RocketMQ商業(yè)化版本,后文統(tǒng)稱為阿里巴巴MQ)對(duì)其定時(shí)消息的改造[1]很有借鑒意義,老方案基于定時(shí)輪詢的方式check消息是否到期,這種方案對(duì)于離散的時(shí)間比較受限,所以也就導(dǎo)致老版本只能支持幾種延遲級(jí)別的定時(shí)消息。為了解決這個(gè)問(wèn)題,阿里巴巴MQ團(tuán)隊(duì)將原方案改造為基于時(shí)間輪+鏈表的方案,從而既能支持離散的定時(shí)消息,也能夠解決傳統(tǒng)時(shí)間輪每個(gè)刻度需要管理各自任務(wù)列表的復(fù)雜性。
圖2簡(jiǎn)明的描述了方案的關(guān)鍵點(diǎn),其設(shè)計(jì)思路就是將任務(wù)列表寫(xiě)入到磁盤(pán),并且在磁盤(pán)中采用鏈表的方式將任務(wù)列表串起來(lái),要達(dá)到這種串起來(lái)的效果,需要每個(gè)任務(wù)中都有一個(gè)指向下一個(gè)任務(wù)的磁盤(pán)offset,只需要拿到鏈表的頭便可以獲取整個(gè)任務(wù)鏈表,于是在該方案中,時(shí)間輪的時(shí)間刻度不需要存儲(chǔ)所有的任務(wù)列表,只需要存儲(chǔ)鏈表的頭即可,從而將內(nèi)存的壓力釋放出來(lái)。
該方案對(duì)于中間件這樣定位的系統(tǒng)來(lái)說(shuō)是可以接受的,但對(duì)于一個(gè)定位在普通應(yīng)用的系統(tǒng)來(lái)說(shuō),對(duì)部署的要求就顯得過(guò)高了,因?yàn)槟阈枰粔K固定的硬盤(pán)。在當(dāng)前容器化以及容量動(dòng)態(tài)管理的趨勢(shì)下,一個(gè)普通應(yīng)用需要依賴一塊固定的磁盤(pán),對(duì)系統(tǒng)的運(yùn)維和部署都會(huì)帶來(lái)額外的復(fù)雜度。于是在此基礎(chǔ)上形成本文重點(diǎn)介紹的輕量級(jí)定時(shí)任務(wù)調(diào)度引擎。
輕量級(jí)定時(shí)任務(wù)調(diào)度引擎
輕量級(jí)定時(shí)任務(wù)調(diào)度引擎借鑒了時(shí)間輪方案的核心思想,同時(shí)去除了系統(tǒng)對(duì)磁盤(pán)的依賴。既然不能將數(shù)據(jù)直接存儲(chǔ)在磁盤(pán),那只能依托專門(mén)的存儲(chǔ)服務(wù)(比如Hbase,Redis等)來(lái)進(jìn)行存儲(chǔ)。于是就將下層的存儲(chǔ)替換成了一種存儲(chǔ)服務(wù)能夠滿足的結(jié)構(gòu):
將任務(wù)設(shè)計(jì)成一種結(jié)構(gòu)化的表,并且將上面的offset替換成了一個(gè)任務(wù)ID(圖2是文件的offset),并且通過(guò)ID將整個(gè)任務(wù)鏈表串起來(lái),時(shí)間輪上只關(guān)聯(lián)鏈表頭的ID。這里對(duì)MQ方案改造的點(diǎn)只是將磁盤(pán)的offset替換成一個(gè)ID,從而解耦對(duì)磁盤(pán)的依賴。
方案到現(xiàn)在感覺(jué)可以行得通,但是又引出了另外兩個(gè)問(wèn)題:
問(wèn)題1:單一鏈表無(wú)法并行提取,從而影響提取效率,對(duì)于某個(gè)時(shí)刻有大量定時(shí)任務(wù)的時(shí)候,定時(shí)任務(wù)處理的延遲會(huì)比較嚴(yán)重;
問(wèn)題2:既然任務(wù)不是存儲(chǔ)在本機(jī)磁盤(pán)了,表明整個(gè)集群的定時(shí)任務(wù)是集中存儲(chǔ),而集群中各個(gè)節(jié)點(diǎn)都擁有自己的時(shí)間輪,那么集群里面每個(gè)節(jié)點(diǎn)重啟之后如何恢復(fù)?集群擴(kuò)容&縮容如何自動(dòng)管理?
任務(wù)鏈表分區(qū)——加速單一鏈表提取
鏈表的好處是在內(nèi)存中不用存儲(chǔ)整個(gè)任務(wù)列表,而只需要存儲(chǔ)一個(gè)簡(jiǎn)單的ID,這樣減少了內(nèi)存的消耗,但是卻帶來(lái)了另一個(gè)問(wèn)題。眾所周知,鏈表的特性是對(duì)寫(xiě)友好,但讀的效率卻并不高,如果某個(gè)時(shí)刻需要掛載很長(zhǎng)的任務(wù)鏈表,那鏈表的方式是完全不能利用并發(fā)來(lái)提高讀的效率。
如何能夠提高某個(gè)時(shí)間的任務(wù)隊(duì)列提取的效率呢?這里利用分區(qū)的原理,將某個(gè)時(shí)刻的單一鏈表通過(guò)分區(qū)的方式拆分成多個(gè)鏈表,當(dāng)將某個(gè)時(shí)間點(diǎn)的任務(wù)提取的時(shí)候,可以根據(jù)鏈表集合大小來(lái)并行處理,從而可以加速整個(gè)任務(wù)提取的速度。所以方案調(diào)整成圖4:
集群管理——集群節(jié)點(diǎn)的自我識(shí)別
解決了定時(shí)任務(wù)的存儲(chǔ)問(wèn)題和單一鏈表提取任務(wù)的效率問(wèn)題,好像整個(gè)方案都已經(jīng)ready了,但是還有另一個(gè)重要的問(wèn)題前面沒(méi)有考慮進(jìn)去,就是集群部署后節(jié)點(diǎn)重啟后如何進(jìn)行恢復(fù)?比如需要知道重啟之前的時(shí)間輪刻度,需要知道重啟之間時(shí)間輪刻度上的定時(shí)任務(wù)鏈表數(shù)據(jù)等,后面我統(tǒng)一將這種數(shù)據(jù)稱之為時(shí)間輪元數(shù)據(jù)。如果任務(wù)寫(xiě)入磁盤(pán),某個(gè)機(jī)器的重啟,可以從本地磁盤(pán)加載當(dāng)前節(jié)點(diǎn)的時(shí)間輪元數(shù)據(jù)來(lái)進(jìn)行恢復(fù);而如果不是通過(guò)磁盤(pán)來(lái)實(shí)現(xiàn),那就帶來(lái)問(wèn)題2-1:一臺(tái)機(jī)器在重啟之后怎么獲取它重啟之前的時(shí)間輪元數(shù)據(jù)呢?
通過(guò)上面的解決定時(shí)任務(wù)存儲(chǔ)問(wèn)題,對(duì)于元數(shù)據(jù)的存儲(chǔ)也可以借助專門(mén)的存儲(chǔ)服務(wù),但是由于集群的各個(gè)節(jié)點(diǎn)是無(wú)狀態(tài)的,所以各個(gè)節(jié)點(diǎn)啟動(dòng)的時(shí)候,并不知道如何從存儲(chǔ)服務(wù)中讀取屬于自己的元數(shù)據(jù)(也就是查詢的條件),這便引出了問(wèn)題2-2:集群節(jié)點(diǎn)怎么獲取屬于它的元數(shù)據(jù)呢?
可能第一想到的就是將元數(shù)據(jù)和節(jié)點(diǎn)ip或者mac地址關(guān)聯(lián)上,但是在現(xiàn)在動(dòng)態(tài)容量調(diào)度的情況下,一個(gè)機(jī)器擁有一個(gè)固定的ip也是很奢侈的,因?yàn)槟悴荒鼙WC你的應(yīng)用會(huì)運(yùn)行在哪臺(tái)機(jī)器上。既然物理環(huán)境不能依賴,就只能依賴邏輯環(huán)境來(lái)對(duì)每個(gè)節(jié)點(diǎn)的時(shí)間輪進(jìn)行區(qū)分了,于是就通過(guò)對(duì)集群每個(gè)節(jié)點(diǎn)分配一個(gè)在集群中唯一的邏輯ID,每臺(tái)機(jī)器通過(guò)自己拿到的ID去獲取時(shí)間輪數(shù)據(jù)。于是又引出了問(wèn)題2-3:這個(gè)ID由誰(shuí)來(lái)分配? 這就是Master節(jié)點(diǎn)。
在整個(gè)集群中需要選舉出一個(gè)節(jié)點(diǎn)為Master,所有的其他節(jié)點(diǎn)會(huì)將自己注冊(cè)到Master節(jié)點(diǎn)中,然后再由Master節(jié)點(diǎn)分配一個(gè)ID給各個(gè)節(jié)點(diǎn),從而通過(guò)這個(gè)ID到存儲(chǔ)服務(wù)中獲取之前時(shí)間輪的元數(shù)據(jù)信息,最后初始化時(shí)間輪。圖5和圖6是該過(guò)程的描述圖:
圖5展示了節(jié)點(diǎn)注冊(cè)和獲取ID的過(guò)程,注意, Master節(jié)點(diǎn)自身也注冊(cè)并且分配了ID,這是因?yàn)閷?duì)一個(gè)普通應(yīng)用來(lái)說(shuō),并沒(méi)有Master和非Master之分,Master也是會(huì)參與整個(gè)業(yè)務(wù)的計(jì)算的,只不過(guò)它除了參與業(yè)務(wù)計(jì)算之外,額外還要進(jìn)行集群的管理。
集群自動(dòng)化管理——自動(dòng)感知擴(kuò)容&縮容
上文引出了Master節(jié)點(diǎn),并且所有節(jié)點(diǎn)都會(huì)將自己注冊(cè)給Master(包括Master自己),于是基于Master就可以構(gòu)建出一套集群管理的機(jī)制。對(duì)于普通應(yīng)用來(lái)說(shuō),集群的擴(kuò)容縮容是很正常的操作,在動(dòng)態(tài)調(diào)度的場(chǎng)景下,擴(kuò)縮容操作甚至連應(yīng)用owner都不感知,那么集群能夠感知自己被擴(kuò)容了和縮容了么?答案是肯定的,因?yàn)榇嬖谝粋€(gè)Master節(jié)點(diǎn),而Master可以感知到集群的其他節(jié)點(diǎn)的存活狀態(tài),Master發(fā)現(xiàn)集群的容量變化,從而做出集群的負(fù)載調(diào)整。
定時(shí)任務(wù)提取集群化——集群壓力軟負(fù)載
上文通過(guò)將時(shí)間輪上的某個(gè)時(shí)間關(guān)聯(lián)單一鏈表拆分成多個(gè)鏈表來(lái)提高提取任務(wù)的并發(fā)度,同時(shí)也已構(gòu)建出了一套集群管理方案。如果這個(gè)并發(fā)度只是在單節(jié)點(diǎn),只讓該節(jié)點(diǎn)自己提取,將會(huì)因?yàn)槟硞€(gè)時(shí)刻的當(dāng)前節(jié)點(diǎn)到期的任務(wù)數(shù)量很大,從而導(dǎo)致集群中某些節(jié)點(diǎn)的壓力會(huì)很大。為了能夠更加合理的利用集群計(jì)算能力,那么可以基于上面的集群管理能力,對(duì)方案再進(jìn)一步優(yōu)化:將提取的到期任務(wù)鏈表集合通過(guò)軟負(fù)載的方式分發(fā)給集群其他節(jié)點(diǎn),同時(shí)由于任務(wù)的數(shù)據(jù)是集中存儲(chǔ)的,只要其他節(jié)點(diǎn)能夠拿到任務(wù)鏈表頭的ID,便可以提取得到該鏈表的所有任務(wù),從而集群的壓力也將被平攤,圖7是該過(guò)程的交互過(guò)程:
總結(jié)
上面對(duì)整個(gè)方案的演進(jìn),以及各個(gè)階段遇到的問(wèn)題進(jìn)行了詳細(xì)的介紹,可能還有一些點(diǎn)沒(méi)有太深入,大家可以回復(fù)留言中詳細(xì)討論。下面結(jié)合上面的內(nèi)容,做了一個(gè)簡(jiǎn)單的流程梳理,以方便大家對(duì)全文的理解。
引用:[1] 消息的處理方法、裝置和電子設(shè)備:中國(guó),201710071742.7[P].2017-10-07.
總結(jié)
以上是生活随笔為你收集整理的如何让快递更快?菜鸟自研定时任务调度引擎首次公开的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 使用开源项目的正确姿势,都是血和泪的总结
- 下一篇: 在阿里,我们如何管理代码分支?