當大數據架構遇上 TiDB

作者介紹:胡夢宇,知乎核心架構平臺開發工程師,大數據基礎架構方向,主要工作內容是負責知乎內部大數據組件的二次開發和數據平臺建設。

前言

一年前,知乎的大數據架構與 TiDB 首次相遇,那時我們將 Hive MetaStore 的元數據庫遷移到了 TiDB,得到了超過單機數據庫一個量級的性能提升。在見識過分佈式 NewSQL 數據庫 TiDB 的威力後,我們對它寄予厚望,將它應用到了大數據架構的其他場景下,如:Hive 大查詢報警,NameNode RPC 加速。

Hive 大查詢報警

背景

在知乎內部,Hive 主要被應用與兩個場景:1. ETL 核心鏈路任務 2. Adhoc 即席查詢。在 ETL 場景下,Hive SQL 任務都比較固定而且穩定,但是在 Adhoc 場景下,用戶提交的 Hive SQL 比較隨機多變。在用戶對 SQL 沒有做好優化的情況下,啓動的 MapReduce 任務會掃描過多的數據,不僅使得任務運行較慢,還會對 HDFS 造成巨大壓力,影響集羣的穩定性,這種情況在季度末或者年底出現得極爲頻繁,有些用戶會掃描一季度甚至一整年的數據,這樣的查詢一旦出現,便會導致集羣資源緊張,進而影響 ETL 任務,導致報表延遲產出。

SQL 大查詢實時報警系統簡介

針對以上痛點,我們開發了大 SQL 查詢實時報警系統,在用戶提交 SQL 時,會做以下事情:

  1. 解析 SQL 的執行計劃,轉化成需要掃描的表路徑以及分區路徑;

  2. 彙總所有分區路徑的大小,計算出掃描數據總量;

  3. 判斷掃描分區總量是否超過閾值,如果超過閾值,在企業微信通知用戶。

下面詳解每一步的具體實現。

從執行計劃拿到 Hive 掃描的 HDFS 路徑

這一步我們利用 Hive Server 的 Hook 機制,在每條 SQL 被解析完成後,向 Kafka 輸出一條審計日誌,審計日誌的格式如下:

{
  "operation": "QUERY",
  "user": "hdfs",
  "time": "2021-07-12 15:43:16.022",
  "ip": "127.0.0.1",
  "hiveServerIp": "127.0.0.1",
  "inputPartitionSize": 2,
  "sql": "select count(*) from test_table where pdate in ('2021-07-01','2021-07-02')",
  "hookType": "PRE_EXEC_HOOK",
  "currentDatabase": "default",
  "sessionId": "5e18ff6e-421d-4868-a522-fc3d342c3551",
  "queryId": "hive_20210712154316_fb366800-2cc9-4ba3-83a7-815c97431063",
  "inputTableList": [
    "test_table"
  ],
  "outputTableList": [],
  "inputPaths": [
    "/user/hdfs/tables/default.db/test_table/2021-07-01",
    "/user/hdfs/tables/default.db/test_table/2021-07-02"
  ],
  "app.owner": "humengyu"
}

這裏我們主要關注以下幾個字段:

字段 含義
operation SQL 的類型,如 QUERY, DROP 等
user 提交 SQL 的用戶,在知乎內部是組賬號
sql 提交的 SQL 內容
inputPaths 掃描的 HDFS 路徑
app.owner 提交 SQL 的個人賬號

彙總分區的大小

彙總分區大小需要知道 inputPaths 字段裏每一個 HDFS 路徑的目錄大小,這裏有以下幾種解決方案:

方案 優點 缺點
調用 HDFS API 實時獲取 結果準確 需要調用 getContentSummary 方法,比較耗費 NameNode 性能,等待時間比較久。
利用 Hive MetaStore 的分區統計信息 速度較快 結果可能不準,有些表通過其他計算引擎如 Flink,Spark 直接寫入 HDFS 目錄,沒有及時更新統計信息;
利用 HDFS 的 fsimage 解析出所有 Hive 目錄大小,存入 TiDB 速度較快 結果具有 T+1 的延遲,當天的分區無法統計大小。

考慮到使用場景,大 SQL 查詢大部分情況下都是掃描了幾個月甚至幾年的數據,一兩天的分區信息忽略可以接受,我們選擇了第三種方案:每天將 HDFS 的 fsimage 解析,並且計算出每個 Hive 目錄的大小,再將結果存入 TiDB。因爲我們在其他場景也會用到 fsimage 的信息,所以這裏我們不僅僅只存儲了 Hive 目錄,而是存儲了整個 HDFS 的目錄情況,近百億條數據。很明顯,在如此大的數據量下,還涉及到數據索引相關,TiDB 是一個很好的選擇。

實時報警

我們將審計日誌實時發送至 Kafka,再用 Flink 實時去消費 Kafka 內的審計日誌,利用 KafkaTableSource 和 Json Format 將 Kafka 作爲流表,再利用 JdbcLookupTableSource 將 TiDB 作爲維表,便可輕鬆計算出每條 SQL 掃描的數據量再進行報警判斷。

最後達成的效果如下:

NameNode PRC 加速

背景

故事的起因是這樣的,在有一段時間內,經常有用戶反饋 Hive 查詢卡住沒有反應,短的卡十幾分鍾,長的卡幾小時,十分奇怪,經過定位發現是 Hive 內部在調用 getInputSummary 方法時,有一把全局鎖,在某一個查詢較大時,調用這個方法會花費較長的時間,導致其他的查詢線程在等待這把鎖的釋放。經過閱讀源碼發現,getInputSummary 方法是可以併發去執行的,它內部其實就是在調用 HDFS 客戶端的 getContentSummary 方法,我們將鎖去掉,不再使用全局鎖的功能,而是採用了類似線程池的方式,讓它可以以一個較高的併發度去執行。但是這樣會帶來一些問題,HDFS 客戶端的 getContentSummary 方法類似於文件系統的 du 操作,如果併發度過高,會顯著影響 NameNode 性能。不僅僅只有 Hive,其他的計算引擎也會調用 getContentSummary 方法,因此,優化這個方法十分必要。

緩存 ContentSummary 信息

知乎在 2019 年 HDFS 就已經拆分了 Federation, 採取的是 Router Base Federation 的方案,引入了 NameNode 的代理組件 Router. 我們只要在 Router 層給 HDFS 的 ContentSummary 做一層緩存,在客戶端發起調用時,如果緩存命中,則從緩存讀取,如果緩存未命中,則從 NameNode 請求。經過內部討論,緩存方案有以下幾種:

方案 優點 缺點
客戶端第一次請求 Router 時,從 NameNode 返回,更新緩存;第二次請求時,先拿緩存,並且判斷目錄的修改時間,如果期間發未發生修改,則返回緩存,如果發生了修改,從 NameNode 返回,更新緩存。 對於不常修改的目錄,只需要請求一次 NameNode。 對於第一次請求依然需要去訪問 NameNode;只能緩存沒有子目錄的目錄,因爲子目錄的變更上層目錄無法感知。
每天利用 fsimage 產出一份全目錄的 ContentSummary 信息緩存至 TiDB,在客戶端請求時,走第一種方案的邏輯。 大部分目錄的第一次請求都不用走 NameNode。 依然只能緩存沒有子目錄的目錄,因爲子目錄的變更上層目錄無法感知。

我們選擇了第二種方案,因爲 ContentSummary 信息在我們之前做 Hive SQL 大查詢報警的時候已經產出,所以接入進來十分方便。在接入 TiDB 做緩存,並且給請求路徑建索引以後,對於一般情況下的 getContentSummary 請求,延遲能保證在 10ms 以下,而對於沒有 TiDB 緩存的 NameNode,這個時間可能會花費幾分鐘甚至幾十分鐘。

展望

本次我們利用 TiDB 的超大存儲和索引功能,緩存了 HDFS 的元信息,滿足了知乎內部的一些場景,後續我們會持續改進和擴展此場景:比如緩存 HDFS 文件信息可以做成實時緩存,利用 Edit log 訂閱文件變更,然後和 TiDB 裏面的存量 fsimage 進行合併,產出低延遲的 NameNode 快照,用於一些在線的分析等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章