隨著美團外賣業務不斷髮展,外賣廣告引擎團隊在多個領域進行了工程上的探索和實踐,目前已經取得了一些成果。我們計劃通過連載的形式分享給大家,本文是《美團外賣廣告工程實踐》專題連載的第一篇。本文針對業務提效的目標,介紹了美團外賣廣告引擎在平臺化過程中的一些思考和實踐。
1 前言
美團外賣已經成為公司最為重要的業務之一,而商業變現又是整個外賣生態重要的組成部分。經過多年的發展,廣告業務覆蓋了Feed流形式的列表廣告,針對KA以及大商家的展示廣告,根據使用者查詢Query的搜尋廣告,以及一些創新場景的創新廣告等多個產品線,並對應十幾個細分的業務場景。
從技術層面而言,一次廣告請求的過程,可以分為以下幾個主要步驟:廣告的觸發、召回、精排、創意優選、機制策略等過程。如下圖所示:即通過觸發得到使用者的意圖,再通過召回得到廣告候選集,通過預估對候選集的店鋪打分、排序,再對於Top的店鋪再進行創意的選擇,最後經過一些機制策略得到廣告結果。
2 現狀分析
在業務迭代的過程中,隨著新業務場景的不斷接入,以及原有業務場景功能的不斷迭代,系統變得越來越複雜,業務迭代的需求響應逐漸變慢。在業務發展前期,開展過單個模組的架構重構,如機制策略、召回服務,雖然對於效率提升有一定的改善,但是還會存在以下一些問題:
- 業務邏輯複用度低:廣告業務邏輯比較複雜,比如機制服務模組,它主要功能是為廣告的控制中樞以及廣告的出價和排序的機制提供決策,線上支援十幾個業務場景,每種場景都存在很多差異,比如會涉及多種召回、計費模式、排序方案、出價機制、預算控制等等。此外,還有大量業務自定義的邏輯,由於相關邏輯是演算法和業務迭代的重點,因此開發人員較多,並且分佈在不同的工程和策略組內,導致業務邏輯抽象粒度標準不夠統一,使得不同場景不同業務之間複用程度較低。
- 學習成本高:由於程式碼複雜,新同學熟悉程式碼成本較高,上手較難。此外,線上服務很早就進行了微服務改造,線上模組數量超過20個,由於歷史原因,導致多個不同模組使用的框架差異較大,不同模組之間的開發有一定的學習成本。在跨模組的專案開發中,一位同學很難獨立完成,這使得人員效率沒有得到充分利用。
- PM(產品經理)資訊獲取難:由於目前業務場景較多、邏輯複雜,對於資訊的獲取,絕大多數同學很難了解業務的所有邏輯。PM在產品設計階段需要確認相關邏輯時,只能讓研發同學先檢視程式碼,再進行邏輯的確認,資訊獲取較難。此外,由於PM對相關模組的設計邏輯不清楚,往往還需要通過找研發人員線下進行詢問,影響雙方的工作效率。
- QA(測試)評估難:QA在功能範圍評估時,完全依賴於研發同學的技術方案,且大多數也是通過溝通來確認功能改動涉及的範圍和邊界,在影響效率的同時,還很容易出現“漏測”的問題。
3 目標
針對以上的問題,我們從2020年初,啟動美團外賣廣告引擎平臺化專案,旨在通過平臺化的專案達成以下目標。
提升產研效率
- 高功能複用度,提升開發效率。
- 降低研發人員(RD)、PM、QA之間的協作成本,提升產研協作的效率。
提升交付質量
- 精確QA測試的範圍,提升交付的質量。
- 對業務進行賦能。
- PM可通過視覺化的平臺化頁面,瞭解其他產品線的能力,互相賦能,助力產品迭代。
4 整體設計
4.1 整體思想
目前,業界已經有不少“平臺化”方向的研究,比如阿里巴巴的TMF,定位於泛交易類系統的平臺化領域範疇,主要建設思想是,流程編排與領域擴充套件分層,業務包與平臺分離的外掛化架構,管理域與執行域分離。而阿里巴巴的AIOS則定位於搜推平臺化領域範疇,主要依賴於底層5大核心元件,以運算元流程圖定製的模式對元件快速組合與部署,從而實現了業務的快速交付。
美團外賣在平臺化專案啟動時,從業務場景和業務痛點出發,確定了我們專案的核心目標:利用平臺化設計理念構建相適應的技術能力,將現有外賣廣告的業務系統和產研流程轉變為平臺化模式,快速支援外賣廣告多業務進行交付。我們借鑑了行業內平臺化的成熟思想,確定了以業務能力標準化為基礎、構建平臺化框架技術能力為支撐、產研平臺化模式升級為保障的平臺化建設整體思想,整體思想可分為三部分:業務能力標準化、技術能力框架化、平臺化產研新流程。
- 業務能力標準化:通過對現有邏輯的梳理,進行標準化的改造,為多業務場景、多模組程式碼複用提供基礎保證。
- 技術能力框架化:提供組合編排能力將標準化的邏輯串聯起來,通過引擎排程執行,同時完成了視覺化能力的透出,幫助使用者快速獲取資訊。
- 平臺化產研新流程:為保證專案上線之後實現研發迭代的整體提效,我們對於研發流程的一些機制也進行了一些優化,主要涉及研發人員、PM、QA三方。
即通過標準化提供複用的保證,通過框架承載平臺化落地的能力,通過產研新流程的執行機制保證了整體提效的持續性。整個廣告引擎服務涉及到的模組都遵循了平臺化的思想,支撐上游各個產品場景,如下圖所示:
4.2 業務標準化
4.2.1 業務場景與流程分析
提效是平臺化最重要的目標之一,而提效最重要的手段是讓功能在系統中得到最大程度上的複用。我們首先針對外賣廣告業務線場景和流量的現狀做了統一的分析,得出以下兩點結論:
第一,各業務線大的流程基本類似,都包括預處理、召回、預估、機制策略、排序、創意、結果組裝等幾個大的步驟;同時,不同業務相同的步驟裡會有很多相似的功能和業務線特有的功能。第二,這些功能理論上都是可以整體進行復用的,但現狀是這些功能都集中在業務線內部,不同的業務線之間,不同的小組之間的複用狀況也不盡相同。而造成這一問題的主要原因是:
- 不同業務處在不同的發展階段,也有著不同的迭代節奏。
- 組織結構天然存在“隔離”,如推薦和搜尋業務分在兩個不同的業務小組。
因此,阻礙外賣廣告進一步提升複用程度的主要原因,在於整體的標準化程度不足,各業務線間沒有統一的標準,所以我們要先解決標準化建設的問題。
4.2.2 標準化建設
標準化建設的廣度和深度決定了系統複用能力的高低。因此,本次標準化的建設目標要覆蓋到所有方面。我們對廣告系統所有的服務,從業務開發的三個維度,包括實現的功能、功能使用的資料、功能組合的流程出發,來進行統一廣告的標準化建設。從而使得:
- 在個體開發層面:開發同學不用關注如何流程排程,只需將重心放在新功能的實現上,開發效率變得更高。
- 從系統整體角度:各個服務對於通用的功能不用再重複開發,整體的複用程度更高,節省了大量的開發時間。
4.2.2.1 功能的標準化
針對功能的標準化問題,我們首先依據功能是否跟業務邏輯相關,將其劃分為兩部分:業務邏輯相關和業務邏輯無關。
① 與業務邏輯無關的功能通過雙層抽象來統一共建
- 所有業務線統一共建的標準化形式是進行雙層抽象。對於單個的、簡單的功能點,抽象為工具層;對於可獨立實現並部署的某一方面功能,比如創意能力,抽象為元件層。工具層和元件層統一以JAR包的形式對外提供服務,所有工程都通過引用統一的JAR包來使用相關的功能,避免重複的建設,如下圖所示:
② 與業務邏輯有關的功能,在複用範圍上進行分層複用
- 業務邏輯相關的功能是此次標準化建設的核心,目標是做到最大程度的業務複用。因此,我們將最小不可拆分的業務邏輯單元抽象為業務同學開發的基本單位,稱為Action。同時根據Action不同的複用範圍,將其劃分為三層,分別是所有業務可以複用的基礎Action,多業務線複用的模組Action,具體單一業務定製的業務Action,亦即擴充套件點。所有的Action都是從Base Action派生出來的,Base Action裡定義了所有Action統一的基礎能力。
- 不同的Action型別分別由不同型別的開發同學來開發。對於影響範圍比較大的基礎Action和模組Action,由工程經驗豐富的同學來開發;對於僅影響單個業務的業務Action或擴充套件點,由工程能力相對薄弱的同學來進行開發。
- 同時我們把多個Action的組合,抽象為Stage,它是不同Action組合形成的業務模組,目的在於遮蔽細節,簡化業務邏輯流程圖的複雜度,並提供更粗粒度的複用能力。
4.2.2.2 資料的標準化
資料作為實現功能的基本元素,不同業務的資料來源大同小異。如果不對資料進行標準化設計,就無法實現功能標準化的落地,也無法實現資料層面的最大化複用。我們從資料來源和資料使用方式兩方面來劃分資料:對於業務能力的輸入資料、中間資料,輸出資料,通過標準化的資料上下文來實現;同時對於第三方外部資料及詞表等內部資料,通過統一的容器儲存和介面獲取。
① 使用上下文Context描述Action執行的環境依賴
- 每個Action執行都需要一定的環境依賴,這些依賴包括輸入依賴、配置依賴、環境引數、對其他Action的執行狀態的依賴等。我們將前三類依賴都抽象到業務執行上下文中,通過定義統一的格式和使用方式來約束Action的使用。
- 考慮不同層級Action對於資料依賴使用範圍由大到小,遵循相同的分層設計,我們設計了三層依次繼承的Context容器,並將三類依賴的資料標準化儲存到相應的Context中。
- 使用標準化Context進行資料傳遞,優勢在於Action可自定義獲取輸入資料,以及後續擴充套件的便利性;同時標準化的Context也存在一定的劣勢,它無法從機制上完全限制Action的資料訪問許可權,隨著後續迭代也可能導致Context日漸臃腫。綜合考慮利弊後,現階段我們仍然採用標準的Context的模式。
② 第三方外部資料的統一處理
- 對於第三方的外部資料的使用,需要成熟的工程經驗提前評估呼叫量、負載、效能、批量或拆包等因素,所以針對所有第三方外部資料,我們統一封裝為基礎Action,再由業務根據情況定製化使用。
③ 詞表資料的全生命週期管理
- 詞表根據業務規則或策略生成,需要載入到記憶體中使用的KV類資料,標準化之前的詞表資料在生成、拉取、載入、記憶體優化、回滾、降級等能力上有不同程度的缺失。因此,我們設計了一套基於訊息通知的詞表管理框架,實現了詞表的版本管理、定製載入、定時清理、流程監控的全生命週期覆蓋,並定義了業務標準化的接入方式。
4.2.2.3 呼叫流程的標準化
最後,將功能和資料進行組合的是業務的呼叫流程,統一的流程設計模式是業務功能複用和提效的核心手段。流程設計統一的最佳方式就是標準化業務流程。其中對於第三方介面的呼叫方式,讓框架研發的同學用集中封裝的方式進行統一。對於介面的呼叫時機,則基於效能優先併兼顧負載,且在沒有重複呼叫出現的原則下,進行標準化。
在具體實踐中,我們首先梳理業務邏輯所使用到的標準化功能,然後分析這些功能之間的依賴關係,最後以效能優先併兼顧負載、無重複呼叫等原則,完成整個業務邏輯流程的標準設計。
從橫向維度看,通過比較不同業務邏輯流程的相似性,我們也提煉了一定的實踐經驗,以中控模組為例:
- 對於使用者維度的第三方資料,統一在初始化後進行封裝呼叫。
- 對於商家維度的第三方資料,有批量介面使用的資料,在召回後統一封裝呼叫;無批量介面使用的資料,在精排截斷後統一封裝呼叫。
4.3 技術框架
4.3.1 整體框架介紹
平臺主要有兩個部分組成,一部分是平臺前臺部分,另一部分是平臺開發框架包。其中前臺部分是一個給研發人員、PM以及QA三種角色使用的Web前臺,主要功能是跟整合了平臺開發框架包的引擎服務進行視覺化的互動,我們也給這個平臺起了個名字,叫Camp平臺,這是大本營的意思,寓意助力業務方攀登業務高峰。平臺開發框架包被引擎後臺服務所整合,提供引擎排程隔離、能力沉澱、資訊上報等功能,同時還能確保各個模組保持同樣標準的框架和業務能力風格。
各個線上服務都需要引入平臺開發框架包,服務效能與平臺通用性之間如何平衡也是我們需要著重考慮的地方。這是因為,引入平臺框架會對原有的程式碼細節進行增強性擴充套件;在C端大流量場景下,平臺框架做得越通用,底層功能做得越豐富,與單純的“裸寫”程式碼相比,會帶來一些效能上的折損。因此,在效能開銷與平臺抽象能力上,需要儘量做到一個折中。我們結合自身業務的特性,給出的安全閾值是TP999損失在5ms以內,將各個業務通用的能力下沉至框架,提供給上層的線上服務。
綜上,整個系統架構設計如下:
① Camp平臺提供管理控制和展示的功能,該平臺由以下幾個子模組包組成:
- 業務視覺化包,提供各個後臺系統上的能力的靜態資訊,包括名稱、功能描述、配置資訊等,這些資訊在需求評估階段、業務開發階段都會被用到。
- 全圖化編排和下發包,業務開發同學通過對已有的能力進行視覺化的拖拽,通過全圖化服務自動生成並行化最優的執行流程,再根據具體業務場景進行調整,最終生成一個有向無環圖,圖的節點代表業務能力,邊表示業務能力之間的依賴關係。該圖會動態下發到對應的後臺服務去供執行框架解析執行。
- 統計監控包,提供業務能力、詞典等執行期間的統計和異常資訊,用於檢視各個業務能力的效能情況以及異常情況,達到對各個業務能力執行狀態可感知的目的。
② 平臺開發框架包被廣告引擎的多個服務引入,執行編排好的業務流程並對外提供服務,平臺框架開發包由以下幾個子模組包組成:
- 核心包,提供兩個功能,第一個是排程功能,執行平臺下發的流程編排檔案,按照定義的DAG執行順序和執行條件去依次或並行執行各個業務能力,並提供必要的隔離和可靠的效能保證,同時監控執行以及異常情況進行上報。第二個是業務採集和上報功能,掃描和採集系統內的業務能力,並上報至平臺Web服務,供業務編排以及業務能力視覺化透出使用。
- 能力包,業務能力的集合,這裡的業務能力在前面章節“4.2.2.1 功能的標準化”中已給出定義,即“將最小不可拆分的業務邏輯單元,抽象為業務同學開發的基本單位,稱為Action,也叫能力”。
- 元件包,即業務元件的集合,這裡的業務元件在章節“4.2.2.1 功能的標準化”中也給出定義,即“對於可獨立實現並部署的某一方面功能,比如創意能力,抽象為元件”。
- 工具包,提供業務能力需要的基礎功能,例如引擎常用的詞典工具、實驗工具以及動態降級等工具。這裡的工具在章節“4.2.2.1功能的標準化”中同樣給出了定義,即單個的、簡單的非業務功能模組抽象為工具。
一個典型的開發流程如上圖所示 ,開發人員開發完業務能力後(1),業務能力的靜態資訊會被採集到Camp平臺(2),同時,經過全圖化依賴推導得到最優DAG圖(3),業務同學再根據實際業務情況對DAG圖進行調整,引擎線上服務執行期間會得到最新的DAG流程並對外提供最新的業務流程服務(4,5),同時會把業務執行的動態資訊上報至Camp平臺(6)。
在下面的章節中,我們將對幾個比較關鍵的技術點進行詳細描述,其中就包括了視覺化相關的元件自動上報和DAG執行相關的全圖化編排、執行排程等,最後,本文還會介紹一下跟廣告業務強相關的、詞典在平臺化中統一封裝的工作。
4.3.2 業務採集&上報
為了方便管理和查詢已有業務能力,平臺開發框架包會在編譯時掃描@LppAbility註解和@LppExtension註解來上報後設資料到Camp平臺。業務同學可以在Camp平臺中對已有元件進行查詢和視覺化的拖拽。
//原子能力(Action)
@LppAbility(name = "POI、Plan、Unit資料聚合平鋪能力", desc = "做預算過濾之前,需要把物件打平",
param = "AdFlatAction.Param", response = "List<KvPoiInfoWrapper>", prd = "無產品需求", func = "POI、Plan、Unit資料聚合平鋪能力", cost = 1)
public abstract class AdFlatAction extends AbstractNotForceExecuteBaseAction {
}
//擴充套件點
@LppExtension(name = "資料聚合平鋪擴充套件點",
func = "POI、Plan、Unit資料聚合平鋪", diff = "預設的擴充套件點,各業務線直接無差異", prd = "無", cost = 3)
public class FlatAction extends AdFlatAction {
@Override
protected Object process(AdFlatAction.Param param) {
//do something
return new Object();
}
}
4.3.3 全圖化編排
在廣告投放引擎服務中,每個業務的DAG圖,動輒便會有幾十甚至上百的Action,通過傳統的人工編排或業務驅動編排,很難做到Action編排的最優並行化。因此,平臺化框架包採用資料驅動的思想,通過Action之間的資料依賴關係,由程式自動推匯出並行化最優的DAG圖,即全圖化編排,此後再由業務人員根據業務場景和流量場景進行定製化調整,動態下發到服務節點,交由排程引擎執行,這樣通過自動推導+場景調優的方式便達到了場景下的最優並行。
① 全圖化自動編排的基本原理
我們定義某個Action x的入參集合為該Action x執行時使用的欄位,表示如下:
$input_x(A,B,C......N)$
定義某個Action y的出參集合為該Action執行後產出的欄位,表示如下:
$output_y(A,B,C......M)$
當存在任意以下兩種情況之一時,我們會認為Action x依賴於Action y。
- input_x ∩ output_y ≠ ∅,即Action x的某個/某些入參是由Action y產出。
- output_x ∩ output_y ≠ ∅,即Action x與Action y操作相同欄位。
② 全圖化自動編排總設計
全圖化自動編排總體分為兩個模組:解析模組、依賴分析模組。
解析模組:通過對位元組碼分析,解析出每個Action的input、output集合。
- 位元組碼分析使用了開源工具ASM,通過模擬Java執行時棧,維護Java執行時區域性變數表,解析出每個Action執行依賴的欄位和產出的欄位。
依賴分析模組:採用三色標記的逆向解析法,分析出Action之間的依賴關係,並對生成的圖進行剪枝操作。
- 依賴剪枝:生成圖會有重複依賴的情況,為了減少圖複雜度,在不改變圖語義的前提下,對圖進行了依賴剪枝。例如:
③ 全圖化自動編排收益效果
自動糾正人工錯誤編排,並最大化編排並行度。某實際業務場景中,全圖化前後的DAG對比,如下圖所示:
標記藍色的兩個Action,會同時操作同一個Map,如果併發執行會有執行緒安全風險。由於方法呼叫棧過深,業務開發同學很難關注到該問題,導致錯誤的並行化編排。經過全圖化分析後,編排為序列執行。
標記綠色、紅色、黃色的三組Action,每組內的兩個Action並沒有資料依賴關係,業務開發同學序列化編排。經過全圖化分析後,編排為並行。
4.3.4 排程引擎
排程引擎的核心功能是對上述下發後的DAG進行排程。因此引擎需要具備以下兩個功能:
- 構圖:根據Action的編排配置生成具體的DAG模板圖。
- 排程:流量請求時,按照正確的依賴關係執行Action。
整個排程引擎的工作原理如下圖:
出於對效能的考慮,排程引擎摒棄了流量請求實時構圖的方法,而是採用“靜態構圖+動態排程”的方式。
- 靜態構圖:在服務啟動時,排程引擎根據下發的DAG編排配置,初始化為Graph模板並載入至記憶體。服務啟動後,多個DAG的模板會持久化到記憶體中。當Web平臺進行圖的動態下發後,引擎會對最新的圖進行構圖並完全熱替換。
- 動態排程:當流量請求時,業務方指定對應的DAG,連同上下文資訊統一交至排程引擎;引擎按照Graph模板執行,完成圖及節點的排程,並記錄下整個排程的過程。
由於廣告投放引擎服務於C端使用者,對服務的效能、可用性、擴充套件性要求很高。排程引擎的設計難點也落在了這三個方面,接下來我們將進行簡要的闡述。
4.3.4.1 高效能實踐
流程引擎服務於C端服務,與傳統的硬編碼排程相比,引擎的排程效能要至少能持平或在一個可接受的效能損失閾值內。下面,我們將從排程器設計、排程執行緒調優這兩個有代表性的方面介紹下我們的效能實踐。
① 排程器設計
含義:如何讓節點一個一個的執行;一個節點執行完成,如何讓其他節點感知並開始執行。如下圖中,A節點在執行完成後,如何通知B,C節點並執行。常見的思路是,節點的分層排程,它的含義及特點如下:
- 依賴分層演算法(如廣度優先遍歷)提前計算好每一層需要執行的節點;節點一批一批的排程,無需任何通知和驅動機制。
- 在同批次多節點時,由於各節點執行時間不同,容易出現長板效應。
- 在多序列節點的圖排程時,有較好的效能優勢。
另一種常見的思路是,基於流水線思想的佇列通知驅動模式:
- 某節點執行完成後,立即傳送訊號給訊息佇列;消費側在收到訊號後,執行後續節點。如上圖DAG中,B執行完成後,D/E收到通知開始執行,不需要關心C的狀態。
- 由於不關心兄弟節點的執行狀態,不會出現分層排程的長板效應。
- 在多並行節點的圖排程時,有非常好的並行效能;但在多序列節點的圖中,由於額外存線上程切換和佇列通知開銷,效能會稍差。
如上圖所示,排程引擎目前支援這兩種排程模型。針對多序列節點的圖推薦使用分層排程器,針對多並行節點的圖推薦使用佇列流水線排程器。
分層排程器
依賴於上面提到的分層演算法,節點分批執行,序列節點單執行緒執行,並行節點池化執行。
佇列流水線排程器
無論是外層的圖任務(GraphTask)還是內部節點任務(NodeTask)均採用池化的方式執行。
節點排程機制
- 排程機制:消費側收到訊息到節點被執行,這中間的過程。如下DAG中,節點在接收到訊息後需依次完成:檢驗DAG執行狀態、校驗父節點狀態、檢驗節點執行條件、修改執行狀態、節點執行這幾個過程,如下圖所示:
- 這幾個步驟的執行,通常存在兩種方式:一種是集中式排程,由統一的方法進行處理;另一種是分散式排程,由每個後續節點獨自來完成。
- 我們採用的為集中式排程:某節點執行完成後,傳送訊息到佇列;消費側存在任務分發器統一負責消費,再進行任務分發。
這樣做的出發點是:
- 如上圖中,ABC三個節點同時完成,到D節點真正執行前仍有一系列操作,這個過程中如果不加鎖控制,D節點會出現執行三次的情況;因此,需要加鎖來保證執行緒安全。而集中式任務分發器,採用無鎖化佇列設計,在保證執行緒安全的同時儘量規避加鎖帶來的效能開銷。
- 再如一父多子的情況,一些公共的操作(校驗圖/父節點狀態、異常檢測等),各子節點都會執行一次,會帶來不必要的系統開銷。而集中式任務分發器,對公共操作統一進行處理,再對子節點任務進行分發。
- 分散式排程中,節點的職責範圍過廣,既需要執行業務核心程式碼,還需要額外處理訊息的消費,職責非單一,可維護性較差。
因此,在專案實際開發中,考慮到實現的難度、可維護性、以及綜合考量效能等因素,最終採用集中式排程。
② 排程執行緒調優
排程引擎在DAG執行上,提供了兩種API給呼叫方,分別為:
- 非同步呼叫:GraphTask由執行緒池來執行,並將最外層GraphTask的Future返回給業務方,業務方可以精準的控制DAG的最大執行時間。目前,外賣廣告中存在同一個請求中處理不同廣告業務的場景,業務方可以根據非同步介面自由組合子圖的排程。
- 同步呼叫:與非同步呼叫最大的不同是,同步呼叫會在圖執行完成/圖執行超時後,才會返回給呼叫方。
而底層排程器,目前提供上述講到兩種排程器。具體如下圖所示:
由此看出,排程引擎在內部任務執行上,多次用到了執行緒池。在CPU密集型的服務上,請求量過大或節點過多的話,大量執行緒切換勢必會影響到服務的整體效能。針對佇列通知排程器,我們做了一些排程優化,儘量將效能拉回到沒有接入排程引擎之前。
排程執行緒模型調優
- 針對同步呼叫的情況,由於主執行緒不會直接返回,而是在等待DAG圖執行完成。排程引擎利用這一特點,讓主執行緒來執行最外層的GraphTask,在處理每個請求時,會減少一次執行緒的切換。
序列節點執行優化
- 如上面DAG圖中,存在一些序列節點(如單向A→B→C→D),在執行這4個序列節點時,排程引擎則不會進行執行緒的切換,而是由一個執行緒依次完成任務執行。
- 在執行序列節點時,排程引擎同樣不再進行佇列通知,而是採用序列排程的方式執行,最大化減少系統開銷。
4.3.4.2 高可用實踐
在高可用上,我們從隔離和監控上簡要介紹下我們的實踐,它的核心原理如下圖所示:
① 業務隔離
廣告場景中,同一服務中經常會存在多條子業務線,每條業務線的邏輯對應一張DAG。對於同一服務內各個業務線的隔離,我們採用的是“單例項-多租戶”的方案。這是因為:
- 流程引擎活躍在同一個程式內,單例項方案管理起來要更容易。
- 流程引擎內部實現過程中,針對圖的粒度上做了一些多租戶隔離工作,所以在對外提供上更傾向於單例項方案。
除DAG排程和Node排程為靜態程式碼外,圖的儲存、DAG的選取與執行、Node節點的選取與執行、各DAG的節點通知佇列都採用多租戶隔離的思想。
② 排程任務隔離
排程任務主要分為:DAG任務(GraphTask)、節點任務(NodeTask)兩類。其中一個GraphTask對應多個NodeTask,並且其執行狀態依賴所有的NodeTask。排程引擎在執行時,採用二級執行緒池隔離的方式將GraphTask和NodeTask的執行進行隔離。
這樣隔離的出發點是:
- 每個執行緒池職責單一,執行任務更加單一,相應的過程監控與動態調整也更加方便。
- 如果共用一個執行緒池,如果出現瞬時QPS猛增,會導致執行緒池全被GraphTask佔據,無法提交NodeTask最終導致排程引擎死鎖。
因此,無論是執行緒精細化管理還是隔離性上,兩級執行緒池排程的方式都要優於一級執行緒池排程。
③ 過程監控
對DAG排程的監控,我們將其分成三類。分別為異常、超時、統計,具體如下:
- 異常:圖/節點執行異常,支援配置重試、自定義異常處理。
- 超時:圖/節點執行超時,支援降級。
- 統計:圖/節點執行次數&耗時,提供優化資料包表。
4.3.4.3 高可用實踐
廣告業務邏輯複雜,在投放鏈路上存在大量的實驗、分支判斷、條件執行等。並且廣告投放服務的迭代頻率和發版頻率也非常高。因此,排程引擎在可擴充套件上首先要考慮的是如何排程條件節點,以及編排配置如何在無釋出下快速生效這兩個問題。
① 節點條件執行
對於節點的條件執行,我們在配置DAG時,需要顯示的增加Condition表示式。排程引擎在執行節點前,會動態計算表示式的值,只有滿足執行條件,才會執行該節點。
② 配置動態下發
- 如前圖所示,我們將構圖與排程通過中間態Graph模板進行解耦,編排配置可以通過Web平臺編輯後,動態下發到服務上。
- 由於排程引擎在排程過程中,多次用到了執行緒池,對於執行緒池的動態更新,我們藉助了公司的通用元件對執行緒池進行動態化配置和監控。
4.3.4.4 排程引擎總結
① 功能方面
DAG核心排程
- 排程引擎提供兩種常見排程器的實現,針對不同的業務場景,能較好的提供支援。
- 排程引擎採用經典的兩級排程模型,DAG圖/節點任務排程更具有隔離性和可控性。
節點條件執行
- 對於節點的排程前置增加條件校驗功能,不滿足條件的節點不會執行,排程引擎會根據上下文以及流量情況動態判斷節點的執行條件。
超時處理
- 對DAG、Stage、Node節點均支援超時處理,簡化內部各個業務邏輯的超時控制,將主動權交給框架統一進行處理。在保證效能的前提之下,提高內部邏輯的處理效率。
節點可配置化
- 同一個Node節點,會被對個業務場景使用,但各業務場景的其處理邏輯且不近相同。針對這種情況,增加節點的配置化功能,框架將節點的配置傳入邏輯內部,實現可配置。
② 效能方面
- 在多序列節點的DAG場景下,效能基本可以持平原有的裸寫方式。
- 在多並行節點的DAG場景下,由於池化的影響,在多執行緒池搶佔和切換上,存在一些效能折損;再進行多次調優和CPU熱點治理上,TP999折損值可以控制到5ms以內。
4.3.5 業務元件層沉澱
如“4.2.2.1 功能的標準化”中給出的定義,可獨立實現並部署的業務功能模組抽象為業務元件。從業務邏輯中提取高內聚、低耦合的業務元件,是提升程式碼複用能力的重要手段。在實踐中,我們發現不同業務元件包含的邏輯千差萬別,具體實現方式和設計與程式碼風格也參差不齊。因此,為了統一業務元件的設計思路和實現方式,我們實現了一套標準化的元件框架,以減少新元件開發的重複性工作,並降低使用方的學習和接入成本。
上圖左邊展示了業務元件的整體框架,底層為統一的公共域和公共依賴,上層為業務元件標準的實現流程,切面能力則實現對業務邏輯的支援。右邊為基於框架開發的智慧出價元件示例。框架的作用是:
① 統一的公共域和依賴管理
- 公共域是指在不同的業務元件中都會使用到的業務實體。我們將業務上的公用域物件提取出來,作為基礎元件提供給其他業務元件使用,以減少域物件在不同元件重複定義。
- 業務元件都有很多內部和外部的依賴。我們對公共依賴進行了統一的梳理和篩選,同時權衡各方面因素,確定了合理的使用方式。最終形成一套完整成熟的依賴框架。
② 統一的介面和流程
- 我們將業務元件抽象為三個階段:資料和環境準備階段Prepare、實際計算階段Process和後置處理階段Post。每個階段都設計了抽象的泛型模板介面,最後通過不同的介面組合完成元件中的不同業務流程。所有類在介面設計上都提供了同步和非同步兩種呼叫方式。
③ 統一的切面能力
- 目前所有的服務模組均採用Spring作為開發框架,我們利用其AOP功能開發了一系列的切面擴充套件能力,包括日誌採集、耗時監控、降級限流、資料快取等功能。這些功能均採用無侵入式程式碼設計,減少切面能力與業務邏輯的耦合。新的業務元件通過配置的方式即可完全複用。
智慧出價元件即為基於以上框架開發的業務元件。智慧出價元件是對廣告出價策略的抽象聚合,包括PID、CEM等多個演算法。出價策略依賴的使用者特徵獲取、實驗資訊解析等資料統一採用Prepare模板實現;具體PID、CEM演算法的實施統一採用Process模板實現;對出價結果的校驗、引數監控等後置操作則統一採用Post模板實現。整個元件所使用的公用域物件和第三方依賴也統一託管於框架進行管理。
4.3.6 工具包-詞典管理
在“4.2.2.1 功能的標準化”中也定義了工具包的含義,即單個的、簡單的非業務功能模組抽象為工具。工具包的建設是廣告平臺化工作提效的重要基礎,其主要的作用是處理業務邏輯無關的輔助類通用流程或功能。例如:廣告系統中存在大量的KV類資料需要載入到記憶體中使用,我們稱之為詞表檔案。為了實現詞表檔案的全生命週期管理,廣告平臺化進行了詞表管理工具的設計與開發,並在業務使用過程中積累了很好的實踐效果。
① 詞表管理的設計
上圖是詞表管理平臺的整體架構,詞表管理平臺整體採用分層設計,自上而下分別五層:
- 儲存層:主要用於資料的儲存和流轉。其中美團內部的S3完成在雲端的詞表檔案儲存,Zookeeper主要用於儲存詞表的版本資訊,線上服務通過監聽的方式獲取最新的版本更新事件。
- 元件層:每個元件可以視為獨立的功能單元,為上層提供通用的介面。
- 外掛層:業務外掛的作用主要是提供統一的外掛定義和靈活的自定義實現。例如:載入器主要用途為提供統一格式的詞表載入和儲存功能,每個詞表可以動態配置其載入器型別。
- 模組層:模組層主要是從業務角度看整體詞表檔案不同流程的某一環節,模組之間通過事件通知機制完成互動。例如:詞表管理類模組包含詞表版本管理、事件監聽、詞表註冊、詞表加/解除安裝、詞表訪問等。
- 流程層:我們將一個完整詞表業務行為過程定義為流程。詞表的整個生命週期可以分為新增詞表流程、更新詞表流程、登出詞表流程、回滾詞表流程等。
② 詞表管理的業務收益
平臺化詞典管理工具在業務實踐中具有的主要優勢為:
- 更靈活的服務架構:詞表流程的透明化。使用方無需關注詞表流轉過程,採用統一API訪問。
- 統一的業務能力:統一的版本管理機制,統一的儲存框架,統一的詞表格式和載入器。
- 系統高可用:快速恢復和降級能力,資源和任務隔離、多優先順序處理能力等多重系統保障功能。
4.4 產研新流程
上文中提到,由於廣告業務線較多,且涉及諸多上下游,工程與策略經過幾年快速迭代之後,現有業務邏輯已極為複雜,導致在日常迭代中,一些流程性問題也逐步凸顯。
① PM資訊獲取困難
PM在進行產品調研與設計時,對涉及的相關模組當前邏輯不是很清楚,往往通過線下諮詢研發人員的方式來解決,影響雙方的效率,同時產品設計文件中純以業務視角和流程來闡述,導致每次評審時,QA和研發人員很難直觀獲取到改動點和改動範圍,中間又會花費大量時間來相互溝通,從而確認邊界與現有邏輯的相容性等問題。
② 研發人員的功能評估完全依賴經驗
研發人員在方案設計時,很難直接獲取到橫向相關模組是否有類似功能點(可複用或可擴充套件),導致複用率低,同時在專案排期時完全依賴個人經驗,且沒有統一的參考標準,經常出現因工作量評估不準而導致專案延期的情況。
③ QA測試及評估效率低
QA在功能範圍評估時,完全依賴研發同學(RD)的技術方案,且大多數也是通過口頭交流的方式來確認功能改動涉及的範圍和邊界,在影響效率的同時,還會導致一些測試問題在整個專案週期中被後置,影響專案的進度。同時,平臺化後基礎JAR包的管理完全依靠人工,對一些Action,尤其是基礎Action也沒有統一的測試標準。以上問題可以概括如下:
4.4.1 目標
藉助平臺化,對專案交付的整個過程(如下圖所示),實施產研新流程,以解決產品、研發與測試人員在迭代中遇到的問題,賦能業務,從而提升整體專案的交付效率與交付質量。
4.4.2 思考與落地
基於平臺化實施產研新流程,即利用Stage/Action的方式來驅動整個專案的交付,如下圖所示:
- 對於PM(產品):建設Stage/Action視覺化能力,並在專案設計中應用。
- 對於RD(研發):統一採用新的基於Stage/Action的方案,設計及開發排期模式。
- 對於QA(測試):統一溝通協作語言-Stage/Action,並推動改進相關測試方法和測試工具
4.4.2.1 產品側
下圖所示的是產研功能建設後的應用與實踐效果。前兩張為建設的業務能力視覺化,為PM提供一個瞭解各業務最新流程及詳細Action能力的視覺化功能,第三張圖為產品設計中相關業務的調研與功能描述(出於資料安全原因,以下截圖採用非真實專案舉例說明)。
4.4.2.2 研發側
根據專案開發週期中研發工作的不同階段,我們制定了基於程式碼開發前後的流程規範,以保證整個開發週期中研發同學能充分利用平臺的能力進行設計與開發提效。
開發前
- 技術設計:基於各業務涉及的現有Action功能與Action DAG的視覺化能力,進行橫向業務的調研參考與複用評估,以及新增或變更Action功能的技術設計。
- 專案排期:基於技術設計中Action能力的新增、變更、複用情況以及Action層級等,對開發工作量進行較為標準化的評估。
開發後
- Action沉澱:系統統一上報並定期評估平臺Action能力的複用度和擴充套件情況。
- 流程反饋:追蹤基於平臺化的每個專案,並對交付流程中的相關指標做量化上報,同時收集專案人員反饋。
4.4.2.3 測試側
- 採用Stage/Action統一溝通協作語言:在需求設計與評審、方案設計與評審、測試用例編寫與評審等多方參與的專案環節,統一採用Stage/Action為功能描述與設計的溝通語言,以便將後續流程中問題的發現儘可能前置,同時各參與方更加明確變更及測試內容,為QA更好的評估測試範圍提供支撐,進而更好的保證專案測試質量。
- 推動基礎Aaction UT全覆蓋:針對基礎Action,構建單元測試,在Merge程式碼時自動觸發單元測試流水線,輸出執行單測的成功率和覆蓋率,並評定指標基線,保證可持續測試的效率與質量。
- 改進JAR管理工具化與自動化分析及測試:一級Action都集中寫在平臺JAR包中,對類似這種公共JAR包的管理,開發專屬的管理與維護工具,解決升級公共JAR自動化單測覆蓋問題以及每次升級JAR版本需要人工分析人工維護的測試效率問題,打通整合測試自動化的全流程。
5 效果
① 產研效率的提升
系統能力沉澱
- 外賣廣告所有業務線已經完成平臺化架構升級,並在此架構上持續的執行和迭代。
- 業務基礎能力沉澱50+個,模組共用能力沉澱140+個,產品線共用能力沉澱500+個。
人效的提升
研發效率提升:在各業務線平臺化架構遷移後,大的業務迭代20+次,業務迭代效率提升相比之前總計提升28+%。特別是在新業務的接入上,相同功能無需重複開發,提效效果更加明顯:
- 能力累計複用500+次,能力複用比52+%;
- 在新業務接入場景中,Action複用65+%。
- 測試的自動化指標提升:藉助於JAR自動化分析、整合測試及流程覆蓋建設,廣告自動化測試覆蓋率提升了15%,測試提效累計提升28%,自動化綜合得分也有了明顯提升。
② 提升交付質量及賦能產品
- 基於Action的變更以及清晰的視覺化業務鏈路,能夠幫助QA更準確的評估影響範圍,其中過程問題數量及線上問題數量均呈下降趨勢,下降比例約為10%。
- 通過系統能力的視覺化透出頁面,增加系統的透明度,在產品調研階段有效幫助產品瞭解系統已有的能力,減少了業務諮詢、跨產品線知識壁壘等問題(詳情可參見4.4.2.1)。
6 總結與展望
本文分別從標準化、框架、產研新流程3個方面介紹了外賣廣告平臺化在建設與實踐中的思考與落地方案。經過兩年的摸索建設和實踐,美團外賣廣告平臺化已經初具規模、有力地支撐了多條業務線的快速迭代。
未來,平臺化會細化標準化的力度,降低業務開發同學成本;深化框架能力,在穩定性、效能、易用性方面持續進行提升。此外,我們在產研新流程方向也會持續優化使用者體驗,完善運營機制,不斷提升產研迭代的流程。
以上就是外賣廣告針對業務平臺化上的一些探索和實踐,在廣告工程架構等其他領域的探索,敬請期待下一篇系列文章。
7 作者簡介
樂彬、國樑、玉龍、吳亮、磊興、王焜、劉研、思遠等,均來自美團外賣廣告技術團隊。
招聘資訊
美團外賣廣告技術團隊大量崗位持續招聘中,誠招廣告後臺/演算法開發工程師及專家,座標北京。歡迎感興趣的同學加入我們。可投簡歷至:yangguoliang@meituan.com(郵件主題請註明:美團外賣廣告技術團隊)
閱讀美團技術團隊更多技術文章合集
前端 | 演算法 | 後端 | 資料 | 安全 | 運維 | iOS | Android | 測試
| 在公眾號選單欄對話方塊回覆【2021年貨】、【2020年貨】、【2019年貨】、【2018年貨】、【2017年貨】等關鍵詞,可檢視美團技術團隊歷年技術文章合集。
| 本文系美團技術團隊出品,著作權歸屬美團。歡迎出於分享和交流等非商業目的轉載或使用本文內容,敬請註明“內容轉載自美團技術團隊”。本文未經許可,不得進行商業性轉載或者使用。任何商用行為,請傳送郵件至tech@meituan.com申請授權。