作者 | 不瞋
當(dāng)我們構(gòu)建一個應(yīng)用,總是希望它是響應(yīng)迅速,成本低廉的。而在實際中,我們的系統(tǒng)卻面臨各種各樣的挑戰(zhàn),例如不可預(yù)測的流量高峰,依賴的下游服務(wù)變得緩慢,少量請求卻消耗大量 CPU/內(nèi)存資源。這些因素常常導(dǎo)致整個系統(tǒng)被拖慢,甚至不能響應(yīng)請求。為了讓應(yīng)用服務(wù)總是響應(yīng)迅速,很多時候不得不預(yù)留更多的計算資源,但大部分時候,這些計算資源都是閑置的。一種更好的做法是將耗時緩慢,或者需要消耗大量資源的處理邏輯從請求處理主邏輯中剝離出來,交給更具資源彈性的系統(tǒng)異步執(zhí)行,不但讓請求能夠被迅速處理返回給用戶,也節(jié)省了成本。
一般來說,長耗時,消耗大量資源,或者容易出錯的邏輯,非常適合從請求主流程中剝離出來,異步執(zhí)行。例如新用戶注冊,注冊成功后,系統(tǒng)通常會發(fā)送一封歡迎郵件。發(fā)送歡迎郵件的動作就可以從注冊流程中剝離出來。另一個例子是用戶上傳圖片,圖片上傳后通常需要生成不同大小的縮略圖。但圖片處理的過程不必包含在圖片上傳處理流程中,用戶上傳圖片成功后就可以結(jié)束流程,生成縮略圖等處理邏輯可以作為異步任務(wù)執(zhí)行。這樣應(yīng)用服務(wù)器避免被圖片處理等計算密集型任務(wù)壓垮,用戶也能更快的得到響應(yīng)。常見的異步執(zhí)行任務(wù)包括:
- 發(fā)送電子郵件/即時消息
- 檢查垃圾郵件
- 文檔處理(轉(zhuǎn)換格式,導(dǎo)出,……)
- 音視頻,圖片處理(生成縮略圖,加水印,鑒黃,轉(zhuǎn)碼,……)
- 調(diào)用外部的三方服務(wù)
- 重建搜索索引
- 導(dǎo)入/導(dǎo)出大量數(shù)據(jù)
- 網(wǎng)頁爬蟲
- 數(shù)據(jù)清洗
- ……
Slack,Pinterest,F(xiàn)acebook 等公司都廣泛的使用異步任務(wù),實現(xiàn)更好的服務(wù)可用性,更低的成本。根據(jù)Dropbox 統(tǒng)計,他們的業(yè)務(wù)場景中一共有超過100種不同類型的異步任務(wù)。一個功能完備的異步任務(wù)處理系統(tǒng)能帶來顯著的收益:
- 更快的系統(tǒng)響應(yīng)時間。將長耗時的,重資源消耗的邏輯從請求處理流程中剝離,在別的地方異步執(zhí)行,能有效的降低請求響應(yīng)延時,帶來更好的用戶體驗。
- 更好的處理大量突發(fā)性請求。在電商等很多場景下,常常有大量突發(fā)性請求對系統(tǒng)造成沖擊。同樣的,如果將重資源消耗邏輯從請求處理流程中剝離,在別的地方異步執(zhí)行,那么相同資源容量的系統(tǒng)能響應(yīng)更大峰值的請求流量。
- 更低的成本。異步任務(wù)的執(zhí)行時長通常在數(shù)百毫秒到數(shù)小時之間,根據(jù)不同的任務(wù)類型,合理的選擇任務(wù)執(zhí)行時間和更彈性的使用資源,就能實現(xiàn)更低的成本。
- 更完善的重試策略和錯誤處理能力。任務(wù)保證被可靠的執(zhí)行(at-least-once),并且按照配置的重試策略進(jìn)行重試,從而實現(xiàn)更好的容錯能力。例如調(diào)用第三方的下游服務(wù),如果能變成異步任務(wù),設(shè)置合理的重試策略,即使下游服務(wù)偶爾不穩(wěn)定,也不影響任務(wù)的成功率。
- 更快的完成任務(wù)處理。多個任務(wù)的執(zhí)行是高度并行化的。通過伸縮異步任務(wù)處理系統(tǒng)的資源,海量的任務(wù)能夠在合理的成本內(nèi)更快的完成。
- 更好的任務(wù)優(yōu)先級管理和流控。任務(wù)根據(jù)類型,通常按照不同的優(yōu)先級處理。異步任務(wù)管理系統(tǒng)能幫助用戶更好的隔離不同優(yōu)先級的任務(wù),既讓高優(yōu)先級任務(wù)能更快的被處理,又讓低優(yōu)先級任務(wù)不至于被餓死。
- 更多樣化的任務(wù)觸發(fā)方式。任務(wù)的觸發(fā)方式是多種多樣的,例如通過 API 直接提交任務(wù),或是通過事件觸發(fā),或是定時執(zhí)行等等。
- 更好的可觀測性。異步任務(wù)處理系統(tǒng)通常會提供任務(wù)日志,指標(biāo),狀態(tài)查詢,鏈路追蹤等能力,讓異步任務(wù)更好的被觀測、更容易診斷問題。
- 更高的研發(fā)效率。用戶專注于任務(wù)處理邏輯的實現(xiàn),任務(wù)調(diào)度,資源擴(kuò)縮容,高可用,流控,任務(wù)優(yōu)先級等功能都由任務(wù)處理系統(tǒng)完成,研發(fā)效率大幅提高。
一、任務(wù)處理系統(tǒng)架構(gòu)
任務(wù)處理系統(tǒng)通常包括三部分:任務(wù) API 和可觀測,任務(wù)分發(fā)和任務(wù)執(zhí)行。我們首先介紹這三個子系統(tǒng)的功能,然后再討論整個系統(tǒng)面臨的技術(shù)挑戰(zhàn)和解決方案。
1.任務(wù) API/Dashboard
該子系統(tǒng)提供一組任務(wù)相關(guān)的 API,包括任務(wù)創(chuàng)建、查詢、刪除等等。用戶通過 GUI,命令行工具,后者直接調(diào)用 API 的方式使用系統(tǒng)功能。以 Dashboard 等方式呈現(xiàn)的可觀測能力也非常重要。好的任務(wù)處理系統(tǒng)應(yīng)當(dāng)包括以下可觀測能力:
- 日志:能夠收集和展示任務(wù)日志,用戶能夠快速查詢指定任務(wù)的日志。
- 指標(biāo):系統(tǒng)需要提供排隊任務(wù)數(shù)等關(guān)鍵指標(biāo),幫助用戶快速判斷任務(wù)的執(zhí)行情況。
- 鏈路追蹤:任務(wù)從提交到執(zhí)行過程中,各個環(huán)節(jié)的耗時。比如在隊列中排隊的時間,實際執(zhí)行的時間等等。下圖展示了 Netflix Cosmos 平臺的 tracing 能力。
2.任務(wù)分發(fā)
任務(wù)分發(fā)負(fù)責(zé)任務(wù)的調(diào)度分發(fā)。一個能應(yīng)用于生產(chǎn)環(huán)境的任務(wù)分發(fā)系統(tǒng)通常要具備以下功能:
- 任務(wù)的可靠分發(fā):任務(wù)一旦提交成功后,無論遇到任何情況,系統(tǒng)都應(yīng)當(dāng)保證該任務(wù)被調(diào)度執(zhí)行。
- 任務(wù)的定時/延時分發(fā):很多類型的任務(wù),希望在指定的時間執(zhí)行,例如定時發(fā)送郵件/消息,或者定時生成數(shù)據(jù)報表。另一種情況是任務(wù)可以延時較長一段時間執(zhí)行也沒問題,例如下班前提交的數(shù)據(jù)分析任務(wù)在第二天上班前完成即可,這類任務(wù)可以放到凌晨資源消耗低峰的時候執(zhí)行,通過錯峰執(zhí)行降低成本。
- 任務(wù)去重:我們總是不希望任務(wù)被重復(fù)執(zhí)行。除了造成資源浪費,任務(wù)重復(fù)執(zhí)行可能造成更嚴(yán)重的后果。比如一個計量任務(wù)因為重復(fù)執(zhí)行算錯了賬單。要做到任務(wù)只執(zhí)行一次(exactly-once),需要在任務(wù)提交,分發(fā),執(zhí)行全鏈路上的每個環(huán)節(jié)都做到,包括用戶在實現(xiàn)任務(wù)處理代碼時也要在執(zhí)行成功,執(zhí)行失敗等各種情況下,做到 exactly-once。如何實現(xiàn)完整的 exactly-once 比較復(fù)雜,超出了本文的討論范圍。很多時候,系統(tǒng)提供一個簡化的語義也很有價值,即任務(wù)只成功執(zhí)行一次。任務(wù)去重需要用戶在提交任務(wù)時指定任務(wù) ID,系統(tǒng)通過 ID來判斷該任務(wù)是否已經(jīng)被提交和成功執(zhí)行過。
- 任務(wù)錯誤重試:合理的任務(wù)重試策略對高效、可靠的完成任務(wù)非常關(guān)鍵。任務(wù)的重試要考慮幾個因素:1)要匹配下游任務(wù)執(zhí)行系統(tǒng)的處理能力。比如收到下游任務(wù)執(zhí)行系統(tǒng)的流控錯誤,或者感知到任務(wù)執(zhí)行成為瓶頸,需要指數(shù)退避重試。不能因為重試反而加大了下游系統(tǒng)的壓力,壓垮下游;2)重試的策略要簡單清晰,易于用戶理解和配置。首先要對錯誤進(jìn)行分類,區(qū)分不可重試錯誤,可重試錯誤,流控錯誤。不可重試錯誤是指確定性失敗的錯誤,重試沒有意義,比如參數(shù)錯誤,權(quán)限問題等等。可重試錯誤是指導(dǎo)致任務(wù)失敗的因素具有偶然性,通過重試任務(wù)最終會成功,比如網(wǎng)絡(luò)超時等系統(tǒng)內(nèi)部錯誤。流控錯誤是一種比較特殊的可重試錯誤,通常意味著下游已經(jīng)滿負(fù)荷,重試需要采用退避模式,控制發(fā)送給下游的請求量。
- 任務(wù)的負(fù)載均衡:任務(wù)的執(zhí)行時間變化很大,短的幾百毫秒,長的數(shù)十小時。簡單的 round-robin 。 方式分發(fā)任務(wù),會導(dǎo)致執(zhí)行節(jié)點負(fù)載不均。實踐中常見的模式是將任務(wù)放置到隊列中,執(zhí)行節(jié)點根據(jù)自身任務(wù)執(zhí)行情況主動拉取任務(wù)。使用隊列保存任務(wù),讓根據(jù)節(jié)點的負(fù)載把任務(wù)分發(fā)到合適的節(jié)點上,讓節(jié)點的負(fù)載均衡。任務(wù)負(fù)載均衡通常需要分發(fā)系統(tǒng)和執(zhí)行子系統(tǒng)配合實現(xiàn)。
- 任務(wù)按優(yōu)先級分發(fā):任務(wù)處理系統(tǒng)通常對接很多的業(yè)務(wù)場景,他們的任務(wù)類型和優(yōu)先級各不相同。位于業(yè)務(wù)核心體驗相關(guān)的任務(wù)執(zhí)行優(yōu)先級要高于邊緣任務(wù)。即使同樣是消息通知,淘寶上買家收到一個商品評論通知的重要性肯定低于新冠疫情中的核酸檢測通知。但另一方面,系統(tǒng)也要保持一定程度的公平,不要讓高優(yōu)先級任務(wù)總是搶占資源,而餓死低優(yōu)先級任務(wù)。
- 任務(wù)流控:任務(wù)流控典型的使用場景是削峰填谷,比如用戶一次性提交數(shù)十萬的任務(wù),期望在幾個小時內(nèi)慢慢處理。因此系統(tǒng)需要限制任務(wù)的分發(fā)速率,匹配下游任務(wù)執(zhí)行的能力。任務(wù)流控也是保證系統(tǒng)可靠性的重要手段,某類任務(wù)提交量突然爆發(fā)式增長,系統(tǒng)要通過流控限制其對系統(tǒng)的沖擊,減小對其他任務(wù)的影響。
- 批量暫停和刪除任務(wù):在實際生產(chǎn)環(huán)境,提供任務(wù)批量暫停和刪除非常重要。用戶總是會出現(xiàn)各種狀況,比如任務(wù)的執(zhí)行出現(xiàn)了某些問題,最好能暫停后續(xù)任務(wù)的執(zhí)行,人工檢查沒有問題后,再恢復(fù)執(zhí)行;或者臨時暫停低優(yōu)先級任務(wù),釋放計算資源用于執(zhí)行更高優(yōu)先級的任務(wù)。另一種情況是提交的任務(wù)有問題,執(zhí)行沒有意義。因此系統(tǒng)要能讓用戶非常方便的刪除正在執(zhí)行和排隊中的任務(wù)。任務(wù)的暫停和刪除需要分發(fā)和執(zhí)行子系統(tǒng)配合實現(xiàn)。
任務(wù)分發(fā)的架構(gòu)可分為拉模式和推模式。拉模式通過任務(wù)隊列分發(fā)任務(wù)。執(zhí)行任務(wù)的實例主動從任務(wù)隊列中拉取任務(wù),處理完畢后再拉取新任務(wù)。相對于拉模式,推模式增加了一個分配器的角色。分配器從任務(wù)隊列中讀取任務(wù),進(jìn)行調(diào)度,推送給合適的任務(wù)執(zhí)行實例。
拉模式的架構(gòu)清晰,基于 Redis 等流行軟件可以快速搭建任務(wù)分發(fā)系統(tǒng),在簡單任務(wù)場景下表現(xiàn)良好。但如果要支持任務(wù)去重,任務(wù)優(yōu)先級,批量暫停或刪除,彈性的資源擴(kuò)縮容等復(fù)雜業(yè)務(wù)場景需要的功能,拉模式的實現(xiàn)復(fù)雜度會迅速增加。實踐中,拉模式面臨以下一些主要的挑戰(zhàn):
- 資源自動伸縮和負(fù)載均衡復(fù)雜。任務(wù)執(zhí)行實例和任務(wù)隊列建立連接,拉取任務(wù)。當(dāng)任務(wù)執(zhí)行實例規(guī)模較大時,對任務(wù)隊列的連接資源會造成很大的壓力。因此需要一層映射和分配,任務(wù)實例只和對應(yīng)的任務(wù)隊列連接。下圖是 Slack 公司的異步任務(wù)處理系統(tǒng)架構(gòu)。Worker 節(jié)點只和部分 Redis 實例相連。這解決了 worker 節(jié)點大規(guī)模擴(kuò)展的能力,但是增加了調(diào)度和負(fù)載均衡的復(fù)雜度。
- 從支持任務(wù)優(yōu)先級,隔離和流控等需求的角度考慮,最好能使用不同的隊列。但隊列過多,又增加了管理和連接資源消耗,如何平衡很有挑戰(zhàn)。
- 任務(wù)去重,任務(wù)批量暫停或者刪除等功能依賴消息隊列功能,但很少有消息類產(chǎn)品能滿足所有需求,常常需要自行開發(fā)。例如從可擴(kuò)展性的角度,通常做不到每一類任務(wù)都對應(yīng)單獨的任務(wù)隊列。當(dāng)任務(wù)隊列中包含多種類型的任務(wù)時,要批量暫停或者刪除其中某一類的任務(wù),是比較復(fù)雜的。
- 任務(wù)隊列的任務(wù)類型和任務(wù)處理邏輯耦合。如果任務(wù)隊列中包含多種類型的任務(wù),要求任務(wù)處理邏輯也要實現(xiàn)相應(yīng)的處理邏輯,對用戶不友好。在實踐中,A 用戶的任務(wù)處理邏輯不會預(yù)期接收到別的用戶任務(wù),因此任務(wù)隊列通常由用戶自行管理,進(jìn)一步增加了用戶的負(fù)擔(dān)。
推模式的核心思想是將任務(wù)隊列和任務(wù)執(zhí)行實例解耦,平臺側(cè)和用戶的邊界更加清晰。用戶只需要專注于任務(wù)處理邏輯的實現(xiàn),而任務(wù)隊列,任務(wù)執(zhí)行節(jié)點資源池的管理都由平臺負(fù)責(zé)。推模式的解耦也讓任務(wù)執(zhí)行節(jié)點的擴(kuò)容不再受任務(wù)隊列的連接資源等方面的限制,能夠?qū)崿F(xiàn)更高的彈性。但推模式也引入了很多的復(fù)雜度,任務(wù)的優(yōu)先級管理,負(fù)載均衡,調(diào)度分發(fā),流控等都由分配器負(fù)責(zé),分配器需要和上下游系統(tǒng)聯(lián)動。
總的來說,當(dāng)任務(wù)場景變得復(fù)雜后,無論拉還是推模式,系統(tǒng)復(fù)雜度都不低。但推模式讓平臺和用戶的邊界更清晰,簡化了用戶的使用復(fù)雜度,因此有較強(qiáng)技術(shù)實力的團(tuán)隊,實現(xiàn)平臺級的任務(wù)處理系統(tǒng)時,通常會選擇推模式。
3.任務(wù)執(zhí)行
任務(wù)執(zhí)行子系統(tǒng)管理一批執(zhí)行任務(wù)的 worker 節(jié)點,以彈性、可靠的方式執(zhí)行任務(wù)。典型的任務(wù)執(zhí)行子系統(tǒng)需具備如下功能:
- 任務(wù)的可靠執(zhí)行。任務(wù)一旦提交成功,無論任何情況,系統(tǒng)應(yīng)當(dāng)保證任務(wù)被執(zhí)行。例如執(zhí)行任務(wù)的節(jié)點宕機(jī),任務(wù)應(yīng)當(dāng)調(diào)度到其他的節(jié)點執(zhí)行。任務(wù)的可靠執(zhí)行通常是任務(wù)分發(fā)和任務(wù)執(zhí)行子系統(tǒng)共同配合實現(xiàn)。
- 共享資源池。不同類型的任務(wù)處理資源共享統(tǒng)一的資源池,這樣才能削峰填谷,提高資源利用效率,降低成本。例如把計算密集,io密集等不同類型的任務(wù)調(diào)度到同一臺 worker 節(jié)點上,就能更充分的利用節(jié)點上的CPU,內(nèi)存,網(wǎng)絡(luò)等多個維度的資源。共享資源池對容量管理,任務(wù)資源配額管理,任務(wù)優(yōu)先級管理,資源隔離提出了更高的要求。
- 資源彈性伸縮。系統(tǒng)能根據(jù)負(fù)載的執(zhí)行情況伸縮執(zhí)行節(jié)點資源,降低成本。伸縮的時機(jī)和數(shù)量非常關(guān)鍵。常見的根據(jù)任務(wù)執(zhí)行節(jié)點的 CPU,內(nèi)存等資源水位情況伸縮,時間較長,不能滿足實時性要求高的場景。很多系統(tǒng)也使用排隊任務(wù)數(shù)等指標(biāo)進(jìn)行伸縮。另一個值得關(guān)注的點是執(zhí)行節(jié)點的擴(kuò)容需要匹配上下游系統(tǒng)的能力。例如當(dāng)任務(wù)分發(fā)子系統(tǒng)使用隊列來分發(fā)任務(wù)時,worker 節(jié)點的擴(kuò)容要匹配隊列的連接能力。
- 任務(wù)資源隔離。在 worker 節(jié)點上執(zhí)行多個不同的任務(wù)時,資源是相互隔離的。通常使用容器的隔離機(jī)制實現(xiàn)。
- 任務(wù)資源配額。用戶的使用場景多樣,常常包含多種任務(wù)類型和優(yōu)先級。系統(tǒng)要支持用戶為不同優(yōu)先級的任務(wù)或者處理函數(shù)設(shè)置資源配額,為高優(yōu)先級任務(wù)預(yù)留資源,或者限制低優(yōu)先級任務(wù)能使用的資源。
- 簡化任務(wù)處理邏輯的編碼。好的任務(wù)處理系統(tǒng),能夠讓用戶專注于實現(xiàn)單個任務(wù)處理邏輯,系統(tǒng)自動并行、彈性、可靠的執(zhí)行任務(wù)。
- 平滑升級。底層系統(tǒng)的升級不要中斷長時任務(wù)的執(zhí)行。
- 執(zhí)行結(jié)果通知。實時通知任務(wù)執(zhí)行狀態(tài)和結(jié)果。對于執(zhí)行失敗的任務(wù),任務(wù)的輸入被保存到死信隊列中,方便用戶隨時手動重試。
任務(wù)執(zhí)行子系統(tǒng)通常使用 K8s 管理的容器集群作為資源池。K8s 能夠管理節(jié)點,將執(zhí)行任務(wù)的容器實例調(diào)度到合適的節(jié)點上。K8s 也內(nèi)置了作業(yè)(Jobs)和定時作業(yè)(Cron Jobs)的支持,簡化了用戶使用 Job 負(fù)載的難度。K8s 有助于實現(xiàn)共享資源池管理,任務(wù)資源隔離等功能。但 K8s 主要能力還是在POD/實例管理上,很多時候需要開發(fā)更多的功能來滿足異步任務(wù)場景的需求。例如:
- K8s 的 HPA 一般難以滿足任務(wù)場景下的自動伸縮。Keda 等開源項目提供了按排隊任務(wù)數(shù)等指標(biāo)伸縮的模式。AWS 也結(jié)合 CloudWatch 提供了類似的解決方案。
- K8s 一般需要配合隊列來實現(xiàn)異步任務(wù),隊列資源的管理需要用戶自行負(fù)責(zé)。
- K8s 原生的作業(yè)調(diào)度和啟動時間比較慢,而且提交作業(yè)的 tps 一般小于 200,所以不適合高 tps,短延時的任務(wù)。
注意:K8s 中的作業(yè)(Job)和本文討論的任務(wù)(task)有一些區(qū)別。K8s 的 Job 通常包含處理一個或者多個任務(wù)。本文的任務(wù)是一個原子的概念,單個任務(wù)只在一個實例上執(zhí)行。執(zhí)行時長從幾十毫秒到數(shù)小時不等。
二、大規(guī)模多租戶異步任務(wù)處理系統(tǒng)實踐
接下來,筆者以阿里云函數(shù)計算的異步任務(wù)處理系統(tǒng)為例,探討大規(guī)模多租戶異步任務(wù)處理系統(tǒng)的一些技術(shù)挑戰(zhàn)和應(yīng)對策略。在阿里云函數(shù)計算平臺上,用戶只需要創(chuàng)建任務(wù)處理函數(shù),然后提交任務(wù)即可。整個異步任務(wù)的處理是彈性、高可用的,具備完整的可觀測能力。在實踐中,我們采用了多種策略來實現(xiàn)多租戶環(huán)境的隔離、伸縮、負(fù)載均衡和流控,平滑處理海量用戶的高度動態(tài)變化的負(fù)載。
1.動態(tài)隊列資源伸縮和流量路由
如前所述,異步任務(wù)系統(tǒng)通常需要隊列實現(xiàn)任務(wù)的分發(fā)。當(dāng)任務(wù)處理中臺對應(yīng)很多業(yè)務(wù)方,那么為每一個應(yīng)用/函數(shù),甚至每一個用戶都分配單獨的隊列資源就不再可行。因為絕大多數(shù)應(yīng)用都是長尾的,調(diào)用低頻,會造成大量隊列,連接資源的浪費。并且輪詢大量隊列也降低了系統(tǒng)的可擴(kuò)展性。
但如果所有用戶都共享同一批隊列資源,則會面臨多租戶場景中經(jīng)典的“noisy neighbor”問題,A 應(yīng)用突發(fā)式的負(fù)載擠占隊列的處理能力,影響其他應(yīng)用。
實踐中,函數(shù)計算構(gòu)建了動態(tài)隊列資源池。一開始資源池內(nèi)會預(yù)置一些隊列資源,并將應(yīng)用哈希映射到部分隊列上。如果某些應(yīng)用的流量快速增長時,系統(tǒng)會采取多種策略:
- 如果應(yīng)用的流量持續(xù)保持高位,導(dǎo)致隊列積壓,系統(tǒng)將為他們自動創(chuàng)建單獨的隊列,并將流量分流到新的隊列上。
- 將一些延時敏感,或者優(yōu)先級高的應(yīng)用流量遷移到其他隊列上,避免被高流量應(yīng)用產(chǎn)生的隊列積壓影響。
- 允許用戶設(shè)置任務(wù)的過期時間,對于有實時性要求的任務(wù),在發(fā)生積壓時快速丟棄過期任務(wù),確保新任務(wù)能更快的處理。
2.負(fù)載隨機(jī)分片
在一個多租環(huán)境中,防止“破壞者”對系統(tǒng)造成災(zāi)難性的破壞是系統(tǒng)設(shè)計的最大挑戰(zhàn)。破壞者可能是被 DDoS 攻擊的用戶,或者在某些 corner case 下正好觸發(fā)了系統(tǒng) bug 的負(fù)載。下圖展示了一種非常流行的架構(gòu),所有用戶的流量以 round-robin 的方式均勻的發(fā)送給多臺服務(wù)器。當(dāng)所有用戶的流量符合預(yù)期時,系統(tǒng)工作得很好,每臺服務(wù)器的負(fù)載均勻,而且部分服務(wù)器宕機(jī)也不影響整體服務(wù)的可用性。但當(dāng)出現(xiàn)“破壞者”后,系統(tǒng)的可用性將出現(xiàn)很大的風(fēng)險。
如下圖所示,假設(shè)紅色的用戶被 DDoS 攻擊或者他的某些請求可能觸發(fā)服務(wù)器宕機(jī)的 bug,那么他的負(fù)載將可能打垮所有的服務(wù)器,造成整個服務(wù)不可用。
上述問題的本質(zhì)是任何用戶的流量都會被路由到所有服務(wù)器上,這種沒有任何負(fù)載隔離能力的模式在面臨“破壞者”時相當(dāng)脆弱。對于任何一個用戶,如果他的負(fù)載只會被路由到部分服務(wù)器上,能不能解決這個問題?如下圖所示,任何用戶的流量最多路由到2臺服務(wù)器上,即使造成兩臺服務(wù)器宕機(jī),綠色用戶的請求仍然不受影響。這種將用戶的負(fù)載映射到部分而非全部服務(wù)器的負(fù)載分片模式,能夠很好的實現(xiàn)負(fù)載隔離,降低服務(wù)不可用的風(fēng)險。代價則是系統(tǒng)需要準(zhǔn)備更多的冗余資源。
接下來,讓我們調(diào)整下用戶負(fù)載的映射方式。如下圖所示,每個用戶的負(fù)載均勻的映射到兩臺服務(wù)器上。不但負(fù)載更加均衡,更棒的是,即使兩臺服務(wù)器宕機(jī),除紅色之外的用戶負(fù)載都不受影響。如果我們把分區(qū)的大小設(shè)為2,那么從3臺服務(wù)器中選擇2臺服務(wù)器的組合方式有 C_{3}^{2}=3 種,即3種可能的分區(qū)方式。通過隨機(jī)算法,我們將負(fù)載均勻的映射到分區(qū)上,那么任意一個分區(qū)不可服務(wù),則最多影響1/3的負(fù)載。假設(shè)我們有100臺服務(wù)器,分區(qū)的大小仍然是2,那么分區(qū)的方式有C_{100}{2}=4950種,單個分區(qū)不可用只會影響1/4950=0.2%的負(fù)載。隨著服務(wù)器的增多,隨機(jī)分區(qū)的效果越明顯。對負(fù)載隨機(jī)分區(qū)是一個非常簡潔卻強(qiáng)大的模式,在保障多租系統(tǒng)的可用性中起到了關(guān)鍵的作用。
3.自適應(yīng)下游處理能力的任務(wù)分發(fā)
函數(shù)計算的任務(wù)分發(fā)采用了推模式,這樣用戶只需要專注于任務(wù)處理邏輯的開發(fā),平臺和用戶的邊界也很清晰。在推模式中,有一個任務(wù)分配器的角色,負(fù)責(zé)從任務(wù)隊列拉取任務(wù)并分配到下游的任務(wù)處理實例上。任務(wù)分配器要能根據(jù)下游處理能力,自適應(yīng)的調(diào)整任務(wù)分發(fā)速度。當(dāng)用戶的隊列產(chǎn)生積壓時,我們希望不斷增加 dispatch worker pool 的任務(wù)分發(fā)能力;當(dāng)達(dá)到下游處理能力的上限后,worker pool 要能感知到該狀態(tài),保持相對穩(wěn)定的分發(fā)速度;當(dāng)任務(wù)處理完畢后,work pool 要縮容,將分發(fā)能力釋放給其他任務(wù)處理函數(shù)。
在實踐中,我們借鑒了 tcp 擁塞控制算法的思想,對 worker pool 的擴(kuò)縮容采取 AIMD 算法(Additive Increase Multiplicative Decrease,和性增長,乘性降低)。當(dāng)用戶短時間內(nèi)提交大量任務(wù)時,分配器不會立即向下游發(fā)送大量任務(wù),而是按照“和性增長”策略,線性增加分發(fā)速度,避免對下游服務(wù)的沖擊。當(dāng)收到下游服務(wù)的流控錯誤后,采用“乘性減少”的策略來,按照一定的比例來縮容 worker pool。其中流控錯誤需要滿足錯誤率和錯誤數(shù)的閾值后才觸發(fā)縮容,避免 worker pool 的頻繁擴(kuò)縮容。
4.向上游的任務(wù)生產(chǎn)方發(fā)送背壓(back pressure)
如果任務(wù)的處理能力長期落后于任務(wù)的生產(chǎn)能力,隊列中積壓的任務(wù)會越來越多,雖然可以使用多個隊列并進(jìn)行流量路由來減小租戶之間的相互影響。但任務(wù)積壓超過一定閾值后,應(yīng)當(dāng)更積極的向上游的任務(wù)生產(chǎn)方反饋這種壓力,例如開始流控任務(wù)提交的請求。在多租共享資源的場景下,背壓的實施會更加有挑戰(zhàn)。例如A,B應(yīng)用共享任務(wù)分發(fā)系統(tǒng)的資源,如果A應(yīng)用的任務(wù)積壓,如何做到:
- 公平。盡可能流控A而不是B應(yīng)用。流控本質(zhì)是一個概率問題,為每一個對象計算流控概率,概率越準(zhǔn)確,流控越公平。
- 及時。背壓要傳遞到系統(tǒng)最外層,例如在任務(wù)提交時就對A應(yīng)用流控,這樣對系統(tǒng)的沖擊最小。
如何在多租場景中識別到需要流控的對象很有挑戰(zhàn),我們在實踐中借鑒了Sample and Hold算法,取得了較好的效果。感興趣的讀者可以參考相關(guān)論文。
三、異步任務(wù)處理系統(tǒng)的能力分層
根據(jù)前述對異步任務(wù)處理系統(tǒng)的架構(gòu)和功能的分析,我們將異步任務(wù)處理系統(tǒng)的能力分為以下三層:
- Level 1:一般需 1-5 人研發(fā)團(tuán)隊,系統(tǒng)是通過整合 K8s 和消息隊列等開源軟件/云服務(wù)的能力搭建的。系統(tǒng)的能力受限于依賴的開源軟件/云服務(wù),難以根據(jù)業(yè)務(wù)需求進(jìn)行定制。資源的使用偏靜態(tài),不具備資源伸縮,負(fù)載均衡的能力。能夠承載的業(yè)務(wù)規(guī)模有限,隨著業(yè)務(wù)規(guī)模和復(fù)雜度增長,系統(tǒng)開發(fā)和維護(hù)的代價會迅速增加。
- Level 2:一般需 5-10人研發(fā)團(tuán)隊,在開源軟件/云服務(wù)的基礎(chǔ)之上,具備一定的自主研發(fā)能力,滿足常見的業(yè)務(wù)需求。不具備完整的任務(wù)優(yōu)先級、隔離、流控的能力,通常是為不同的業(yè)務(wù)方配置不同的隊列和計算資源。資源的管理比較粗放,缺少實時資源伸縮和容量管理能力。系統(tǒng)缺乏可擴(kuò)展性,資源精細(xì)化管理能力,難以支撐大規(guī)模復(fù)雜業(yè)務(wù)場景。
- Level 3:一般需 10+ 人研發(fā)團(tuán)隊,能夠打造平臺級的系統(tǒng)。具備支撐大規(guī)模,復(fù)雜業(yè)務(wù)場景的能力。采用共享資源池,在任務(wù)調(diào)度,隔離流控,負(fù)載均衡,資源伸縮等方面能力完備。平臺和用戶界限清晰,業(yè)務(wù)方只需要專注于任務(wù)處理邏輯的開發(fā)。具備完整的可觀測能力。
Level 1 | Level 2 | Level 3 | |
任務(wù)的可靠分發(fā) | 支持 | 支持 | 支持 |
任務(wù)定時/延時發(fā)送 | 取決于選擇的消息隊列能力。一般支持定時任務(wù),但不支持延時任務(wù) | 支持 | 支持 |
任務(wù)去重 | 不支持 | 支持 | 支持 |
任務(wù)錯誤自動重試 | 有限支持。一般依賴于 K8s Jobs 內(nèi)置的重試策略。對于未使用 K8s Jobs 的任務(wù),則需用戶在任務(wù)處理邏輯中自行實現(xiàn) | 有限支持。一般依賴于 K8s Jobs 內(nèi)置的重試策略。對于未使用 K8s Jobs 的任務(wù),則需用戶在任務(wù)處理邏輯中自行實現(xiàn) | 支持。平臺和用戶界限清晰,根據(jù)用戶設(shè)定的策略重試 |
任務(wù)負(fù)載均衡 | 有限支持。在任務(wù)執(zhí)行實例規(guī)模小的情況下通過消息隊列實現(xiàn) | 有限支持。在任務(wù)執(zhí)行實例規(guī)模小的情況下通過消息隊列實現(xiàn) | 支持。系統(tǒng)具備大規(guī)模節(jié)點的負(fù)載均衡能力 |
任務(wù)優(yōu)先級 | 不支持 | 有限支持。允許用戶為高優(yōu)先級任務(wù)預(yù)留資源,或者限制低優(yōu)先級任務(wù)的資源使用 | 支持。高優(yōu)先級任務(wù)可搶占低優(yōu)先級任務(wù)資源,同時系統(tǒng)會兼顧公平,避免低優(yōu)先級任務(wù)被餓死 |
任務(wù)流控 | 不支持 | 不支持。一般是為不同任務(wù)類型或者業(yè)務(wù)方配置獨立的隊列和計算資源 | 在系統(tǒng)的每個環(huán)節(jié)具備流控能力,系統(tǒng)不會因為任務(wù)爆發(fā)式提交雪崩 |
任務(wù)批量暫停/刪除 | 不支持 | 有限支持。取決于是否為不同任務(wù)類型或者業(yè)務(wù)方配置獨立的隊列和計算資源 | 支持 |
共享資源池 | 有限支持。依賴 K8s 的調(diào)度能力。一般是為各個業(yè)務(wù)方搭建不同的集群 | 有限支持。依賴 K8s 的調(diào)度能力。一般是為各個業(yè)務(wù)方搭建不同的集群 | 支持。不同類型的任務(wù),不同業(yè)務(wù)場景共享同一個資源池 |
資源彈性伸縮 | 不支持。K8s 的 HPA 通常難以滿足任務(wù)場景下的伸縮要求 | 不支持。K8s 的 HPA 通常難以滿足任務(wù)場景下的伸縮要求 | 支持。根據(jù)排隊任務(wù)數(shù),節(jié)點資源利用率等多維度實時伸縮 |
任務(wù)資源隔離 | 支持。依賴容器的資源隔離能力 | 支持。依賴容器的資源隔離能力 | 支持。依賴容器的資源隔離能力 |
任務(wù)資源配額 | 不支持 | 支持 | 支持 |
簡化任務(wù)處理邏輯編碼 | 不支持。任務(wù)處理邏輯需要自行拉取任務(wù),執(zhí)行任務(wù) | 不支持。任務(wù)處理邏輯需要自行拉取任務(wù),執(zhí)行任務(wù) | 支持 |
系統(tǒng)平滑升級 | 不支持 | 不支持 | 支持 |
執(zhí)行結(jié)果通知 | 不支持 | 不支持 | 支持 |
可觀測性 | 依賴 K8s,消息隊列等開源軟件自身的可觀測能力。具備基本的任務(wù)狀態(tài)查詢 | 依賴 K8s,消息隊列等開源軟件自身的可觀測能力。具備基本的任務(wù)狀態(tài)查詢 | 具備從任務(wù)到系統(tǒng)各個層面的完整可觀測能力 |
四、結(jié)論
異步任務(wù)是構(gòu)建彈性、高可用,響應(yīng)迅速應(yīng)用的重要手段。本文對異步任務(wù)的適用場景和收益進(jìn)行了介紹,并討論了典型異步任務(wù)系統(tǒng)的架構(gòu)、功能和工程實踐。要實現(xiàn)一個能夠滿足多種業(yè)務(wù)場景需求,彈性可擴(kuò)展的異步任務(wù)處理平臺具有較高的復(fù)雜度。而阿里云函數(shù)計算 FC 為用戶提供了開箱即用的,接近于Level ?3能力的異步任務(wù)處理服務(wù)。用戶只需要創(chuàng)建任務(wù)處理函數(shù),通過控制臺,命令行工具,API/SDK,事件觸發(fā)等多種方式提交任務(wù),就可以彈性、可靠、可觀測完備的方式處理任務(wù)。函數(shù)計算異步任務(wù)覆蓋任務(wù)處理時長從毫秒到24小時的場景,被阿里云數(shù)據(jù)庫自制服務(wù) DAS,支付寶小程序壓測平臺,網(wǎng)易云音樂,新東方,分眾傳媒,米連等集團(tuán)內(nèi)外客戶廣泛應(yīng)用。
附錄
1.函數(shù)計算異步任務(wù)和 K8S Jobs 的能力對比。
對比項 | 函數(shù)計算異步任務(wù) | K8S Jobs |
適用場景 | 適合任務(wù)執(zhí)行時長數(shù)十毫秒的實時任務(wù)和任務(wù)執(zhí)行時長幾十小時的離線任務(wù) | 適合任務(wù)提交速度要求不高,任務(wù)負(fù)載比較固定,任務(wù)實時性要求不高的離線任務(wù) |
任務(wù)可觀測能力 | 支持。提供日志,任務(wù)排隊數(shù)等指標(biāo),任務(wù)鏈路耗時,任務(wù)狀態(tài)查詢等豐富可觀測能力 | 自行整合開源軟件實現(xiàn)。 |
任務(wù)實例自動擴(kuò)縮容 | 支持。根據(jù)任務(wù)排隊數(shù),實例資源使用率自動擴(kuò)縮容 | 不支持。一般通過任務(wù)隊列,自行實現(xiàn)自動擴(kuò)縮容和實例負(fù)載均衡,復(fù)雜度高 |
任務(wù)實例伸縮速度 | 毫秒級 | 分鐘級 |
任務(wù)實例資源利用率 | 用戶只需要選擇合適的實例規(guī)格,實例自動伸縮,按實際處理任務(wù)的時長計量,資源利用率高 | 需在作業(yè)(Job)提交時確定實例的規(guī)格和數(shù)目。實例難以自動伸縮和負(fù)載均衡,資源利用率低 |
任務(wù)提交速度 | 單個用戶支持每秒提交數(shù)萬任務(wù) | 整個集群每秒最多啟動數(shù)百作業(yè)(Jobs) |
任務(wù)定時/延時提交 | 支持 | 支持定時任務(wù),不支持延時任務(wù) |
任務(wù)去重 | 支持 | 不支持 |
暫停/恢復(fù)任務(wù)執(zhí)行 | 支持 | Alpha 狀態(tài)(K8S v1.21) |
終止指定任務(wù) | 支持 | 有限支持。通過終止任務(wù)實例間接實現(xiàn) |
任務(wù)流控 | 支持。可在用戶,任務(wù)處理函數(shù)等不同粒度進(jìn)行流控 | 不支持 |
任務(wù)結(jié)果自動回調(diào) | 支持 | 不支持 |
開發(fā)運維成本 | 只需要實現(xiàn)任務(wù)的處理邏輯 | 需維護(hù)K8S集群 |
2.網(wǎng)易云音樂 Serverless Jobs 實踐,音頻處理算法業(yè)務(wù)落地速度10x提升
3.其他異步任務(wù)案例
參考鏈接:
[1] slack engineering:https://slack.engineering/scaling-slacks-job-queue/
[2] Facebook:https://engineering.fb.com/2020/08/17/production-engineering/async/
[3] Dropbox 統(tǒng)計:https://dropbox.tech/infrastructure/asynchronous-task-scheduling-at-dropbox
[4] Netflix Cosmos 平臺:https://netflixtechblog.com/the-netflix-cosmos-platform-35c14d9351ad
[5] keda:https://keda.sh/
[6] Autoscaling Asynchronous Job Queues :https://d1.awsstatic.com/architecture-diagrams/ArchitectureDiagrams/autoscaling-asynchronous-job-queues.pdf
[7] 異步任務(wù):https://help.aliyun.com/document_detail/372531.html
[8] Sample and Hold 算法:https://dl.acm.org/doi/10.1145/633025.633056
[9] 網(wǎng)易云音樂音視頻算法的 Serverless 探索之路: https://developer.aliyun.com/article/801501
[10] 其它異步任務(wù)案例:https://developer.aliyun.com/article/815182