嗶哩嗶哩⼤資料建設之路—實時DQC篇

ITPUB社群發表於2023-02-01


背景


資料質量是基於大資料衍生的應用有效與否的重要的前提和保障之一。B站現在高速發展的業務需求以及未來能夠依靠大資料孵化出更有深度和競爭力應用的願景,都要求我們資料平臺能夠提供實時的、準確的、可以被各個業務方所信賴的資料。可以說,可信賴的資料,是大資料平臺核心競爭力的體現。因此,在B站的大資料平臺的建設過程中,資料質量平臺成為了不可或缺的一環,因為它的使命便是為大資料平臺的資料質量保駕護航。


 質量平臺


平臺元件


嗶哩嗶哩⼤資料建設之路—實時DQC篇


 DQC簡介


功能圖


嗶哩嗶哩⼤資料建設之路—實時DQC篇


  • DQC主要的工作鏈路與普通的監控系統如prometheus基本相似,過程都包括資料採集、資料檢查、告警通知,都需要儘量減少對監控物件正常工作的影響,同時對監控物件的異常做到及時告警。

  • 功能上有離線和實時之分

  • 離線DQC:主要用來保障由離線任務生產出來的資料,觸發規則主要是由離線排程任務完成後來通知DQC做採集,然後再根據閾值進行檢查及告警。通常使用者配置檢查規則,一般會在下一次任務完成後生效。
  • 實時DQC:主要針對Kafka資料來源。Kafka是大資料平臺中一個非常重要的基礎元件,特別是在實時數倉中,使用的則更加頻繁。一般是根據規則在固定的檢查週期對在一定視窗資料進行採集及檢查。通常使用者配置檢查規則,一般會在下一個資料週期後生效。


實時DQC


 第一版方案


嗶哩嗶哩⼤資料建設之路—實時DQC篇


公司內部流計算的主流框架是Flink,所有實時DQC也是採用Flink計算框架。

實時DQC 相對離線DQC複雜不少,因為它的資料採集任務是一直執行著的,向一個已經執行的任務中去更新規則,或者新增採集物件,有一定的難度。

因此在第一版方案中,使用者每新增一個採集物件或一個規則,DQC服務會為使用者重新生成一個新的Flink任務來完成採集,並將採集結果寫入到MySQL中。同時定時觸發質量規則檢查,將檢查結果寫入到MySQL中,並根據檢查結果判斷是否做告警通知。

這個方案架構簡單,但是仍存在以下弊端:

資源利用率低: 由於一個檢查規則一個資料採集任務,導致Flink任務大多數時間都比較空閒,特別在一些流量小的Topic更加明顯。

網路頻寬消耗大: 對一個Topic不同的規則,需要多個任務去消費。如果Topic流量很大,對網路頻寬會有很大沖擊。

穩定性差: 規則的修改需要重啟資料採集任務,叢集資源緊張時可能因為申請不到YARN/K8s資源而導致啟動失敗。

監控程式本身佔用資源大,在降本增效為主流的現在,是不合時宜的。因此,我們迫切需要對實時DQC的方案進行改造,在資源使用上有下面三個要求:

  1. 一次啟動,不再重啟。避免資源競爭,提高系統可用性。

  2. 一個任務,多Topic共用。實際應用中,一些Topic流量很小,導致Flink任務大多數時間都比較空閒。如果一個Flink可以多消費幾個這樣Topic,相同的資源做更多的事,資源利用率更高。

  3. 一次消費,多規則校驗。避免多工重複消費一個Topic,減少網路頻寬消耗。


 新的方案


嗶哩嗶哩⼤資料建設之路—實時DQC篇


  • 設計目標:

    • 實時DQC採集程式的準確性;

    • 規則檢查的及時性;

    • 最大限度的減低實時DQC的資源消耗;

    • 最大限度的減低Kafka正常作業的影響;


總體架構


嗶哩嗶哩⼤資料建設之路—實時DQC篇


目前線上Topic 有7000+,為了便於管理,主要根據Influxdb能接受的QPS、及Flink任務利用率,實時DQC把Topic劃分為大Topic、中Topic、小Topic。小Topic: 訊息量 <1000/s,中Topic: 訊息量 (1000/s—10w/s),大Topic: 訊息量 > 10w/s。

方案分析:

  • 大中小Topic分開管理,走不同的路徑來做資料採集,中小Topic中的資料全量匯入到全量表中(欄位會根據檢查規則做過濾),然後利用Influxdb CQ功能做匯聚存入CQ表完成資料採集,而大Topic則在Flink任務中直接算出最終的匯聚資料存入CQ表;

  • Influxdb 全量表:中小Topic使用,儲存Topic中的全量資料(欄位會根據檢查規則做過濾),一般有效時間為一個小時;

  • Influxdb CQ表:所有Topic都會使用, 儲存了Topic的匯聚資料,用於規則檢查,一般有效時間為二個星期;

  • Topic動態化:執行時可新增或刪除監控的Topic,無需重啟Fllink任務;

  • 規則動態化:執行時可以新增、刪除、修改Topic的監控規則,無需重啟Fllink任務;

  • DQC資源管理:這裡包括兩個部分,一個Flink任務和Influxdb資源,合理利用這兩個資源,對Topic或者Topic的規則進行動態分配管理;


中小Topic方案


嗶哩嗶哩⼤資料建設之路—實時DQC篇


  • 資料全量匯入到全量表中(欄位會根據檢查規則做過濾)

  • Topic、Mapper支援動態化:

  • Topic動態化,Topic列表維護在配置中心,KafkaConsumer可以感知配置的變化從而達到動態的增刪Topic;
  • Mapper動態化,根據消費Topic 列表、Topic內部的資料格式、及對應DQC規則,動態形成Mapper的處理邏輯,推到Mapper Warpper;

中小Topic方案—kafkaconsumer


嗶哩嗶哩⼤資料建設之路—實時DQC篇


解決方案:因為開發人力、時間的原因,沒有完全開發一個新的支援動態Topic的Kafka Consumer,而是擴充套件了FlinkKafkaConsumer,KafkaFetcher && KafkaConsumerThread採用hack方式處理。


中小Topic方案—Mapper


嗶哩嗶哩⼤資料建設之路—實時DQC篇


解決方案:根據TopicList、Topic內容、DQC規則形成Mapper的bytecode,以Base64方式存入配置中心,Mapper Wrapper動態獲取,形成新的Mapper,來替換老的Mapper。


大Topic方案


由於大型Topic流量很大,如果還和中小型Topic使用相同的方案,不管是對網路頻寬,還是對底層儲存,都會造成非常大的壓力。因此我們選擇了另一種方案------單Topic單任務 + 規則動態。

每一個Flink任務只消費一個Topic,在這個任務中,我們需要能夠動態感知針對該Topic的檢查規則的變化,並根據檢查規則對消費到的實時資料流進行打標,對打標後的結果按視窗進行匯聚。


嗶哩嗶哩⼤資料建設之路—實時DQC篇


  • 動態邏輯:規則動態化,規則元資訊維護在配置中心,FlatMap可以感知規則變化,將規則命中的記錄向後輸出進行聚合


規則動態


嗶哩嗶哩⼤資料建設之路—實時DQC篇


為了降低使用者的學習成本,在該方案中,我們與離線DQC配置保持一致,使用者可以透過SQL自定義where子句對資料進行過濾,實現更加精細的質量檢查。規則解析器會對SQL中的where子句部分進行詞法解析,生成對應的過濾器,用於資料過濾,對符合規則的記錄,打上規則ID後,根據規則ID向後輸出後由聚合運算元進行區域性的聚合。


資料膨脹


在規則打標後,輸出到視窗進行聚合的過程中,可能會產生資料膨脹。


嗶哩嗶哩⼤資料建設之路—實時DQC篇


如上圖所示,當資料進入FlatMap後,FlatMap會根據規則過濾器對資料打上規則ID標籤,再將資料向後傳送給聚合運算元,聚合運算元根據規則進行聚合。如果一條記錄沒有命中任何規則,則不會向後輸出。從上圖可以看出,輸入條數為1,輸出條數為4,資料膨脹4倍。這是由於我們向後輸出的資料格式為,使用規則ID作為分組條件。一條記錄命中多個規則,則會輸出多條記錄。由於該方案本身是針對大Topic的,流量本身就很大,在經過規則過濾器放大,頻寬壓力很重,不符合設計初衷。

針對這種情況,我們做了兩個最佳化:

  1. FlatMap資料輸出格式調整為 <分組Key,RuleIdList,Data>。將資料命中的所有規則ID合併在一起,向後輸出。聚合運算元收到資料後,根據RuleIdList進行業務計算。

  2. 使用map -> reduce -> reduce架構,對規則打標後輸出的結果,會先進行一次區域性聚合,最後再進行全域性規約資料彙總。


嗶哩嗶哩⼤資料建設之路—實時DQC篇


但是,這個設計並不能徹底解決問題。


規則
處理邏輯
分組key
錶行數類規則區域性累加,彙總時將區域性計算結果累加得到彙總值資料hash
彙總值類規則區域性累加,彙總時將區域性計算結果累加得到彙總值資料hash
最大值/最小值類規則區域性取最大/小值,彙總時將區域性計算結果取最大/小值得到全域性的最大/小值資料hash
平均數類規則區域性計算<欄位值累加值, 條數累加值>,彙總時將區域性計算結果合併計算全域性的<欄位值累加值, 條數累加值>得到全域性的<欄位值累加值, 條數累加值>資料hash
欄位去重(Distinct)類規則區域性對欄位值去重後向後輸出一遍,彙總時累加欄位數量欄位值


經過對實時DQC質量規則分析,除了欄位去重(Distinct)類規則外,其他所有規則,對分組的key的具體取值是完全不依賴,因為區域性聚合與最終彙總時的業務邏輯是一致的。然而欄位去重(Distinct)類規則是在區域性進行去重,最終彙總時則是將計算去重後的記錄數量,因此在區域性聚合時分組key必須使用欄位值。對此,我們調整了第一步的最佳化方案:

  1. 當Topic沒有欄位去重(Distinct)類規則,將所有規則合併輸出,輸出格式為

  2. 當Topic有一個欄位去重(Distinct)類規則,則將所有規則合併輸出,輸出格式為 <欄位值,ruleIDList,Data>

  3. 當Topic有多個欄位去重(Distinct)類規則,且使用不同的欄位時。將其中一個與其他所有非去重類規則合併輸出。剩餘的數值欄位去重(Distinct)類規則單獨輸出。

最終形態如下圖所示:


嗶哩嗶哩⼤資料建設之路—實時DQC篇


在最佳化之前,由於FlatMap的資料膨脹率很高,聚合運算元經常會出現背壓情況,導致消費效能下降。

在最佳化之後,資料膨脹率大大降低,但是仍然存在。而膨脹率取決於欄位去重(Distinct)類規則類規則的數量,膨脹率是可預知且可控的。目前在B站大資料平臺中,除了特殊業務外,基本沒用使用該類規則的情況,但仍需要透過監控該規則使用情況,適時增加資源或規則下線方式保障任務穩定性。


Influxdb proxy方案


為了更好的適配和處理讀寫請求和Influxdb之間的聯絡,我們引入了Influxdb proxy代理服務。


嗶哩嗶哩⼤資料建設之路—實時DQC篇


後端的Influxdb叢集包含多組例項,每一組例項中的資料都不分片,只互為備份,最終一致性由proxy雙寫來保證。

雙寫過程中,如有寫入失敗,Influxdb proxy將失敗請求內容,記錄到本地檔案中並進行重試,直至成功寫入。

每一個Influxdb例項節點包含分配到該例項的完整的全量表和CQ表資料。

最佳化讀請求

查詢時,proxy會選擇後端的最優Influxdb節點(資料完整性、節點效能)來查詢。

如果後端的Influxdb節點都有問題情況時,那麼這次查詢會降級。

最佳化寫請求

由於實時DQC寫入資料量大且極為頻繁,所以為了減少網路IO流量開銷,Influxdb proxy使用gzip壓縮、批次寫入的方式提升寫入效能。

在Influxdb proxy上線後,由於實際執行時,實時寫入的資料量十分大,導致網路IO一直處於峰值狀態。經過排查,發現原本資料輸入都是一次請求包含5000+的完整Influxdb資料插入語句,且大多都是相同db和相同measurement,同時每條插入語句有太多的無效tag。隨即我們最佳化的資料寫入的介面協議,每次請求只能寫入同一個db,且把相同的measurement抽出來,同時刪除插入語句中無效tag。

在上述改造之後,網路IO流量開銷有了明顯的改善和下降。

運維保障

在Influxdb proxy中,引入了Prometheus監控,實時監控讀寫請求的qps、寫入的db,measurement數量和分佈情況等,更好的加強相關資源的利用。

支援對後端的Influxdb叢集管理,包括節點資料同步,新節點加入、刪除,節點資料恢復等。


Influxdb方案


全量表

在中小型Topic的處理上,我們選擇落庫全部資料,每個Topic都會單獨儲存為一張Influxdb的measurement。measurement的結構如下所示:


嗶哩嗶哩⼤資料建設之路—實時DQC篇


  • time:資料時間

  • subtask:業務預設欄位,消費的flink子任務號

  • sinknum:業務預設欄位,子任務消費順序

  • 擴充套件tag:topic欄位,做索引用

  • record_num:業務預設欄位,值為1,用於計算行數類規則

  • 擴充套件field:topic欄位

業務預設欄位在全量表的設計中,我們額外增加了兩個tag欄位:subtask和sinknum。由於Influxdb的特性,當兩條記錄的時間和tag完全相同時,此時後寫入的記錄會覆蓋前一條。當計算錶行數規則時,這種情況可能導致誤告。因此,為了保障資料可以寫入,我們增加了subtask、sinknum兩個欄位,subtask使用的flink上下文中的子任務號,sinknum我們使用環形陣列實現,取值範圍是1-200,用於確保每條記錄都是獨一無二的,不會發生覆蓋的問題。

TTL時間全量表的作用,主要是為CQ表做準備。Influxdb會定時讀取全量表,根據質量規則進行資料聚合,並將聚合結果寫入到CQ表。而實時DQC的特性決定了已經被計算進入CQ表的全量表資料便已經過期了。因此,為了避免資料條數、序列數等資源無限制增長,全量表的TTL時間我們設計為1小時,超過1小時的全量表記錄會被Influxdb刪除。

Tag和Field的關係tag在Influxdb中是被作為索引使用的,通常用來作為查詢條件使用,而field則是在某個時間點產生的事件的具體值。在Influxdb中,一次查詢如果沒有查詢任何一個field欄位,那麼這次查詢是沒有意義的,不會返回任何結果。因為只有field才能反應那個時間點的資訊。因此我們業務預設保留一個record_num欄位作為field,錶行數類規則會優先使用該欄位。


什麼欄位會被儲存到Influxdb?

一個Topic的欄位數量無法確定,幾個或上百個,都有可能。起初對這些欄位,我們並沒有篩選,選擇全部落庫,在執行過程中發現呼叫Influxdb-proxy服務屢屢發生異常。在問題定位中發現這樣的異常現象,Influxdb-proxy機器網路頻寬消耗大,CPU使用率高。最終結論是,Influxdb-proxy寫入Influxdb時會將資料壓縮,減少網路頻寬消耗,提高寫入效能,但由於寫入資料的冗餘資訊過多,壓縮過程導致CPU負載非常高。Influxdb-porxy機器由於負載過高,無法繼續向外提供服務。

因此,去除冗餘資料使我們需要最佳化的點。經過業務分析,我們任務一個Topic中大部分欄位,都是不需要落庫的。以直播業務某TopicA(40個欄位)為例,計算某個時間視窗下的質量規則:


規則ID
規則
業務SOL
需要儲存的欄位
1
錶行數select count(1) from TopicA
2
來自IOS平臺的記錄數select count(1) from TopicA where platform = 'IOS'platform
3
來自安卓平臺的不同mid數select count(distinct mid) from TopicA where platform = 'android'platform,mid


根據規則發現,我們實際使用的欄位只有platform和mid欄位,其餘欄位對當前業務而言,都是冗餘的。針對目前實時質量規則使用的現狀,去除掉冗餘欄位,只儲存必須的資訊,網路頻寬消耗可以減少90%以上,Influxdb-proxy的使用率一直維持在一個穩定的變化範圍。在後續的使用中,該類異常再也未曾復現。


什麼欄位會被應用為tag?

tag在Influxdb中是作為索引被使用的,一條記錄所有的tag被稱為序列Series,根據機器效能的不同,每臺機器可以承載的序列數量是有限的,序列數膨脹會造成Influxdb的讀寫效能急劇下降。

剛上線初期,由於接入Topic時未進行嚴格的SOP流程,發生過這樣一起事故。某Topic中包含XxxId欄位,運維人員誤認為該欄位是某字典資訊,取值範圍是有限的。因此直接作為Tag欄位進行接入,結果該欄位是高基數的,短時間造成Influxdb序列數急速膨脹到300W,Influxdb幾乎不可對外提供服務。在總結會議上,為避免此類情況再次發生,我們制定了Topic接入SOP,對Tag欄位進行嚴格校驗。

由於Tag欄位的選取對Influxdb的穩定性十分重要。我們是如何判斷什麼欄位會被應用為tag?


嗶哩嗶哩⼤資料建設之路—實時DQC篇


我們認為tag欄位的使用需要滿足以下兩點:

  1. where子句中被用作過濾條件,如 where platform = 'ios' 子句中,platform欄位會被選中繼續進行篩選

  2. 欄位取值範圍可列舉,不存在高基,如 platform 欄位的取值範圍在 ios、android、web中,符合tag欄位的選取特徵。

在上面的例子中,platform欄位會被應用為tag,而mid欄位由於存在高基,會被應用為field。實際應用中,不能作為tag又需要使用的欄位,都會被儲存為field。


新增規則,欄位如何擴充套件?

類似mongodb這種非結構化資料庫,Influxdb中的measurement也不需要預先定義schema,擁有良好的擴充套件性。當新增欄位時,只需要在寫入語句中指定新增欄位與欄位值即可完成欄位新增。


CQ表

CQ表儲存的是實時資料根據業務規則聚合後的結果,它的結構簡單且固定,如下圖所示:


嗶哩嗶哩⼤資料建設之路—實時DQC篇


  • time:計算視窗開始時間

  • rule_id:質量規則ID

  • value:時間視窗內質量規則的計算結果

CQ的表資料來源有兩種:

  1. 全量表定時匯聚寫入:依靠Influxdb自身提供的基礎能力Continuous Query實現,依靠該能力,Influxdb能夠對實時資料自動週期執行查詢,並將查詢結果寫入指定的CQ表中,目前Continuous Query執行 99分位耗時<30ms.

  2. Flink任務(大Topic)實時匯聚寫入:大Topic消費任務會在時間視窗內進行質量規則計算,並將計算結果寫入到CQ表中

CQ表的TTL時間CQ表是按分鐘聚合的結果,它的TTL時間我們是根據業務來設計的。由於存在日同比、周同比等波動率比值型別的規則,因此CQ表的TTL時間目前設定的是14天,避免由於資料過期刪除導致誤告警。


Influxdb水位

Influxdb是整個方案的核心,保障Influxdb的穩定性是非常重要的一環。因此,對Influxdb的監控便是重中之重。經過對我們實際使用機器的測試,目前單機寫入瓶頸在150W/s,序列數量峰值在200W左右,超出數值可能會導致寫入效能下降。

對此我們採取了以下措施:

  1. Influxdb支援水平擴充套件,根據Topic後設資料與Influxdb監控資訊判斷,如果某臺Influxdb當前承載的Topic流量或Influxdb實時寫入量已經達瓶頸的80%,新接入的Topic會往新的Influxdb寫入。

  2. 序列數量增速監控,由於資料ttl時間的限制,序列的數量應該是呈週期性的變化,並且峰值穩定保持在一個資料範圍內。但若是tag欄位選取不當,選擇了高基欄位,就會造成序列數量急速增長。當監控到這種情況,需要及時處理相關Topic,保障服務穩定。


運維&&異常情況


為了保障實時DQC新架構的穩定性,我們主要考慮了以下異常情況。


流量突增,訊息堆積

流量突增,訊息堆積主要分兩種情況:

業務方可預知的增長情況:這類情況會比較多,比如某些賽事直播活動,相關的Topic流量突增,類似於最近的英雄聯盟S12總決賽直播

業務方不可預知的增長情況:這類情況會比較少,UP主上傳某個影片成了爆款,這種現象是不可預知的。

經過評估,當出現訊息堆積時,此時的告警都可能是不準確,因此我們需要極力避免出現訊息堆積的情況。

針對可預知的增長情況,我們會提前增加資源,提高任務的消費能力。

針對不可預知的增長情況或即使增加資源仍舊導致訊息堆積時,我們認為此時任務是以非正常狀態執行的,對其進行降級處理,關閉任務觸發的相關告警,P0等高優級Topic會傳送通知給到使用者,告知其影響面。


嗶哩嗶哩⼤資料建設之路—實時DQC篇


程式Crash

使用Flink的checkpoint機制,保障任務異常恢復。但是仍然存在以下問題。


中小Topic:

該方案中資料需要寫入Influxdb,為提高吞吐量,在sink階段接收的資料不會每次都寫入儲存,而是先放入緩衝區,當達到資料長度或等待時間達到閾值後,再寫入Influxdb。因此,若程式Crash,可能導致最近一次緩衝區中的資料丟失,該情況可能導致漏告與誤告。

目前我們設定的時間閾值是10秒,記錄數量是1000條,考慮到把快取中的資料在每次checkpoint時儲存到檔案系統所耗費的成本,我們認為這樣的損失是可以接受的,在產品的宣發上也會與使用者說明。

大Topic:

使用Flink的checkpoint機制,可以很好的保障任務從上次狀態恢復。在寫入Influxdb時,會根據時間和tag資訊覆蓋聚合結果,避免結果重複寫入。

需要考慮的點是,這個方案針對的Topic都是大流量的,如果恢復時間過長,可能導致資料堆積。需要考慮資料堆積的處理方案。


重複消費

多個任務重複消費相同的Topic,導致寫入Influxdb資料增多,可能導致誤告。

需要注意:kafkaconsumer裡面配置的group.id已經不具備消費組的特性,因為kafkaconsumer使用了底層API來分配TP。


解決方案:

任務與Topic強繫結,分配Topic給消費任務時,註冊任務與Topic到資料庫中,若已存在相同的Topic,則啟動失敗,同時發出告警。

結合以上異常情況,為了保障新架構的穩定性,在運維方面我們主要做了以下措施:

  1. Flink斷流與堆積監控

  2. Influxdb叢集狀態監控

  3. Influxdb序列數監控

藉助這些措施,幫助我們及時發現問題並解決問題。


實時DQC後續工作


 工程化


嗶哩嗶哩⼤資料建設之路—實時DQC篇


新的方案雖然目前已經上線實施,但是,由於新的架構較之前稍顯複雜,在自動工程化方面還有所欠缺。存量的DQC規則都是依靠開發人員人工移植,後續的規則新增也需開發人員參與。

另一方面,分級保障也是我們工程化的一個發力點。P0級別的Topic是在任何情況下,都是需要我們優先保障的。P0與P1的Topic不會再一個任務中合併消費,當啟動P0任務如果失敗,根據失敗異常資訊,會選擇停止一個正在執行中的P1級別任務,釋放資源,優先用來啟動P0任務。當P1任務停止時,會發出告警通知到使用者與運維人員,進行人工判斷是否需要增加佇列資源。

下一階段,我們會在自動工程化和分級保障方面持續發力,將更好的使用體驗呈現給使用者。


 Flink任務管理


在降本增效主流的現在,依靠Topic動態與規則動態兩種設計,節約大量叢集資源消耗,但仍有最佳化的空間,那就是多Flink任務更加精細化的管理。比如,Topic動態方案中,一個Topic是否應該向已經存在的任務重動態加入,還是新啟動一個任務進行消費?這需要結合任務當前負載進行判斷。

還有Influxdb單機處理瓶頸問題,在我們的規劃中,Influxdb可以水平擴充套件,Flink任務啟動時,需要結合Influxdb叢集負載,與當前任務資訊,選擇最優Influxdb節點寫入資料。


 對正常流的影響


對Topic的質量監控,不應該影響到線上正常作業的任務。目前質量任務所使用的佇列仍然是和其他任務混合使用的,可能會造成資源競爭、佔用機器資源影響到機器上其他任務等問題,後續會根據資源規劃進行調整。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024420/viewspace-2933594/,如需轉載,請註明出處,否則將追究法律責任。

相關文章