幾年前曾經(jīng)寫過一點(diǎn)點(diǎn)對(duì)于緩存框架設(shè)計(jì)的體會(huì),,這大半年和工作流系統(tǒng)打交道頗為豐富,因此想總結(jié)一點(diǎn)關(guān)于工作流系統(tǒng)的設(shè)計(jì),。 首先,,明確工作流(workflow)系統(tǒng)的定義。維基百科上有極其簡單的介紹,。我記得以前在文章里面說過,,作為大公司里面的小team,為了做一些有趣的東西,,從而更好的招人,,通常有幾個(gè)眾人皆知的突破口:比如一個(gè)更符合業(yè)務(wù)需求的storage,再比如一個(gè)自定義的工作流系統(tǒng),。在Amazon內(nèi)部,,我接觸過好多個(gè)workflow,而且大多以Amazon SWF為原型(當(dāng)時(shí)學(xué)習(xí)的時(shí)候還寫了一點(diǎn)體會(huì),,link 1和link 2),,于是宏觀上看,60%的東西是一樣的,,大同小異,;但是也有很多重要的元素大不相同,而它們被放到一起比較也是常事,。幾次折騰之后,,我也慢慢在思考,如何去設(shè)計(jì)一個(gè)工作流系統(tǒng),,其中都有哪些重要的需要考慮到的方面,。 Scalability 基本上隨便設(shè)計(jì)什么基礎(chǔ)設(shè)施,擴(kuò)展性都是重要的考慮內(nèi)容,。作為workflow來講,,基本上工作節(jié)點(diǎn)的水平擴(kuò)展是考量擴(kuò)展性的最重要標(biāo)志。既然工作節(jié)點(diǎn)可以水平擴(kuò)展,,那么這就意味著任務(wù)(task)必須是以pull的方式由工作節(jié)點(diǎn)主動(dòng)去獲取,,而不是由pull的方式從調(diào)度節(jié)點(diǎn)來分配(曾經(jīng)非常簡單地比較過pull和push,但其實(shí)二者差異遠(yuǎn)不止文中內(nèi)容之淺顯),。任務(wù)的分配上,,需要考慮這樣的事情:如果有多個(gè)工作節(jié)點(diǎn)嘗試來pull任務(wù),該分配給誰,?具體來說,,比如這樣的例子:如果每一個(gè)task節(jié)點(diǎn)允許同時(shí)執(zhí)行5個(gè)任務(wù),而現(xiàn)在可同時(shí)執(zhí)行的總?cè)蝿?wù)數(shù)只有5個(gè),,總共的task節(jié)點(diǎn)也有5個(gè),,最理想的狀態(tài)應(yīng)當(dāng)是這5個(gè)被均勻分配到這5個(gè)節(jié)點(diǎn)去,但是采用簡單的pull機(jī)制并不能保證這一點(diǎn),,有可能這5個(gè)任務(wù)全部跑到一臺(tái)機(jī)器上去了,,因?yàn)檫@并不超過一個(gè)節(jié)點(diǎn)可同時(shí)執(zhí)行任務(wù)數(shù)量的上限。 另一方面,,通常來講,,所有任務(wù)都應(yīng)當(dāng)是idempotent的,即可以重復(fù)提交執(zhí)行,,執(zhí)行若干次和執(zhí)行一次的結(jié)果是一樣的,。工作節(jié)點(diǎn)的任務(wù)執(zhí)行可以在任意一步發(fā)生錯(cuò)誤,隨著節(jié)點(diǎn)數(shù)量的增加,,這樣的錯(cuò)誤更多地成為一種常態(tài),,而不是“異常”,。工作節(jié)點(diǎn)的健康狀態(tài)需要由某種方式來維系和通知,,最典型和廉價(jià)有效的方式就是“心跳”,,我曾經(jīng)寫過一篇文章詳細(xì)介紹一種心跳系統(tǒng)的設(shè)計(jì),感興趣的話,,歡迎移步閱讀,。 功能性解耦
同步與異步任務(wù) 事實(shí)上,當(dāng)考慮到了獨(dú)立的資源管理功能,,異步和同步任務(wù)的劃分就變得自然而然,。
分布式鎖 在某些情況下,,分布式鎖變成一個(gè)必選項(xiàng),。比如前面提到的資源管理。有許多資源是要求操作是獨(dú)占的,,換言之,,不支持兩個(gè)操作并發(fā)調(diào)用,期間可能出現(xiàn)不可以預(yù)料的問題,;另一方面,,一個(gè)節(jié)點(diǎn)在對(duì)資源進(jìn)行操作時(shí),它需要和別的節(jié)點(diǎn)進(jìn)行協(xié)作,,從而兩個(gè)工作節(jié)點(diǎn)的操作是有序和正確的,,不至于發(fā)生沖突。 舉個(gè)例子來說,,工作節(jié)點(diǎn)A要查詢當(dāng)前EMR的狀態(tài),,如果已經(jīng)空閑10分鐘,就要執(zhí)行操作結(jié)束掉這個(gè)EMR資源,;而工作節(jié)點(diǎn)B則查詢?cè)揈MR的狀態(tài),,如果沒有被結(jié)束掉,就要往上面提交新的計(jì)算任務(wù)。這時(shí)候,,如果沒有分布式鎖的協(xié)作,,問題就來了,可能B節(jié)點(diǎn)先查詢發(fā)現(xiàn)EMR狀態(tài)還活著,,就這這一瞬間,,A節(jié)點(diǎn)結(jié)束了它,可是B不知道,,接著提交了一個(gè)計(jì)算任務(wù)到這個(gè)已經(jīng)結(jié)束了的(死了的)EMR資源上,,于是這個(gè)提交的計(jì)算任務(wù)必然執(zhí)行失敗了。 有很多分布式鎖的實(shí)現(xiàn)方式,,簡單的有強(qiáng)一致性的存儲(chǔ)系統(tǒng),,當(dāng)然也有更高效的實(shí)現(xiàn),比如一些專門的分布式鎖系統(tǒng),。 功能的可擴(kuò)展性 之前講到了性能架構(gòu)上的可擴(kuò)展性,在功能層面亦然,。
可用性和可靠性 大多數(shù)workflow,都采用了去中心節(jié)點(diǎn)的設(shè)計(jì),,保證不存在任何單點(diǎn)故障問題,。所有的子系統(tǒng)都是。也保證在業(yè)務(wù)壓力增加的情況下,標(biāo)志著可用性的latency在預(yù)期范圍之內(nèi),。其它的內(nèi)容不展開,,介紹這方面的文章到處都是。 生命周期管理 這里既指workflow一次執(zhí)行的生命周期管理,,也指單個(gè)task的生命周期管理,。 談?wù)撨@些必然涉及到這樣幾個(gè)問題:
任務(wù)DAG的設(shè)計(jì)和表達(dá) 這是workflow執(zhí)行的流程圖,,也是所有task之間依賴關(guān)系的表述。我見過多種表達(dá)方式的,,有XML的,,也有JSON的,還有一些不知名的自己定義的格式的,。有些workflow的定義可以以一個(gè)圖形化工具來協(xié)助完成這個(gè)流程圖,。這個(gè)DSL的設(shè)計(jì),一定程度上決定了workflow的使用是不是能夠易于理解,。另外提一句,,這里提到的這個(gè)可選的圖形化工具,畢竟只是一個(gè)輔助,,它不是workflow的核心(你可以說這個(gè)DSL是核心的一部分,但這個(gè)幫助完成的工具顯然不是)——我見過一個(gè)團(tuán)隊(duì),,workflow整體設(shè)計(jì)得不怎么樣,,跑起來一堆問題,但是這個(gè)工具花了大量的時(shí)間精力去修繕,,本末倒置,。 另外,workflow的狀態(tài)和執(zhí)行情況,,還有對(duì)其的歸檔和管理,,也需要一個(gè)整合工具來協(xié)助。這方面幾乎所有workflow都具備,,通常都是網(wǎng)頁工具,,以及命令行工具。 輸入輸出的管理 這也是一個(gè)nice-to-have的東西,,對(duì)于每一個(gè)task,,都存在input和output,它們可以完全交給用戶自己來實(shí)現(xiàn),,比如用戶把它們存儲(chǔ)到文件里面,,或者寫到數(shù)據(jù)庫里面,,而workflow根本不管,每個(gè)task內(nèi)部自己去讀取相應(yīng)的用戶文件即可,。但是更好的方法是,,對(duì)于一些常用和簡單的input、output,,是可以隨著execution一起持久化到workflow和task的狀態(tài)里面去的,。這樣也便于workflow的definition里面,放置一些根據(jù)前一步task執(zhí)行結(jié)果來決策后續(xù)執(zhí)行的表達(dá)式,。 另外,,還有一個(gè)稍微冷門的use case,就是input和output的管理,。通常workflow是重復(fù)執(zhí)行的,,而每次執(zhí)行的input和output的數(shù)據(jù)規(guī)模往往是很多人關(guān)心的內(nèi)容。關(guān)于這部分,,我還沒有見到任何一個(gè)workflow提供這樣的功能,。許多用戶自己寫工具和腳本來獲取這樣的信息。 獨(dú)立的metrics和日志系統(tǒng) 對(duì)于metrics,,核心的內(nèi)容也無非節(jié)點(diǎn)的健康狀況,、CPU、內(nèi)存,,task執(zhí)行時(shí)間分布,,失敗率等等幾項(xiàng)。有些情況下用戶還希望自行擴(kuò)展,。 關(guān)于日志,,則主要指的是歸檔和合并。歸檔,,指的是歷史日志不丟失,,或者在一定時(shí)間內(nèi)不丟失,過期日志可以被覆寫,,從而不引起磁盤容量的問題,;而合并,指的是日志能被以更統(tǒng)一的視角進(jìn)行查詢和瀏覽,,出了問題不至于到每臺(tái)機(jī)器上去手動(dòng)查找,。缺少這個(gè)功能,有時(shí)候會(huì)很麻煩,。在工作中我遇到過一個(gè)資源被異常終止的問題,,為了找到那個(gè)終止資源的節(jié)點(diǎn),我查閱了幾十個(gè)節(jié)點(diǎn)的日志,,痛苦不堪,。 版本控制和平滑部署 把這兩個(gè)放一起是因?yàn)?,代碼升級(jí)是不可避免且經(jīng)常要發(fā)生的。為了保證平滑部署,,顯然通常情況下,,節(jié)點(diǎn)上的代碼不能同時(shí)更新,需要一部分一部分進(jìn)行,。比如,,先終止50%的節(jié)點(diǎn),部署代碼后,,激活并確保成功,,再進(jìn)行剩下那50%的節(jié)點(diǎn)。但是在這期間存在新老代碼并存的問題,,這通常會(huì)帶來很多奇形怪狀的問題,。對(duì)于這種問題,我見過這樣兩個(gè)解決方式:
無論選用哪一種,,這種方式實(shí)現(xiàn)起來相對(duì)簡單,但是也有不少問題,,比如這種情況下,外部資源怎么處理,?例如在外部EMR資源上執(zhí)行Spark任務(wù),,但是已經(jīng)有老代碼被放到EMR上去執(zhí)行了,這時(shí)候工作節(jié)點(diǎn)更新,,這些EMR上正在執(zhí)行的任務(wù)怎樣處理,?是作廢還是保留,如果保留的話這些執(zhí)行可還是依仗著老代碼的,,其結(jié)果的后續(xù)處理是否會(huì)和剛部署的新代碼產(chǎn)生沖突,。再比如對(duì)于workflow有定義上的改變(比如DAG的改變),對(duì)于現(xiàn)有的execution,,應(yīng)當(dāng)怎樣處理,,是更新還是保持原樣(通常都是保持原樣,,因?yàn)楦聨淼膹?fù)雜問題非常多)。 |
|