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