字節(jié)跳動大規(guī)模埋點數(shù)據(jù)治理優(yōu)秀實踐
本文整理自火山引擎開發(fā)者社區(qū) Meetup 第四期演講,主要介紹了字節(jié)跳動流量平臺的埋點內(nèi)容解決方案和埋點鏈路解決方案,揭秘了流量平臺如何支撐起字節(jié)跳動萬億+的實時數(shù)據(jù)處理。
首先我們定義一下埋點是什么?埋點主要是描述用戶在 APP 內(nèi)觸發(fā)的一系列行為,包括點擊、側(cè)滑等?;谶@些行為,我們可以進行行為分析、個性化推薦、精準營銷等很多事情。埋點主要描述的是哪些數(shù)據(jù)?
- Who:誰操作的數(shù)據(jù)
- When:什么時候操作的數(shù)據(jù)
- Where:在哪些頁面、模塊的數(shù)據(jù)
- How:用戶如何操作的
- What:有哪些附加信息
因為本文介紹的是埋點治理,所以這里再介紹一下什么是數(shù)據(jù)治理。數(shù)據(jù)治理是指在數(shù)據(jù)的生命周期內(nèi),對其進行管理的原則性方法,其目標是為了確保數(shù)據(jù)的安全、及時、準確、可用和易用。數(shù)據(jù)總是會變得無效甚至無用,因此就涉及到對存量數(shù)據(jù)的治理。但這里要強調(diào)一下,數(shù)據(jù)治理不只針對存量數(shù)據(jù),更重要的是對增量數(shù)據(jù)的治理,通過一系列手段,能保證數(shù)據(jù)從源頭開始就是正確的。此外,所有的治理都有具體的落地內(nèi)容,一個穩(wěn)定的治理鏈路是所有數(shù)據(jù)治理的基石。
下面就為大家介紹字節(jié)跳動是如何治理埋點數(shù)據(jù)的。
字節(jié)跳動流量平臺
流量平臺是字節(jié)跳動內(nèi)部統(tǒng)一的埋點平臺,覆蓋埋點數(shù)據(jù)定義、采集、生產(chǎn)、應(yīng)用、治理等埋點全生命周期。當前,流量平臺已經(jīng)覆蓋了 2000 多個應(yīng)用,管理埋點(事件)數(shù) 20 萬,每天產(chǎn)生的埋點數(shù)據(jù)量超過萬億,每年能給公司節(jié)省的成本超億元。
上圖是字節(jié)跳動流量平臺的產(chǎn)品概念圖,可以看到流量平臺主要分為幾塊:
- 埋點內(nèi)容:這是用戶接觸最多的一塊,包括埋點生命周期的設(shè)計、開發(fā)、驗證、上線、使用、乃至下線。
- 埋點治理:指的是狹義的對存量數(shù)據(jù)的治理。涉及成本、SLA 等的治理。
- 鏈路側(cè):包含埋點的收集、處理和訂閱的全鏈路,平臺目前支持對 iOS、Android 等全端數(shù)據(jù)的收集。平臺已經(jīng)跟下游使用流量數(shù)據(jù)的應(yīng)用進行打通,用戶可以訂閱數(shù)據(jù)。
- 鏈路根基:即自研的動態(tài)實時計算平臺,也是整個平臺的核心技術(shù),它能夠支撐起字節(jié)跳動萬億+的實時數(shù)據(jù)的處理。
埋點內(nèi)容解決方案
埋點內(nèi)容主要管理埋點生命周期,這里要著重強調(diào)一下上圖中心位置的埋點模型其實非常重要,因為埋點模型設(shè)計的好壞直接影響到埋點的設(shè)計、開發(fā)、測試甚至使用。
埋點內(nèi)容用戶痛點
埋點內(nèi)容的用戶主要是有兩大類:埋點消費者和埋點生產(chǎn)者。對于埋點消費者來說,存在如下痛點:
- 查找難度大:埋點數(shù)量非常多,找不到自己想要的埋點。
- 使用難度高:找到埋點之后,用戶也很可能不清楚指標埋點口徑。
- 埋點難信任:當數(shù)據(jù)不符合預(yù)期時,不確定埋點數(shù)據(jù)是否可用。
對于埋點生產(chǎn)者來說,也有一些痛點:
- 生產(chǎn)鏈路長:各方信息對齊、流程推動難度大;
- 模型落地難:不知如何設(shè)計、不知是否符合規(guī)范;
- 缺乏工具支持:設(shè)計、開發(fā)、測試純白手起家。
那怎么去解決用戶的這些痛點呢?首先我們要弄清楚埋點的第一站是什么。很多公司都有埋點系統(tǒng),對于大部分公司而言,埋點的第一站是埋點錄入。但是大家會發(fā)現(xiàn),埋點錄入并不是一切的源頭,埋點設(shè)計才是。埋點設(shè)計是第一手的資料,根據(jù)埋點的設(shè)計文稿可以將用戶的需求梳理得非常細致。而埋點錄入是第二手甚至是第 N 手資料,錄入的信息肯定會有丟失,并且只能進行一些基本的校驗,滿足基本的準確性。
其次,如果沒有資產(chǎn)的輔助設(shè)計,每一個埋點錄入都要從 0 到 1 去實現(xiàn)一遍。但是埋點設(shè)計通過資產(chǎn)輔助設(shè)計可以變得很簡單。因此,我們認為埋點設(shè)計才是 the single source of truth,這是我們整體設(shè)計的核心。下面來看看用戶如何在我們的系統(tǒng)設(shè)計埋點。
字節(jié)跳動流量平臺的產(chǎn)品輔助設(shè)計基于靈活的模型支持、設(shè)計資產(chǎn)積累、設(shè)計輔助,可以方便每一個用戶定義高質(zhì)量數(shù)據(jù),讓用戶愿意在系統(tǒng)進行設(shè)計和評審。
設(shè)計完埋點,我們在埋點開發(fā)上也有對應(yīng)的工具來服務(wù)來發(fā)。下面是我們的一段演示:
當用戶要設(shè)計埋點時,可以通過 ID 找到要開發(fā)的埋點,通過點擊即可插入代碼。同時,系統(tǒng)支持 VSCode 等主流編輯器,針對不同語言和代碼風格自定義代碼模版,還有類型校驗、編輯切換等。
埋點測試
埋點測試比 QA 要難很多,看的是一串數(shù)字、類型的值等。在字節(jié)跳動流量平臺系統(tǒng)中,可以依托埋點設(shè)計中的規(guī)則輔助測試,針對類型、取值、必填等自動驗證,并且可以一鍵生成報告。
我們是怎么去做好測試這件事的呢?重點還是前面提到的做好埋點設(shè)計。只有設(shè)計周全,才能積攢足夠的規(guī)則進行自動化測試,因此埋點設(shè)計方案非常重要。
埋點設(shè)計者會在方案設(shè)計時制定一系列的約束規(guī)則,我們會依托這些約束規(guī)則生成一系列相匹配的測試用例,并在測試過程中進行自動匹配、測試。
埋點測試時,測試者手機掃碼即可將服務(wù)器和瀏覽器建立連接,在 App 上操作后,流量平臺可以實時接收到對應(yīng)的埋點數(shù)據(jù)。因為已經(jīng)有測試用例,規(guī)則執(zhí)行引擎便可以自動匹配執(zhí)行并得到結(jié)果,再通過驗證結(jié)果推送服務(wù)實時推送至瀏覽器。
埋點測試后,用戶可以通過報告生成器可以一鍵生成報告,發(fā)送給 RD 進行修改或者 DA 進行驗收。這樣就完成了整個測試流程。
埋點存量治理
在生成了大量的埋點之后,我們需要進行存量埋點數(shù)據(jù)的治理,具體涉及 SLA、成本、合規(guī)以及數(shù)據(jù)質(zhì)量等方面。為什么還要進行存量埋點數(shù)據(jù)的治理呢?我們有這樣一些觀點:
- 數(shù)據(jù)不一定都是重要的。因為業(yè)務(wù)都不一定總是重要的。
- 數(shù)據(jù)并不總是有用的。比如活動下線了,埋點就要下線,否則付出了成本卻沒有收益。
- 數(shù)據(jù)不一定總是合規(guī)的。隨著數(shù)據(jù)隱私的重要性越來越高,合規(guī)要求也在不斷更新變動。
對于存量埋點數(shù)據(jù)的治理,也有一些痛點。對于治理負責方來說,數(shù)據(jù)越來越多,而對數(shù)據(jù)的實時性要求卻越來越高;隨著數(shù)據(jù)量暴增,成本也急劇增加,SLA 等級越來越慢;用戶隱私也越來越重要。對于治理實施方來說,他們可能不敢治理,不愿治理,也不懂治理。
我們梳理了用戶的需求,發(fā)現(xiàn)是這樣幾層:
- 用戶層:自動化識別埋點是否有用,流程化引導埋點的分級和下線。
- 統(tǒng)計層:做到數(shù)字化、貨幣化,治理大盤清晰可知,埋點成本準確無誤。
- 甄別層:通過的血緣圖譜,對實時統(tǒng)計、離線報表、行為分析、推薦系統(tǒng)等做實時決策。
- 執(zhí)行層:從 APP 端到數(shù)倉全流程,強兜底。
- 鏈路層:需要高效、穩(wěn)定、完整的鏈路方案解決治理難題。
針對這些需求,我們是怎么做的呢?
埋點分級/無用埋點甄別
埋點血緣和離線血緣抽取不太一樣。離線血緣是點與點之間的血緣,但埋點血緣關(guān)注的是內(nèi)容與點的血緣,它需要知道一張表的哪些行的信息有用。這是完全不同的一個領(lǐng)域,沒有任何前人的經(jīng)驗可以借鑒,我們在埋點血緣做了幾個方面的事情:
- 離線處理:通過 SQL 解析,計算埋點與離線表的血緣;
- 實時處理:利用埋點鏈路優(yōu)勢,掌握實時分流血緣關(guān)系;
- 即時分析:與行為分析、SQL 臨時查詢系統(tǒng)打通;
- 推薦系統(tǒng):利用數(shù)據(jù)清洗鏈路優(yōu)勢,解耦推薦血緣。
在埋點分級上,我們以性能埋點為突破口,給予匹配的 SLA 和 TTL 配置。
埋點鏈路解決方案
下面著重介紹一下我們的埋點鏈路解決方案。首先我們還是來看下埋點鏈路用戶的需求是什么。對于非技術(shù)的運營、分析師同學,他們需要清楚自己:
- 需要什么樣的數(shù)據(jù):是埋點數(shù)據(jù)還是展現(xiàn)數(shù)據(jù)?
- 需要哪些數(shù)據(jù):最好能對不同來源不同時效性的數(shù)據(jù)進行可視化過濾、清洗。
- 需要在哪里用這些數(shù)據(jù):是實時報表,還是行為分析,還是推薦?
埋點鏈路的挑戰(zhàn)
在埋點鏈路設(shè)計上我們也遇到了一些挑戰(zhàn):
- 大數(shù)據(jù)量下的穩(wěn)定性:埋點數(shù)據(jù)在字節(jié)跳動是核心數(shù)據(jù),其穩(wěn)定性非常重要。
- 低延遲實時處理:尤其對于推薦,實時性要求非常高。
分級構(gòu)建+下線。這里分幾塊內(nèi)容:
- 數(shù)據(jù)接入:我們提供全棧 SDK 接入,還在 SDK 內(nèi)置了管控的機制,利用各個 APP 內(nèi)終端計算的能力,大大節(jié)省成本;而且根據(jù)合規(guī)要求不能上報的埋點就可以直接在 SDK 端丟棄掉;
- 數(shù)據(jù)收集:數(shù)據(jù)收集一般是提供 HTTP 接口,將上報的數(shù)據(jù)存到消息隊列。而埋點數(shù)據(jù)量特別大,于是我們進行了埋點聚合,將埋點的 Event 數(shù)據(jù)聚合成 Applog 數(shù)據(jù)一起上報。數(shù)據(jù)進入到 Applog 后通過自研的實時數(shù)據(jù)處理平臺來解析。
實時動態(tài)處理引擎
上圖是我們自研的實時數(shù)據(jù)平臺架構(gòu),該平臺主要解決兩方面的問題:
- 實時處理:快速處理大量數(shù)據(jù);
- 動態(tài)化:字節(jié)跳動服務(wù)規(guī)模巨大,重啟時間過長會導致下游斷流,因此我們的實時領(lǐng)域里不接受重啟。此外,邏輯有任何修改都要重啟,在大量的業(yè)務(wù)和邏輯下這是不可能實現(xiàn)的。所以該平臺一定要做到動態(tài)化。
動態(tài)實時處理引擎在收到實時數(shù)據(jù)的 Applog 后將其解析成真正的埋點數(shù)據(jù)。再通過數(shù)據(jù)加工,可以轉(zhuǎn)換為其他的(甚至自定義的)數(shù)據(jù)格式,最后通過數(shù)據(jù)訂閱推送到各應(yīng)用。
第二種模式是分級。在埋點數(shù)據(jù)被解析以后,我們會打上標記,然后 dump 到 hdfs 不同的路徑下。后續(xù) Hive 進行構(gòu)建的時候可以區(qū)分優(yōu)先級,優(yōu)先級高的進入高優(yōu)隊列。Hive 的數(shù)據(jù)也是分區(qū)的,分區(qū)的數(shù)據(jù)可以制定不同的 TTL。這樣,數(shù)據(jù)的 TTL 和 SLA 就都能分級了。
第三是強保證。在埋點數(shù)據(jù)下線前,先將要下線的數(shù)據(jù)分流到 pre-discard Hive 表中暫存 30 天。如果在這段時間里沒有問題,30 天之后就可以直接下線。
現(xiàn)在,該引擎的處理邏輯、拓撲、函數(shù)以及 RPC 都可以做到動態(tài)化。用戶對于上游而言,一般是寫 SQL 或者進行界面化操作。因為用戶不懂如何處理,我們就需要特定的模型讓用戶進行適配。于是我們用聲明式表達建立統(tǒng)一的邏輯模型讓用戶直接適配。在引擎上我們還能以插件化的形式支持 Flink、Pyjstorm、TCE 等多種運行時平臺,業(yè)務(wù)方基于視圖表達可以定制化支持業(yè)務(wù)場景。
Map 計算模型
下面介紹下該引擎的邏輯動態(tài)性。我們使用的是簡單的 map 模型。
數(shù)據(jù)進來后判斷是否是需要的,過濾清洗之后需要的數(shù)據(jù)進入下游,不需要的數(shù)據(jù)就丟棄掉。基于 Groovy 語言的熱加載,將語言轉(zhuǎn)換成可執(zhí)行的邏輯。
拓撲重構(gòu)
字節(jié)跳動的業(yè)務(wù)新增速度很快,我們希望新增業(yè)務(wù)下游后也不需要重啟。對于業(yè)務(wù)方來說,用戶只關(guān)心業(yè)務(wù)邏輯,運維關(guān)心底層穩(wěn)定性和 Job 執(zhí)行效率。但是在實際處理中,一個大的困境在數(shù)據(jù)源。我們以 Kafka 為例,每多一個消費者就多一份網(wǎng)絡(luò)消耗和數(shù)據(jù)反序列化的計算成本,對 Kafka 的壓力就越大。我們應(yīng)對的方法原理其實很簡單,即基于源數(shù)據(jù)集來進行重構(gòu)。
相同 SLA 下的業(yè)務(wù)線,只要用了相同的 source,就可以把拓撲重構(gòu)為新的模型。拓撲重構(gòu)之后,用戶側(cè)無感知,SLA 也沒有打破,但是效率確實成倍提升,而且對于上游 Kafka 的壓力小了許多。
實時動態(tài)處理引擎整體架構(gòu)
我們希望這個引擎是一個會變形的引擎。上游用戶可以通過 SQL、圖形化/界面化配置,我們可以根據(jù) schema 產(chǎn)生的 catalog 生成一個通用的自定義邏輯規(guī)則。之后用戶還會對邏輯規(guī)則進行修改,比如進行校驗或函數(shù)重構(gòu),我們再會轉(zhuǎn)換成用戶的物理規(guī)則(physical rule),我們現(xiàn)在是使用 Groovy 進行轉(zhuǎn)換。轉(zhuǎn)換成物理規(guī)則之后,還有其他一些問題要處理。
首先是動態(tài)化,包括:
- UDF 動態(tài)化:我們期望 UDF 改變也不用重啟,所以 UDF 需要進行動態(tài)化編譯。
- 拓撲重構(gòu)動態(tài)化:重構(gòu)之后拓撲改變,需要新的拓撲結(jié)構(gòu)。
- RPC 動態(tài)化:可以加載動態(tài)的函數(shù)。
這些配置更新以后,經(jīng)過 Planbuilder 生成 JobGraph,引擎再拉取配置。
這時也有一個問題:我們的規(guī)則非常多,不能因為一條規(guī)則的更改就更新所有規(guī)則。所以我們做的是增量更新,只對有需要的規(guī)則進行更新。
引擎拉取之后,會加載新的資源(RPC、Schema),并進行拓撲重構(gòu)以及編譯。因為之前給到的是一些 Groovy 的代碼片段,用戶可以將其熱編譯為物理規(guī)則。
此外我們還做了很多細致的工作,例如 Object catch。舉個例子:大部分埋點上報的是 String 格式的 Json 數(shù)據(jù),用戶在進行數(shù)據(jù)清洗時就需要將 String 反序列化為 Json object,如果用戶在規(guī)則中多次用到該 Json object 就會導致多次反序列化計算。因此,我們將反序列化后的 object 進行緩存,這樣再次使用時就可以直接使用,避免重復(fù)反序列化成本。
Q&A
Q:新增埋點之后不用重啟,那多久會生效,如何保證生效?
A:我們對用戶承諾的 SLA 是 2 分鐘生效。因為實時動態(tài)引擎是動態(tài)獲取數(shù)據(jù)的過程,可以更高頻地感知變化。在變化完成之后,我們是增量修改,修改的頻率也更低。
如何能保證新增埋點生效?前面提到了重視 SLA。SLA 不一樣,資源的利用率也就不一樣。此外埋點也不可能無限加,當資源利用率達到一定的閾值之后,就需要擴容。資源不足的問題沒有辦法解決,只能重啟。
Q:資源是先申請到位再重啟嗎?
A:在我們這邊是的,資源申請到位后才會進行對應(yīng)的重啟。同時我們的開發(fā)套件會進行增量重啟,也就是不會一次把所有服務(wù)節(jié)點全部重啟,一次只會對有問題的部分(比如 10% 的服務(wù)節(jié)點)進行重啟,把限度降到最低。但是在大數(shù)據(jù)量下,重啟執(zhí)行的運維成本依然很高。
Q:平臺上的埋點開發(fā)代碼模版可以復(fù)用嗎?
A:一般不會復(fù)用。不同語言的模版肯定不同,不同產(chǎn)品的工程團隊風格要求也不一樣,也需要定制,所以模版幾乎不會被復(fù)用。
Q:埋點的丟失和重復(fù)上報的問題是怎么處理的?
A:對于丟失數(shù)據(jù)的處理分兩個方面:
端上日志:上報數(shù)據(jù)失敗后會進行重試,并且端上有監(jiān)控,可以了解當前客戶端上報的情況。
服務(wù)端的丟失,我們也有對應(yīng)的監(jiān)控,這時候丟失有幾種情況:
- 臟數(shù)據(jù):沒有通過判斷校驗邏輯的數(shù)據(jù)不會直接丟棄,而是分到一個 dirty 流,可以重新找回。
- 特殊邏輯:比如風控邏輯。
重復(fù)上報的問題很少遇到,更多的是重啟之后重新消費 Kafka 的 offset 不是那么精準。但我們的引擎是動態(tài)的,不需要重啟,就避免了這個問題。