TiDB 實踐 | 當大資料架構遇上 TiDB

語言: CN / TW / HK

作者介紹

胡夢宇,知乎核心架構平臺開發工程師,大資料基礎架構方向,主要工作內容是負責知乎內部大資料元件的二次開發和資料平臺建設。

前言

一年前,知乎的大資料架構與 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 路徑的目錄大小,這裡有以下幾種解決方案:

考慮到使用場景,大 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 請求。經過內部討論,快取方案有以下幾種:

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

展望

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