B站直播的極速排障建設(shè)-全鏈路Trace追蹤
一、概述
直播業(yè)務(wù)具有實(shí)時(shí)性強(qiáng),復(fù)雜度高,排查鏈路長(zhǎng),影響面大等特征,線上問(wèn)題如果不能立刻排查處理,分分秒秒都在影響用戶的觀看體驗(yàn)、主播的收入。
但各端的問(wèn)題可能都只是表象,例如,一個(gè)看似簡(jiǎn)單的畫(huà)面卡頓問(wèn)題,可能涉及到編碼器配置、網(wǎng)絡(luò)帶寬分配、服務(wù)器負(fù)載等多個(gè)方面,各個(gè)團(tuán)隊(duì)經(jīng)常在等待合作方的反饋,一整套流程下來(lái),一個(gè)線上問(wèn)題的定位可能要消耗掉數(shù)小時(shí)的人力。
我們迫切的需要一套高效的跨端實(shí)時(shí)排障系統(tǒng)!
為此我們采取了以下措施:
- 關(guān)鍵業(yè)務(wù)監(jiān)控:聯(lián)合各協(xié)作方,對(duì)關(guān)鍵業(yè)務(wù)的接口、廣播和核心處理邏輯實(shí)施了實(shí)時(shí)埋點(diǎn)監(jiān)控,并附加了相關(guān)場(chǎng)景信息,確保了問(wèn)題定位的準(zhǔn)確性和全面性。
- 統(tǒng)一追蹤系統(tǒng):為了實(shí)現(xiàn)單個(gè)業(yè)務(wù)鏈路所有埋點(diǎn)的跨端聯(lián)絡(luò),我們?cè)O(shè)計(jì)了統(tǒng)一的trace_id字段,并在數(shù)據(jù)層進(jìn)行串聯(lián),通過(guò)看板直觀展示,極大地提升了問(wèn)題追蹤和定位的效率。
這些措施帶來(lái)了顯著的成效:
- 跨部門(mén)協(xié)作效率提升:通過(guò)實(shí)時(shí)數(shù)據(jù)共享和統(tǒng)一追蹤系統(tǒng),直播移動(dòng)端、PC端、Web端、服務(wù)端以及流媒體等各個(gè)團(tuán)隊(duì)協(xié)作效率大幅提升。在開(kāi)播、視頻連線等9個(gè)核心業(yè)務(wù)的故障排查中,排障率達(dá)到了91%,異常定位的平均時(shí)間從2小時(shí)縮短至僅需5分鐘。兄弟部門(mén)也采納了我們的方案,有效減輕了工作壓力。
- 系統(tǒng)穩(wěn)定性增強(qiáng):這些措施還幫助我們優(yōu)化了開(kāi)播異常斷流、連麥發(fā)布訂閱失敗等多項(xiàng)關(guān)鍵業(yè)務(wù)問(wèn)題,確保了系統(tǒng)的高效運(yùn)行,減少了因技術(shù)問(wèn)題導(dǎo)致的用戶流失。
- 用戶體驗(yàn)改善:我們的快速響應(yīng)和問(wèn)題解決能力極大地提升了主播和用戶的直播體驗(yàn)。用戶和主播的正面反饋絡(luò)繹不絕,間接提高了主播收入穩(wěn)定性,增強(qiáng)了平臺(tái)的吸引力。
二、技術(shù)方案詳解
1. 方案設(shè)計(jì)
圖片
如上圖所示,整體全鏈路排障建設(shè)可以分為數(shù)據(jù)采集、數(shù)據(jù)處理&存儲(chǔ)、可視化工具建設(shè)3大塊。
在開(kāi)始介紹實(shí)現(xiàn)方案之前,需要簡(jiǎn)單介紹一下OpenTracing,它是業(yè)內(nèi)實(shí)現(xiàn)分布式鏈路追蹤系統(tǒng)通常會(huì)采用的方案,我們?cè)诤罄m(xù)的埋點(diǎn)和上報(bào)組件設(shè)計(jì)也對(duì)它進(jìn)行了一些參考。OpenTracing定義了追蹤數(shù)據(jù)所需要的操作和數(shù)據(jù)結(jié)構(gòu),幫助開(kāi)發(fā)人員實(shí)現(xiàn)分布式追蹤的能力。OpenTracing里面有兩個(gè)比較核心的概念,簡(jiǎn)單說(shuō)明一下:
Trace:Trace代表一條追蹤路徑,它由多個(gè)Span組成,存在一個(gè)唯一ID
Span:Span代表追蹤路徑中的一個(gè)時(shí)間跨度,包括操作名稱、開(kāi)始時(shí)間、結(jié)束時(shí)間等信息,由SpanID作為標(biāo)識(shí)。由多個(gè) Span 可以形成一條追蹤路徑。Span還定義了父子、跟隨兩種關(guān)系。在Span上下文中,記錄和維護(hù)了Trace的ID和當(dāng)前Span的ID。
下圖是OpenTracing的模型圖,它描述了由多個(gè)Span組成一條追蹤路徑:
圖片
OpenTracing在服務(wù)端得到了廣泛的使用,但是面臨客戶端業(yè)務(wù)現(xiàn)狀和問(wèn)題,我們調(diào)整了最終方案的實(shí)現(xiàn)方式
- 直播場(chǎng)景用戶行為一般都是即時(shí)操作,時(shí)間片段的設(shè)計(jì)并太不合適
- OpenTracing是跨編程語(yǔ)言的標(biāo)準(zhǔn),一些API的設(shè)計(jì)比較抽象,在業(yè)務(wù)中使用不友好
考慮到這些,我們借鑒了OpenTracing中核心的概念:trace_id和事件上下文,并簡(jiǎn)化了OpenTracing中Span的概念,嘗試復(fù)用端上已有的埋點(diǎn),擴(kuò)展字段來(lái)實(shí)現(xiàn)全鏈路Trace的能力。下面我們開(kāi)始介紹。
首先要確認(rèn)的是必須上報(bào)的埋點(diǎn)字段。為了減輕理解和上報(bào)的成本,在設(shè)計(jì)上我們希望盡可能簡(jiǎn)單。實(shí)際上,這些字段的設(shè)計(jì)也是為了解決幾個(gè)關(guān)鍵的問(wèn)題:
1.1 如何將各端的埋點(diǎn)關(guān)聯(lián)起來(lái)?
trace_id:一個(gè)復(fù)雜的事件鏈路往往并不是單端閉環(huán)的。拿邀請(qǐng)上麥舉例,它涉及到主播客戶端A -> 業(yè)務(wù)服務(wù) -> 廣播服務(wù) -> 觀眾客戶端B -> RTC。我們希望將這一次事件鏈路中相關(guān)的日志都能聚合起來(lái)呈現(xiàn),而不是各端查各端的。為了解決這個(gè)問(wèn)題,我們生成一個(gè)全鏈路都會(huì)透?jìng)鞯奈ㄒ籌D,在每個(gè)端的上報(bào)中都會(huì)攜帶這個(gè)ID,然后通過(guò)這個(gè)ID,把這次事件關(guān)聯(lián)的上報(bào)檢索出來(lái),一起展示。
1.2 如何解決日志中缺失的上下文?
extends:我們?cè)谏蠄?bào)中增加了擴(kuò)展數(shù)據(jù),用于攜帶上下文以及自己關(guān)心的信息,同時(shí)在可視化工具中展示出來(lái)。
1.3 如何快速的找到異常的環(huán)節(jié)?
level:我們給每一個(gè)上報(bào)定義了3種狀態(tài),正常、警告和異常。在可視化工具中,針對(duì)警告和異常狀態(tài)的上報(bào),用黃色和紅色展示出來(lái),這樣可以第一時(shí)間定位到出現(xiàn)問(wèn)題的地方,找到負(fù)責(zé)的端和同學(xué)。
1.4 如何衡量這次的事件是否正常?
type:我們給每一個(gè)上報(bào)節(jié)點(diǎn)定義了3種類(lèi)型,起始、過(guò)程和結(jié)束。一次事件執(zhí)行中,會(huì)有一個(gè)起始節(jié)點(diǎn),一個(gè)結(jié)束節(jié)點(diǎn)和多個(gè)過(guò)程節(jié)點(diǎn)。如果這次事件執(zhí)行鏈路里面,有結(jié)束節(jié)點(diǎn),并且所有節(jié)點(diǎn)的狀態(tài)都是正常的,那我們就認(rèn)為這次事件執(zhí)行是正常,否則就是異常的
到這里,最主要的埋點(diǎn)字段就介紹完了,接下來(lái)就只需要各端在關(guān)鍵路徑上添加上報(bào)即可。
在上線驗(yàn)證階段,移動(dòng)端先通過(guò)透?jìng)鱰race_id的方式快速上線并打通了整條鏈路,驗(yàn)證了可行性。但是這種方式弊端很大,代碼入侵嚴(yán)重而且健壯性差,對(duì)于業(yè)務(wù)同學(xué)來(lái)說(shuō)這是非常勸退的,所以我們針對(duì)上報(bào)組件做了一些設(shè)計(jì),目的是降低接入和維護(hù)成本,減少代碼入侵。
2. 上報(bào)組件設(shè)計(jì)
上報(bào)組件隨項(xiàng)目發(fā)展共迭代了三個(gè)版本,每一版都比上一版更加易用和完善。下面介紹我們的迭代過(guò)程,共分為“快速驗(yàn)證可行性”、“大幅提升易用性” 和 “繼續(xù)增強(qiáng)魯棒性”。
2.1 快速驗(yàn)證可行性
在項(xiàng)目初期,為了快速驗(yàn)證鏈路可行性,上報(bào)組件未做過(guò)多設(shè)計(jì),僅實(shí)現(xiàn)了最基礎(chǔ)的功能:
將上述的基礎(chǔ)字段(trace_id, level, type等)、業(yè)務(wù)方自定義參數(shù)以及公共參數(shù)(房間信息、網(wǎng)絡(luò)、推流、設(shè)備、外設(shè)情況、線程id等)進(jìn)行上報(bào)。
在最初版中,我們將所有的非公共參數(shù)都寫(xiě)到了函數(shù)入?yún)⒅?,并在業(yè)務(wù)層透?jìng)髁藅race_id。如圖所示:
圖片
2.2 大幅提升易用性
2.2.1 快速方案遇到的問(wèn)題
基礎(chǔ)功能上線后,驗(yàn)證了我們通過(guò)trace_id串聯(lián)起多個(gè)埋點(diǎn)的想法是可行的。隨著越來(lái)越多的業(yè)務(wù)接入,顯而易見(jiàn)的兩個(gè)問(wèn)題便浮上水面:
- 上報(bào)代碼過(guò)于繁雜:由于需要8個(gè)參數(shù),需要多行代碼才能完成一次上報(bào),在業(yè)務(wù)代碼中插入這一塊又一塊的和業(yè)務(wù)無(wú)關(guān)的代碼,會(huì)嚴(yán)重降低可讀性和可維護(hù)性;
- 業(yè)務(wù)入侵性大:在低耦合的代碼架構(gòu)下,一個(gè)功能點(diǎn)的實(shí)現(xiàn)經(jīng)常橫跨1~3個(gè)模塊、縱深5~10層方法調(diào)用,想要做到精確的全鏈路追蹤,勢(shì)必要將trace_id透?jìng)鳎@樣就需要在每個(gè)方法的入?yún)⒍荚黾右粋€(gè)trace_id的參數(shù),不僅寫(xiě)起來(lái)麻煩,還對(duì)業(yè)務(wù)的入侵性巨大;
這里舉兩個(gè)實(shí)際的代碼例子:
圖片
圖片
接下來(lái)我們就這兩個(gè)問(wèn)題對(duì)埋點(diǎn)組件進(jìn)行優(yōu)化:
2.2.2 解決埋點(diǎn)代碼冗長(zhǎng)
需要在業(yè)務(wù)層和上報(bào)層中間插入一個(gè)埋點(diǎn)聚合層,負(fù)責(zé)組裝參數(shù),并針對(duì)每一個(gè)節(jié)點(diǎn)向外提供一個(gè)簡(jiǎn)明的方法,在業(yè)務(wù)層就只需要一行簡(jiǎn)短的代碼就可以完成上報(bào)了。
在埋點(diǎn)聚合層中,我們也做了一些簡(jiǎn)單的設(shè)計(jì),旨在減少業(yè)務(wù)方的代碼量:
- 盡力減少上報(bào)方法傳參的數(shù)量,將需要的參數(shù)都封裝進(jìn)一個(gè)事件模型類(lèi)中,并針對(duì)起始節(jié)點(diǎn)提供便利構(gòu)造方法。且將入?yún)ode_type、trace_id、level、extends字段加上默認(rèn)值,這樣對(duì)于大部分的節(jié)點(diǎn),就不再需要攜帶所有的參數(shù)了。
- 針對(duì)每個(gè)業(yè)務(wù)類(lèi)型都額外做了一層封裝,這樣就不用每次都填寫(xiě)event_type字段了,進(jìn)一步的減少了代碼量。至此,對(duì)于普通節(jié)點(diǎn),甚至只需要指定key和log兩個(gè)字段就可以完成上報(bào)了。
下面是解決第一個(gè)問(wèn)題(埋點(diǎn)代碼冗長(zhǎng))的簡(jiǎn)單圖示:
2.2.3 解決業(yè)務(wù)入侵性
業(yè)內(nèi)流行使用插樁的方式來(lái)進(jìn)行非入侵式的埋點(diǎn),但切面的形式很難獲取業(yè)務(wù)上下文,無(wú)法解決方法A調(diào)用方法B的trace_id透?jìng)鲉?wèn)題,因此并不適用于這個(gè)場(chǎng)景。
對(duì)于trace_id透?jìng)鞯膯?wèn)題,解決方案是把trace_id緩存一下。但是需要解決以下場(chǎng)景的問(wèn)題:
- 多線程并發(fā):并行啟動(dòng)了多次同一個(gè)事件,且他們的完成時(shí)間也不固定,如同時(shí)上傳了多張大小不一的封面。
- 事件中斷:前一次事件因?yàn)槟承┰蛑袛嗔?,永遠(yuǎn)的停留在了某個(gè)節(jié)點(diǎn)。
為此,我們首先引入狀態(tài)機(jī)的概念,將所有節(jié)點(diǎn)使用有向圖進(jìn)行表示,這樣我們便能清晰的感知到事件的發(fā)生到結(jié)束,以及某個(gè)節(jié)點(diǎn)后續(xù)可以流轉(zhuǎn)至哪些節(jié)點(diǎn)、是否發(fā)生錯(cuò)誤中斷。
圖片
下面舉一個(gè)具體的例子,上麥流程圖和其對(duì)應(yīng)的有向圖:
圖片
圖中使用了虛實(shí)線來(lái)區(qū)分跨端或者跨線程的動(dòng)作,其必要性可參見(jiàn)下文第(2)點(diǎn)。
(1)自動(dòng)化尋找trace_id
當(dāng)一個(gè)起始節(jié)點(diǎn)準(zhǔn)備上報(bào)時(shí):
- 為其創(chuàng)建一個(gè)上報(bào)實(shí)例
- 在實(shí)例中記錄trace_id、當(dāng)前的節(jié)點(diǎn),以及它之后可能會(huì)流轉(zhuǎn)到的節(jié)點(diǎn)
- 將這個(gè)實(shí)例扔到池子中
當(dāng)一個(gè)非起始節(jié)點(diǎn)準(zhǔn)備上報(bào)時(shí):
- 組件會(huì)根據(jù)有向圖去池子中查找需要流轉(zhuǎn)到的節(jié)點(diǎn)
- 使用實(shí)例的trace_id進(jìn)行上報(bào)
- 更新實(shí)例的時(shí)間戳
- 將實(shí)例流轉(zhuǎn)到下一個(gè)節(jié)點(diǎn),若無(wú)后續(xù)節(jié)點(diǎn),則移除實(shí)例
(2)解決多線程問(wèn)題
這個(gè)方案似乎很完美,但從B到E是一個(gè)網(wǎng)絡(luò)接口請(qǐng)求,如果先后很快地發(fā)出了兩次請(qǐng)求,很可能會(huì)出現(xiàn)下面的情況:
圖片
如果遵照上述的方案,B和E之間就會(huì)被錯(cuò)誤的關(guān)聯(lián)起來(lái)。為了規(guī)避這個(gè)問(wèn)題,trace_id會(huì)在跨線程時(shí)從埋點(diǎn)組件中外拋,需要業(yè)務(wù)方短暫記錄,并傳遞到下一個(gè)節(jié)點(diǎn)。當(dāng)然,針對(duì)常用的網(wǎng)絡(luò)請(qǐng)求,我們也做了易用的封裝,詳見(jiàn)2.3.1。
于是之前上麥的有向圖會(huì)變?yōu)椋?/p>
圖片
在多線程問(wèn)題解決之后,當(dāng)一個(gè)含有trace_id的節(jié)點(diǎn)進(jìn)入上報(bào)組件時(shí),即會(huì)為其創(chuàng)建一個(gè)上報(bào)實(shí)例,按上文記錄字段后放到池子里。
至于無(wú)trace_id的上報(bào)則完全相同,唯一的區(qū)別就是在查找節(jié)點(diǎn)時(shí),加上線程id的校驗(yàn),這樣可以防止同一事件在不同的線程中同時(shí)啟動(dòng)。
在trace_id已經(jīng)被自動(dòng)化后,整體的上報(bào)流程圖如下:
圖片
2.2.4 提升易用性后的代碼架構(gòu)
圖片
2.3 繼續(xù)增強(qiáng)魯棒性
2.3.1 對(duì)“接口與廣播”的封裝
在端上遇到的跨線程/跨端場(chǎng)景絕大多數(shù)都是網(wǎng)絡(luò)請(qǐng)求和廣播,因此,為了避免出錯(cuò)和降低復(fù)雜度,我們做了一套易用的封裝:
1. 網(wǎng)絡(luò)請(qǐng)求
(1)組件會(huì)在發(fā)起指定請(qǐng)求之前通過(guò)有向圖自動(dòng)尋路獲取此次trace_id
(2)使用該trace_id上報(bào)請(qǐng)求事件,并會(huì)自動(dòng)帶上所有的業(yè)務(wù)請(qǐng)求參數(shù)
(3)將trace_id置于請(qǐng)求頭,用于串聯(lián)服務(wù)端節(jié)點(diǎn)
(4)在接口返回后,自動(dòng)上報(bào)響應(yīng)數(shù)據(jù),若接口錯(cuò)誤,會(huì)將該節(jié)點(diǎn)標(biāo)記為error
2. 廣播
(1)組件會(huì)在指定廣播到達(dá)時(shí),嘗試去獲取trace_id字段。
(2)若獲取成功,則自動(dòng)進(jìn)行上報(bào),若沒(méi)有,則會(huì)走自動(dòng)化上報(bào)流程。
這樣,業(yè)務(wù)方即使遇到跨線程/跨端,也無(wú)需關(guān)心trace_id了,在這兩種場(chǎng)景下徹底做到了業(yè)務(wù)無(wú)感。
2.3.2 對(duì)“抗風(fēng)險(xiǎn)能力”的補(bǔ)足
當(dāng)業(yè)務(wù)鏈路與對(duì)應(yīng)的有向圖不符時(shí),trace_id的自動(dòng)化管理便會(huì)失效。為此我們?cè)O(shè)計(jì)了特殊異常case的監(jiān)控:
1. 事件跟蹤上下文丟失
自動(dòng)化尋找trace_id失敗,此種情況多發(fā)生在上報(bào)的埋點(diǎn)與有向圖描述不符,此時(shí)埋點(diǎn)組件會(huì)上報(bào)一個(gè)警告埋點(diǎn)觸達(dá)開(kāi)發(fā)及時(shí)修改鏈路。
2. 事件跟蹤超時(shí)
即上次單線程的流轉(zhuǎn)還未結(jié)束,新的節(jié)點(diǎn)就已到達(dá)。此種情況多發(fā)生在流轉(zhuǎn)過(guò)程被意外打斷,如check失敗后直接return。此時(shí)原事件在看板中會(huì)表現(xiàn)為鏈路中斷的錯(cuò)誤。同樣的,會(huì)上報(bào)一個(gè)警告埋點(diǎn)。
2.4 上報(bào)組件整體概覽
圖片
3. 數(shù)據(jù)處理和存儲(chǔ)
在處理上報(bào)的海量數(shù)據(jù)時(shí),需要清洗掉錯(cuò)誤的數(shù)據(jù),并解析各個(gè)終端上報(bào)的不同數(shù)據(jù)結(jié)構(gòu),轉(zhuǎn)化為統(tǒng)一數(shù)據(jù)模型。由于數(shù)據(jù)是逐條上報(bào)的,必須將這些離散數(shù)據(jù)串聯(lián)成完整的事件鏈路,這樣就知道用戶操作了什么、經(jīng)歷了哪些端、哪些節(jié)點(diǎn)出了問(wèn)題或漏了哪些節(jié)點(diǎn)。
3.1 事件串聯(lián)
(1)單trace_id串聯(lián):事件由唯一的trace_id串聯(lián)整個(gè)流程。
(2)多trace_id串聯(lián):事件由多端各自的trace_id組合而成。
為兼容業(yè)務(wù)服務(wù)和廣播服務(wù)各自獨(dú)立的trace追蹤系統(tǒng),我們實(shí)現(xiàn)了一套多重映射算法,且無(wú)縫兼容了單trace_id方案,最終溯源成事件開(kāi)始的原始trace_id。
圖片
3.2 數(shù)據(jù)清洗與存儲(chǔ)
為應(yīng)對(duì)直播的實(shí)時(shí)性要求,我們采用流計(jì)算技術(shù)。先快速篩選出trace相關(guān)事件,再清洗掉異常數(shù)據(jù),在單次流計(jì)算執(zhí)行過(guò)程中進(jìn)行映射建立關(guān)系并落表存儲(chǔ),實(shí)現(xiàn)小于5min級(jí)別的數(shù)據(jù)響應(yīng)處理。數(shù)據(jù)表支持靈活的定制化查詢和分析需求。
圖片
4. 數(shù)據(jù)可視化
可視化簡(jiǎn)化了查詢過(guò)程,能快速準(zhǔn)確地捕捉異常和關(guān)鍵信息。適用于開(kāi)發(fā)、測(cè)試、產(chǎn)品、運(yùn)營(yíng)、客服等角色。
4.1 覆蓋場(chǎng)景
從App啟動(dòng)到退出,從開(kāi)播到關(guān)播,從上麥到下麥,從PK發(fā)起到結(jié)束等關(guān)鍵業(yè)務(wù)場(chǎng)景。
圖片
4.2 落地效果
在日常業(yè)務(wù)中,已經(jīng)有效解決了很多實(shí)際問(wèn)題,以下是遇到的一些案例查詢:
圖片
三、結(jié)束語(yǔ)
不知何時(shí)起,“用關(guān)鍵鏈路查一下”已經(jīng)成了身邊同事常說(shuō)的口頭禪,整套排障體系的價(jià)值,也得到了驗(yàn)證。
未來(lái)還有許多要做的事,我們將致力于拓展業(yè)務(wù)覆蓋面、建立業(yè)務(wù)健康度監(jiān)控體系、提升上下文信息的有效性等。
我們相信,通過(guò)不斷的技術(shù)創(chuàng)新和服務(wù)優(yōu)化,我們的業(yè)務(wù)能夠迎接更大的挑戰(zhàn),為用戶創(chuàng)造更大的價(jià)值。