百度搜索exgraph圖執(zhí)行引擎設(shè)計(jì)重點(diǎn)分成三個(gè)部分:圖描述語(yǔ)言、圖執(zhí)行引擎、對(duì)接擴(kuò)展。 圖描述語(yǔ)言是一種基于文本可讀的圖描述語(yǔ)言,用于描述任務(wù)中的算子以及算子之間的依賴(lài)關(guān)系,即讓人可以理解,也可以被計(jì)算機(jī)理解并執(zhí)行。 圖執(zhí)行引擎是exgraph的核心,負(fù)責(zé)根據(jù)圖描述語(yǔ)言生成的圖語(yǔ)法樹(shù)進(jìn)行高效執(zhí)行。它支持如串行、并行、中斷、選擇等范式,以滿(mǎn)足不同場(chǎng)景下的需求。 對(duì)接擴(kuò)展則提供了與其他協(xié)議框架的接口,方便用戶(hù)將exgraph集成到現(xiàn)有的系統(tǒng)中。 總之,exgraph圖執(zhí)行引擎設(shè)計(jì)的目標(biāo)是實(shí)現(xiàn)高效、靈活的任務(wù)編排,以滿(mǎn)足復(fù)雜邏輯處理需求。
01
背景
搜索展現(xiàn)架構(gòu)承載模版選擇、實(shí)時(shí)摘要補(bǔ)充、展現(xiàn)數(shù)據(jù)適配、結(jié)果渲染等職責(zé),當(dāng)前由PHP開(kāi)發(fā)、HHVM執(zhí)行,對(duì)接數(shù)十個(gè)產(chǎn)品線(xiàn),數(shù)百個(gè)精細(xì)化的展現(xiàn)策略由100+RD共同開(kāi)發(fā)。隨著搜索業(yè)務(wù)產(chǎn)品日益復(fù)雜和生成式大模型產(chǎn)品開(kāi)發(fā)需要,展現(xiàn)架構(gòu)面臨以下難題:
1、HHVM基礎(chǔ)設(shè)施停止維護(hù),且不支持異步并行支持,架構(gòu)升級(jí)難度大;
2、歷史累計(jì)的多個(gè)展現(xiàn)策略框架分布在各個(gè)階段,且各自參數(shù)不同,研發(fā)難度大。
通過(guò)調(diào)研,了解到DAG有向無(wú)環(huán)圖,將DAG圖中頂點(diǎn)描述為業(yè)務(wù)拆分后的一個(gè)個(gè)算子,邊及其方向作為執(zhí)行順序,一對(duì)一作為串行執(zhí)行,一對(duì)多作為并發(fā)執(zhí)行,即使是很復(fù)雜的業(yè)務(wù)也可以用這套邏輯進(jìn)行表達(dá)。且代碼實(shí)現(xiàn)較簡(jiǎn)單,還能用graphviz將DAG圖生成圖片,將整個(gè)邏輯可視化。
△算子化后的邏輯執(zhí)行視圖
好像很完美~~
但似乎還有些問(wèn)題:
1、對(duì)于簡(jiǎn)單邏輯,DAG圖不復(fù)雜,用graphviz構(gòu)建圖也很簡(jiǎn)單,但一旦頂點(diǎn)數(shù)量爆發(fā),可閱讀性急速下降。而不幸的是,搜索的PHP模塊幾百個(gè)策略,如果遷移進(jìn)來(lái),預(yù)計(jì)會(huì)有幾百個(gè)頂點(diǎn),構(gòu)建這個(gè)圖以及這個(gè)圖的可讀性,依然很差;
2、簡(jiǎn)單意味著功能弱。
比如搜索有多種版式:手百內(nèi)、手百外、純NA渲染等,下游頂點(diǎn)根據(jù)上游頂點(diǎn)的執(zhí)行結(jié)果來(lái)選擇不同的版式渲染。這種場(chǎng)景下只能呆呆的在每個(gè)版式頂點(diǎn)內(nèi)自行判斷是否執(zhí)行,而不能由上游頂點(diǎn)直接選擇一個(gè)版式分支執(zhí)行。
比如執(zhí)行到某個(gè)頂點(diǎn),發(fā)現(xiàn)后續(xù)不用執(zhí)行了,邏輯執(zhí)行沒(méi)有好的退場(chǎng)機(jī)制。
各個(gè)算子間傳遞數(shù)據(jù)怎么處理。
...
02
圖執(zhí)行引擎
DAG能滿(mǎn)足大多數(shù)場(chǎng)景的需要,但依然不夠。所以搜索設(shè)計(jì)了一套超集于DAG的圖描述,并在這個(gè)描述上,添加邏輯執(zhí)行的高級(jí)功能,與web框架進(jìn)行融合,逐步誕生了exgraph圖執(zhí)行引擎。
exgraph圖執(zhí)行引擎設(shè)計(jì)重點(diǎn)分成兩個(gè)三個(gè)部分:圖描述語(yǔ)言、圖執(zhí)行引擎、對(duì)接擴(kuò)展(用來(lái)對(duì)接協(xié)議框架)。
? ?
2.1 圖描述語(yǔ)言
2.1.1 核心語(yǔ)法
算子:業(yè)務(wù)執(zhí)行的最小單位,通常一個(gè)單詞就是一個(gè)算子(語(yǔ)法單獨(dú)定義的關(guān)鍵詞除外)。
串行組:即兩個(gè)算子按照順序執(zhí)行,在圖上表示為用箭頭連接:
△串行組
并發(fā)組:即多個(gè)算子并發(fā)的執(zhí)行,在圖上用中括號(hào)?[]?包圍:
△并發(fā)組
屬性:圖上所有用大括號(hào)?{}?包圍的,都是屬性。屬性用于通過(guò)圖描述傳遞參數(shù)給代碼。
△屬性
算子、串行組、并發(fā)組都是一個(gè)執(zhí)行單元,意味著,他們可以互相包含(算子是最小的執(zhí)行單元,不能包含別的執(zhí)行單元)。比如:
△互相包含
上面的這個(gè)描述,用人話(huà)說(shuō)就是:
1、執(zhí)行a算子
2、并發(fā)地:
執(zhí)行b算子,
執(zhí)行c算子,然后執(zhí)行d算子,然后執(zhí)行e算子
執(zhí)行f算子,然后再并發(fā)地執(zhí)行g(shù)算子和h算子
3、最后再執(zhí)行i算子
子圖:主圖支持通過(guò)文件引入的方式,引入另一個(gè)圖嵌入到主圖
△主圖引入sub_graph子圖
通過(guò)上面簡(jiǎn)單的介紹,你已經(jīng)掌握幾乎全部圖描述語(yǔ)言語(yǔ)法了,可以開(kāi)始思考,將自己所負(fù)責(zé)的業(yè)務(wù)如何用圖進(jìn)行描述了。
另外,為了更好的適配業(yè)務(wù)場(chǎng)景,exgraph還設(shè)計(jì)了幾種指令來(lái)處理特殊場(chǎng)景。
擴(kuò)展指令
START指令:圖開(kāi)始的標(biāo)記,用做給圖設(shè)置屬性。
△START指令
目前START指令用來(lái)指導(dǎo)創(chuàng)建HTTP的handler,直接讓圖引擎承接http處理、streaming rpc處理請(qǐng)求。
MIDWARE指令:包裝含義。
△MIDWARE指令
可以在執(zhí)行c算子前,先執(zhí)行b算子,并控制是否執(zhí)行c算子;也可以在執(zhí)行c算子前后,執(zhí)行一些通用的邏輯。
SWITCH指令:選擇執(zhí)行分支。
△SWITCH指令
可以在?switch_pc_or_wise?算子內(nèi),選擇執(zhí)行哪個(gè)分支。
基于圖描述語(yǔ)言,用純文本的方式就可以將業(yè)務(wù)整體描述,很好的解決了DAG圖構(gòu)圖復(fù)雜性問(wèn)題,并允許自定義一些高級(jí)用法。
2.2圖執(zhí)行引擎
上面介紹的圖描述語(yǔ)言,讓“人”可以更加簡(jiǎn)單的方式了解到程序的執(zhí)行流程,但也僅僅只是個(gè)描述而已。
如何讓其按照我們?cè)O(shè)定的描述將邏輯跑起來(lái)呢?
首先介紹一個(gè)重要的、執(zhí)行單元必須實(shí)現(xiàn)的接口:
type Job interface{ DoImpl(*engine.Context) error }
其中*Context負(fù)責(zé)傳遞所有信息到各個(gè)算子,提供:算子選項(xiàng)(算子{}附帶的內(nèi)容)內(nèi)容獲取、數(shù)據(jù)傳遞等功能。
在上面的章節(jié)中講到算子、串行組、并發(fā)組都是一個(gè)執(zhí)行單元,其實(shí)就是說(shuō),它們都實(shí)現(xiàn)了Job接口。
exgraph圖執(zhí)行引擎是:將圖解析后的語(yǔ)法樹(shù)作為入?yún)ⅲ钆淙炙阕幼?cè),讓算子按照預(yù)定的規(guī)則執(zhí)行起來(lái)。
它的執(zhí)行過(guò)程近似于:
em~~,簡(jiǎn)單的有點(diǎn)像把大象放冰箱的過(guò)程,但實(shí)際遠(yuǎn)不止如此。
想一下,如果你執(zhí)行到a算子,發(fā)現(xiàn)沒(méi)有必要執(zhí)行b算子了,怎么辦?又或者a有數(shù)據(jù)要傳遞到b算子,怎么辦?
2.2.1 對(duì)象容器
exgraph中實(shí)現(xiàn)了一個(gè)并發(fā)安全的對(duì)象容器,用戶(hù)可以通過(guò)*engine.Context提供的接口,方便的設(shè)置和獲取對(duì)象,就像這樣:
type a struct {} func (o *a) DoImpl(ctx *Context) error { // 算子a,設(shè)置對(duì)象 var a int = 2023 ctx.RegisterInstance(&a) return nil } type b struct {} func (o *b) DoImpl(ctx *Context) error { var a int // 通過(guò)類(lèi)型獲取值 ctx.MutableInstance(&a) // 打印2023 fmt.Println(a) return nil }
對(duì)象容器再存入時(shí),將其類(lèi)型作為標(biāo)識(shí)符,取值時(shí)也通過(guò)相同類(lèi)型的變量,通過(guò)反射賦值。
2.2.2依賴(lài)注入和對(duì)象導(dǎo)出
有了對(duì)象容器,exgraph設(shè)計(jì)了支持基于struct tag的對(duì)象依賴(lài)注入和導(dǎo)出功能,且采用腳本生成代碼的方式實(shí)現(xiàn):
type Operator struct { http.Request `inject:""` http.Response `inject:"canLost=true,canNil=true"` *Userinfo `extract:"canNil=true"` } type UserInfo struct { Name string } func (o *Operator) DoImpl(engine.Context) error { // 通過(guò)inject,算子內(nèi)可以直接獲取到Request對(duì)象 if v, ok := o.Request.Header.Get("xx"); ok { // do something } return nil }
利用struct tag和生成的代碼,用戶(hù)在使用算子時(shí),實(shí)現(xiàn)了以下功能:
1、inject tag可以直接通過(guò)算子屬性獲取對(duì)象,省去了繁瑣的取值過(guò)程,并支持:canLost=true表示允許對(duì)象不存在,canNil=true表示循序?qū)ο笾禐閚il。
2、extract tag則允許用戶(hù)直接賦值為算子屬性,由生成的代碼賦值將對(duì)象導(dǎo)出到對(duì)象容器中,且支持:canNil=true表示允許導(dǎo)出對(duì)象值為nil,repace=true表示允許替換對(duì)象。
2.2.3 中斷和跳過(guò)
為方便程序邏輯執(zhí)行,exgraph內(nèi)置了幾種中斷跳過(guò)邏輯:
1、全局錯(cuò)誤中斷
type a struct {} func (o *a) DoImpl(ctx *Context) error { // 模擬業(yè)務(wù)執(zhí)行遇到了不可兜底的錯(cuò)誤 err := errors.New("fatal error") // 調(diào)用Abort函數(shù)即可中斷整個(gè)圖執(zhí)行引擎 ctx.Abort(err) return nil }
2、全局正常中斷
type a struct {} func (o *a) DoImpl(ctx *Context) error { // 發(fā)現(xiàn)沒(méi)必要走后面的邏輯 // 直接中斷整個(gè)圖執(zhí)行引擎 ctx.Exit() return nil }
3、跳過(guò)串行組
type a struct {} func (o *a) DoImpl (ctx *Context) error { // a算子執(zhí)行跳過(guò)`a -> b`這個(gè)子集串行組 // 即b算子不再執(zhí)行,但c算子正常執(zhí)行 ctx.SkipSerialGroup() return nil }
2.3執(zhí)行優(yōu)化
exgraph執(zhí)行的一個(gè)聲明周期內(nèi),大部分對(duì)象都允許池化。
2.3.1對(duì)象池
對(duì)于算子:exgraph內(nèi)部對(duì)每個(gè)注冊(cè)的算子,都是注冊(cè)到一個(gè)sync.Pool中,算子對(duì)象在執(zhí)行完成后,執(zhí)行reset后返回到對(duì)象池內(nèi)。
對(duì)于放入對(duì)象容器的對(duì)象:在exgraph執(zhí)行引擎結(jié)束時(shí),會(huì)循環(huán)對(duì)每個(gè)對(duì)象檢測(cè)是否實(shí)現(xiàn)了Release接口,如果實(shí)現(xiàn)接口就會(huì)調(diào)用,用戶(hù)就可以在Release時(shí)將對(duì)象reset后返回對(duì)象池內(nèi)。
2.3.2其他優(yōu)化
exgraph在執(zhí)行每個(gè)算子時(shí)默認(rèn)在當(dāng)前goroutine執(zhí)行,除非用戶(hù)顯示的給算子設(shè)置了超時(shí)時(shí)間a{timeout="1s"}。
依賴(lài)注入和對(duì)象導(dǎo)出,是基于腳本生成代碼的,而非反射。
03
場(chǎng)景案例
3.1 同路徑不同邏輯
背景:搜索PC和wise(移動(dòng)端)同模塊執(zhí)行,檢索路徑都為/s
方案:可以用SWITCH選擇模式,通過(guò)一個(gè)算子來(lái)判斷使用哪個(gè)分支:
3.2PHP策略遷移Go
背景:搜索展現(xiàn)架構(gòu)當(dāng)前逐步由PHP遷移到Go。在過(guò)渡期,PHP代碼遷移到Go之后,需要通過(guò)抽樣驗(yàn)證Go代碼邏輯無(wú)誤,即:命中抽樣,執(zhí)行Go代碼,否則執(zhí)行PHP代碼。而且需要遷移的PHP策略很多,如果沒(méi)有統(tǒng)一的機(jī)制來(lái)支持,成本很高。
方案:用MIDWARE指令,用CommonDealPhpOrGoStrategy算子作為判斷包裝,判斷命中抽樣時(shí),允許執(zhí)行DemoStrategy1算子,并帶標(biāo)識(shí)到PHP,不執(zhí)行PHP相應(yīng)邏輯。
否則不執(zhí)行DemoStrategy1而執(zhí)行PHP相應(yīng)邏輯。
關(guān)鍵的是,遷移后的Go算子都不需要做特殊處理,正常遷移代碼加上MIDWARE就能支持以上功能。
審核編輯:劉清
-
RPC
+關(guān)注
關(guān)注
0文章
111瀏覽量
11572 -
PHP
+關(guān)注
關(guān)注
0文章
454瀏覽量
26785 -
DAG
+關(guān)注
關(guān)注
0文章
17瀏覽量
8193
原文標(biāo)題:百度搜索exgraph圖執(zhí)行引擎設(shè)計(jì)與實(shí)踐
文章出處:【微信號(hào):OSC開(kāi)源社區(qū),微信公眾號(hào):OSC開(kāi)源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論