支付寶精細化調度的技術演進
?????♀??編者按:本文作者是螞蟻集團客戶端開發(fā)工程師企立,介紹了支付寶精細化調度的技術演進以及未來規(guī)劃,歡迎查閱~

背景
從 Unix 誕生到現在 Android,iOS 系統生態(tài)完善,對設備資源如何合理分配一直是開發(fā)者,用戶等各個角色所努力的方向。對于 Android 這樣的 linux 內核來說,設備對外調度的資源單位是進程,但是進程內部還有線程,目的就是為了更充分的利用 CPU 資源。
隨之帶來的資源爭奪,系統開發(fā)者也設計了不少的調度器 ,只為更合理的安排執(zhí)行“用戶”提交上來的線程任務,從基本的 FIFO 和 RT 調度策略,再到 CFS 和各項調度器的混合使用,每一次變化其實都是在針對不同場景,不同系統,不用時代,做不同的策略轉換。
支付寶作為生態(tài)中的重要一員,并且擁有強大的用戶群體,也在這個過程中演進,也時刻參與著這個競爭,支付寶內部的業(yè)務復雜繁多,導致“內部競爭”也異常激烈,針對這樣外有與系統其他資源的爭奪,內有業(yè)務內部的資源搶占的情況, 如何能合理的制定出適合支付寶自身的調度方案,滿足不同時期的業(yè)務需求一直是端上一個重大的挑戰(zhàn)。
一路走來-性能體系化的建設
支付寶發(fā)展到現在,已為用戶提供了大量的服務,這對性能的挑戰(zhàn)異常艱巨。性能的優(yōu)化,主要經歷了三個階段。
雛形階段:最初的版本中,業(yè)務處于高速發(fā)展期,大部分業(yè)務直接使用原生系統提供的線程池進行開發(fā)

問題與挑戰(zhàn)
由于每個業(yè)務維護自己的線程池,他們很少從端上整體資源的角度去考慮,并且隨著支付寶的發(fā)展業(yè)務也越來越多,凸顯出如下問題:
超負運行:其中很多業(yè)務會無節(jié)制的起大量線程去執(zhí)行,使 CPU 的調度壓力增大,主線程及高優(yōu)線程被分配的概率減少,資源沒有合理分配,導致支付寶運行超標,并發(fā)嚴重,引起用戶卡頓。
缺少統一調度管控:此時框架也無法進行對業(yè)務執(zhí)行線程或任務的干預,導致線程復用率低,任務也不能進行調度。
架構分散:每個業(yè)務都使用自己的線程池,架構分散厲害,對技術的沉淀也相對較少。
1.0 線程調度
雛形階段最大的問題,是支付寶超負荷運行,框架也沒有進行合理調度,所以 1.0 開始框架提供了統一的線程池服務,業(yè)務根據不同的任務類型,可以拿到一個合適的線程類型去執(zhí)行任務。這樣框架可以從整體上把控線程數量。并且不同類型線程池策略是不一樣的。這樣系統資源的分配就更加合理了,并且也有了框架干預的能力。

關鍵技術
構建核心線程池調度
支持不同優(yōu)先級類型:在原生提供的線程池基礎上新添加不同優(yōu)先級類型,將線程池歸類,按照優(yōu)先級不同,吐給業(yè)務不用類型的調度,針對支付寶特征分為:
為前臺 UI 所依賴,優(yōu)先級最高?
一類緊急任務,專為首頁渲染相關任務使用?
二類緊急任務,優(yōu)先級一般,不能容忍排隊?
普通不太緊急,可容忍排隊的后臺任務?
文件 IO 類操作,持久化任務,耗時可以預計,要么不久成功,要么發(fā)生異常?
網絡相關的后臺任務,耗時波動較大,典型使用場景為發(fā)起 RPC 請求
相同 KEY 的 Task 會保證有序串行執(zhí)行(但不一定全在同一線程),不同的 KEY 對應的 Task 之間會并發(fā)
線程數量控制:針對不同類型的線程池,制定合理的線程數量,并且做到可動態(tài)調節(jié),控制
遺留的問題
1.0 時代架構解決了統一對業(yè)務線程進行管理、分級的問題,但是我們所服務的業(yè)務也在不斷增加擴展,要支持的場景也越來越多,這樣也暴露出新的問題。
缺少重點場景聚焦:如 每個業(yè)務都想在支付寶冷起之后預加載一些邏輯,這就導致啟動后一段時間持續(xù)高并發(fā),CPU 利用率持續(xù)處于高位,這導致點擊進入某個一級業(yè)務(比如掃一掃、付款碼)等操作,用戶體感明顯降低,卡頓現象出現
調度單位局限:此時的調度單位只能到線程維度,不能更細粒度的調度執(zhí)行,這樣會導致雖然線程復用了, 但是業(yè)務還是會無節(jié)制的提交任務,導致潛在的丟任務風險,并且任務與任務之間的聯系也會被忽略
2.0 任務調度
為解決上面的問題,就需要對執(zhí)行的任務有干預能力,理解當前的用戶場景,于是我們構建了基于任務調度的框架,架構圖如下:

任務調度框架在線程調度的基礎上,添加任務調度層,業(yè)務層,監(jiān)控診斷工具?
任務調度的優(yōu)勢:
分工更明確細致,結構清晰
可接入專屬任務隔離接口,對重點場景(例如進入掃一掃)做更細粒度的資源分配調度
監(jiān)控能力提升,診斷工具逐步完善
對于基于場景的調度,例如進入掃一掃后的性能,效果顯著:

關鍵技術
任務染色:隔離公共任務與專屬任務
專屬染色線程池:框架專門提供一種染色線程池,并提高其優(yōu)先級,該線程池不對外開放,業(yè)務在執(zhí)行專屬任務時,回調用染色接口,該任務回自動轉派到該線程池中執(zhí)行
公共任務隔離:其他公共任務因沒有使用染色接口,還會繼續(xù)在公共線程池中執(zhí)行,這樣就做到了專屬任務與公共任務的隔離

染色窗口:有了染色接口并且解決了任務隔離,還有一個重要的問題是任務依賴,如業(yè)務依賴一個基礎模塊提供的服務,在基礎服務中需要開啟子線程執(zhí)行一個任務,那么在業(yè)務層面,無法干預其他模塊的任務,那么解決辦法就是業(yè)務方打開一個染色窗口,在此窗口內的任務,會被自動染色。

Captain 任務鏈調度
在解決完重點場景的調度問題后,還需要解決啟動后任務無序、高并發(fā)的情況,進一步優(yōu)化整體的執(zhí)行效率,那么如何提供能力解決這個問題呢?這里我們參考了 google 提供的 WorkManager 的設計,我們命名為 Captain 調度。
打散調度原理
構建一個 worker 任務族,用來提供存放 worker 任務,編排任務執(zhí)行過程及控制并發(fā)數量的能力, 將業(yè)務提交的任務(runnable)封裝為一個 worker,并將其放入上述的任務族中,使用對外提供的一個 WorkManager 設置各項執(zhí)行參數和調度行為。通過 worker 專屬線程池,執(zhí)行被封裝為 worker 的任務。添加 Captain 調度接口,用來業(yè)務接入和調度時機配置。
調度觸發(fā)時機可定制,目前支持
主線程繁忙程度觸發(fā)調度
當前幀率變化觸發(fā)調度
當前 CPU 使用率變化觸發(fā)
支持能力
創(chuàng)建工作鏈,建立任務依賴執(zhí)行關系能力?
可支持最大并行數量限制?
打散能力,根據任務調度優(yōu)先級,觸發(fā)時機等條件構建出對應任務族,任務族根據上述參數變化執(zhí)行任務

描述:A 先執(zhí)行,接著并行執(zhí)行 BC 和 D(此時并發(fā)數量限制為2個線程并發(fā)),其中 B 和 C 是串行,最后等 C 和 D 都執(zhí)行完,再執(zhí)行 E
接入方式

3.0 調度任務升級
針對 2.0 時代功能機制上基本滿足需求,但隨著業(yè)務發(fā)展暴露如下問題:
染色接入成本高,除自身業(yè)務模塊接入外,還需要對依賴的模塊也進行接入
調度范圍有限,只支持框架線程池任務調度
關鍵技術
AOP 收口
針對調度 2.0 產生的新問題,我們又進行了升級,首先是要收口所有非框架線程池的任務,這里用到了 AOP 的技術,對 java 層的所有啟動線程/任務的方式進行了切面收口,收口的發(fā)起方式有:

升級染色傳遞方式
調度 2.0 在遇到 native 線程、三方的 SDK 是無法做到染色傳遞的,并且對于任務(Runnable)啟動任務也是無法染色傳遞,上述情況都需要手動染色加白,業(yè)務方適配起來成本高,容易“踩坑”,調度 3.0 為了解決這個問題,采用了任務樹的構建原理,進行依賴染色傳遞。
任務樹構建原理
使用 DexAop 的能力,對任務的構造函數添加切面,在每個構造任務的時機去反向抓取任務的執(zhí)行鏈路,可以尋找到拉起當前任務的上一個任務,根據上一個任務的染色標記決定當前任務的相關屬性,以此類推生成調用鏈關系,導出運行時任務的執(zhí)行"樹"。如下圖:

任務調用鏈關系生成方式(規(guī)則):
調度管控期間由主線程派發(fā)出來的線程及任務被認為是前臺線程或任務
任務調度管控期間由 native 線程派發(fā)的
若當前線程或任務屬于前臺,則它的 next 任務及線程同樣置為前臺屬性
預先被加載的線程:按照首次喚起該線程任務作為上一個節(jié)點進行傳遞,若喚醒任務或線程為前臺任務則被喚起的線程同樣被傳遞
云控加白策略

監(jiān)控:建立以任務樹為維度的執(zhí)行監(jiān)控,對支付寶的整體任務執(zhí)行情況,不同場景內任務執(zhí)行時間,執(zhí)行占比,執(zhí)行次數進行分析
調度能力提升
讓線程進入 TIMED_WAITING 狀態(tài)
任務被提交后判斷該任務是否為前臺任務
若為前臺任務直接運行
若為非前臺任務則計算當前管控任務時間作為 sleep TimeOut 時間,并對當前線程執(zhí)行 sleep 操作
若已到達調度兜底管控時間或者業(yè)務流程已經結束則進行推出管控邏輯:對已被 sleep 的各線程觸發(fā)中斷信號,喚醒,并繼續(xù)執(zhí)行

相比 2.0 的提升
任務調度升級V2.0V3.0接入成本高低是否有漏染是無調度范圍框架線程池線程及任務所有Java線程任務調度場景隔離無有
遺留問題
能力單一問題:當前的調度能力架構,只能調度任務級別,對支付寶這樣龐大的 App 來說調度維度和調度能力,相對單一,所以我們是不是可以擴大調度范圍,從業(yè)務維度思考,建立更多維度的調度能力。
缺乏對業(yè)務的理解,任務沒有業(yè)務歸屬:其實現在支付寶已經有明顯的層級關系:不同業(yè)務域會對應有不同業(yè)務線,業(yè)務線會有不同的業(yè)務,業(yè)務內又會分為不同的模塊 ,模塊里可能才是我們調度的任務。如果可以把任務歸屬到某一業(yè)務,這樣對調度范圍,屬性傳遞都可以進行更好的控制
框架提供的啟動時機,廣播、切面等這樣的異步接口過度使用,缺少規(guī)范
關鍵節(jié)點沒有強收口,沒有調度管控,導致高并發(fā)
依賴關系不明確,未按需加載:當前任務,業(yè)務之間的依賴關系相對較分散,這樣的情況對于管理無疑是不可控的
??展望未來
精細化調度建設
3.0 任務調度仍然有很多問題:框架缺乏對業(yè)務的理解,任務沒有業(yè)務歸屬,調度的范圍也只有任務這一單一維度,各種啟動時機、廣播等過度使用,缺少規(guī)范,沒有站在用戶的角度去思考和決策端上的任務編排。后續(xù)新的調度框架會以“按需加載”為原則,建立更符合當前情況的精細化調度,解決上述問題。

精細化調度要具備的能力

統一管控:將對端上的事件,周期等做統一管控
模塊化插拔:支持以"模塊"為維度的調度,可支持云端配置,以用戶視角去調度功能模塊
分級能力:基于設備分級體驗能力,包括:性能評分分級,網絡狀態(tài)分級。對設備做更細致的分析,達到功能模塊,產品在適當的機型運行,例如低端機上可以對營銷類業(yè)務,動畫進行降級處理
場景識別,串聯:解決端上錯綜復雜的依賴關系,如當前冷起進入掃碼場景時,將前臺場景屬性 tag 添加到多媒體模塊的任務里,保證在接下來的流程中傳遞并高優(yōu)執(zhí)行
中心化決策:建立統一決策中心,包括產品、運營決策調度;業(yè)務輸入的調度;根據用戶行為預測的智能決策