MySQL到TiDB:Hive Metastore橫向擴充套件之路

vivo網際網路技術發表於2023-09-28
作者:vivo 網際網路大資料團隊 - Wang Zhiwen

本文介紹了vivo在大資料後設資料服務橫向擴充套件道路上的探索歷程,由實際面臨的問題出發,對當前主流的橫向擴充套件方案進行了調研及對比測試,透過多方面對比資料擇優選擇TiDB方案。其次分享了整個擴充套件方案流程、實施遇到的問題及解決方案,對於在大資料後設資料效能上面臨同樣困境的開發者本篇文章具有非常高的參考借鑑價值。

一、背景

大資料後設資料服務Hive Metastore Service(以下簡稱HMS),儲存著資料倉儲中所依賴的所有後設資料並提供相應的查詢服務,使得計算引擎(Hive、Spark、Presto)能在海量資料中準確訪問到需要訪問的具體資料,其在離線數倉的穩定構建上扮演著舉足輕重的角色。vivo離線數倉的Hadoop叢集基於CDH 5.14.4版本構建,HMS的版本選擇跟隨CDH大版本,當前使用版本為1.1.0-cdh5.14.4。

vivo在HMS底層儲存架構未升級前使用的是MySQL儲存引擎,但隨著vivo業務發展,資料爆炸式增長,儲存的後設資料也相應的增長到億級別(PARTITION_PARAMS:8.1億、PARTITION_KEY_VALS:3.5億、PARTITIONS:1.4億),在如此大量的資料基數下,我們團隊經常面臨機器資源的效能瓶頸,往往使用者多併發的去查詢某些大分割槽表(50w+分割槽),機器資源的使用率就會被打滿,從而導致後設資料查詢超時,嚴重時甚至整個HMS叢集不可用,此時恢復手段只能暫時停服所有HMS節點,直到MySQL機器負載降下來後在逐步恢復服務。為此,針對當前MySQL方案存在的嚴重效能瓶頸,HMS急需一套完善的橫向擴充套件方案來解決當前燃眉之急。

二、橫向擴充套件技術方案選型

為解決HMS的效能問題,我們團隊對HMS橫向擴充套件方案做了大量的調研工作,總體下來業內在HMS的橫向擴充套件思路上主要分為對MySQL進行拆庫擴充套件或用高效能的分散式引擎替代MySQL。在第一種思路上做的比較成熟的方案有Hotels.com公司開源的Waggle Dance,實現了一個跨叢集的Hive Metastore代理閘道器,他允許使用者同時訪問多個叢集的資料,這些叢集可以部署在不同的平臺上,特別是雲平臺。第二種思路當前主流的做法是用分散式儲存引擎TiDB替換傳統的MySQL引擎,在Hive社群中有不少公司對hive 2.x接入TiDB做了大量的測試並應用到生產中(詳情點選)。

2.1 Waggle Dance

Waggle-dance向使用者提供統一的入口,將來自Metastore客戶端的請求路由到底層對應的Metastore服務,同時向使用者隱藏了底層的Metastore分佈,從而在邏輯層面整合了多個Metastore的Hive庫表資訊。Waggle-dance實現了Metastore的Thrift API,客戶端無需改動,對使用者來說,Waggle-dance就是一個Metastore。其整體架構如下:

圖片

Waggle Dance架構

從Waggle-dance的架構中最突出的特性是其採用了多個不同的MySQL例項分擔了原單MySQL例項的壓力,除此之外其還有如下優勢:

  1. 使用者側可以沿用Metastore客戶端的用法,配置多臺Waggle-dance的連線,在當前Waggle-dance連線服務不可用的時候切換到其他的Waggle-dance服務上。
  2. Waggle-dance只需幾秒即可啟動,加上其無狀態服務的特性,使得Waggle-dance具備高效的動態伸縮性,可以在業務高峰期快速上線新的服務節點分散壓力,在低峰期下線部分服務節點釋放資源。
  3. Waggle-dance作為一個閘道器服務,除了路由功能外,還支援後續的定製化開發和差異化部署,平臺可根據需要新增諸如鑑權、防火牆過濾等功能。

2.2 TiDB

TiDB 是 PingCAP 公司自主設計、研發的開源分散式關係型資料庫,是一款同時支援線上事務處理與線上分析處理 (Hybrid Transactional and Analytical Processing, HTAP) 的融合型分散式資料庫產品,具備水平擴容或者縮容、金融級高可用、實時 HTAP、雲原生的分散式資料庫、相容 MySQL 5.7 協議和 MySQL 生態等重要特性。在TiDB 4.x版本中,其效能及穩定性較與之前版本得到了很大的提升並滿足HMS的後設資料查詢效能需求。故我們對TiDB也做了相應的調研及測試。結合HMS及大資料生態,採用TiDB作為後設資料儲存整體的部署架構如下:

圖片

HMS on TiDB架構 

由於TiDB本身具有水平擴充套件能力,擴充套件後能均分查詢壓力,該特性就是我們解決HMS查詢效能瓶頸的大殺器。除此外該架構還有如下優勢:

  1. 使用者無需任何改動;HMS側面沒有任何改動,只是其依賴的底層儲存發生變化。
  2. 不破壞資料的完整性,無需將資料拆分多個例項來分擔壓力,對HMS來說其就是一個完整、獨立的資料庫。
  3. 除引入TiDB作為儲存引擎外,不需要額外的其他服務支撐整個架構的執行。

2.3 TiDB和Waggle Dance對比

前面內容對Waggle-dance方案和TiDB方案做了簡單的介紹及優勢總結,以下列舉了這兩個方案在多個維度的對比:

圖片

透過上述多個維度的對比,TiDB方案在效能表現、水平擴充套件、運維複雜度及機器成本上都優於waggle-dance方案,故我們線上選擇了前者進行上線應用。 

三、TiDB上線方案

選擇TiDB引擎替代原MySQL儲存引擎,由於TiDB與MySQL之間不能做雙主架構,在切換過程中HMS服務須完全停服後並重新啟動切換至TiDB,為保障切換過程順利及後面若有重大問題發生能及時回滾,在切換前做了如下資料同步架構以保障切換前MySQL與TiDB資料一致以及切換後仍有MySQL兜底。

圖片

TiDB&MySQL上線前後資料同步架構

在上述架構中,切換前唯一可寫入的資料來源只有源資料庫主庫,其他所有TiDB、MySQL節點都為只讀狀態,當且僅當所有HMS節點停服後,MySQL源資料庫從庫及TiDB源資料庫主庫的資料同步最大時間戳與源資料庫主庫一致時,TiDB源資料庫主庫才開放可寫入許可權,並在修改HMS底層儲存連線串後逐一拉起HMS服務。

在上述架構完成後,即可開始具體的切換流程,切換整體流程如下:

圖片

HMS切換底層儲存流程

其中在保障源MySQL與TiDB資料正常同步前,需要對TiDB做以下配置:

  • tidb_skip_isolation_level_check需要配置為1 ,否則啟動HMS存在MetaException異常。
  • tidb_txn_mode需配置為pessimistic ,提升事務一致性強度。
  • 事務大小限制設定為3G,可根據自己業務實際情況進行調整。
  • 連線限制設定為最大3000 ,可根據自己業務實際情況進行調整。

此外在開啟sentry服務狀態下,需確認sentry後設資料中NOTIFICATION_ID的值是否落後於HMS後設資料庫中NOTIFICATION_SEQUENCE表中的NEXT_EVENT_ID值,若落後需將後者替換為前者的值,否則可能會發生建表或建立分割槽超時異常。

以下為TiDB方案在在不同維度上的表現:

  1. 在對HQL的相容性上TiDB方案完全相容線上所有引擎對後設資料的查詢,不存在語法相容問題,對HQL語法相容度達100% 
  2. 在效能表現上查詢類介面平均耗時優於MySQL,效能整體提升15%;建表耗時降低了80%,且支援更高的併發,TiDB效能表現不差於MySQL
  3. 在機器資源使用情況上整體磁碟使用率在10%以下;在沒有熱點資料訪問的情況下,CPU平均使用率在12%;CPU.WAIT.IO平均值在0.025%以下;叢集不存在資源使用瓶頸。
  4. 在可擴充套件性上TiDB支援一鍵水平擴縮容,且內部實現查詢均衡演算法,在資料達到均衡的情況下各節點可平攤查詢壓力。
  5. 在容災性上TiDB Binlog技術可穩定支撐TiDB與MySQL及TiDB之間的資料同步,實現完整的資料備份及可回退選擇。
  6. 在服務高可用性上TiDB可選擇LVS或HaProxy等服務實現負載均衡及故障轉移。

以下為上線後HMS主要API介面呼叫耗時情況統計:

圖片

圖片

圖片

圖片

圖片

圖片

圖片

圖片

四、問題及解決方案

4.1 在模擬TiDB回滾至MySQL過程中出現主鍵衝突問題

在TiDB資料增長3倍後,切換回MySQL出現主鍵重複異常,具體日誌內容如下:

主鍵衝突異常日誌

產生該問題的主要原因為每個 TiDB 節點在分配主鍵ID時,都申請一段 ID 作為快取,用完之後再去取下一段,而不是每次分配都向儲存節點申請。這意味著,TiDB的AUTO_INCREMENT自增值在單節點上能保證單調遞增,但在多個節點下則可能會存在劇烈跳躍。因此,在多節點下,TiDB的AUTO_INCREMENT自增值從全域性來看,並非絕對單調遞增的,也即並非絕對有序的,從而導致Metastore庫裡的SEQUENCE_TABLE表記錄的值不是對應表的最大值。

造成主鍵衝突的主要原因是SEQUENCE_TABLE表記錄的值不為後設資料中實際的最大值,若存在該情況在切換回MySQL後就有可能生成已存在的主鍵導致初見衝突異常,此時只需將SEQUENCE_TABLE裡的記錄值設定當前實際表中的最大值即可。

4.2 PARTITION_KEY_VALS的索引取捨

在使用MySQL引擎中,我們收集了部分慢查詢日誌,該類查詢主要是查詢分割槽表的分割槽,類似如下SQL:

#以下查詢為查詢三級分割槽表模板,且每級分割槽都有過來條件

SELECT PARTITIONS.PART_ID
FROM PARTITIONS
  INNER JOIN TBLS
  ON PARTITIONS.TBL_ID = TBLS.TBL_ID
    AND TBLS.TBL_NAME = '${TABLE_NAME}'
  INNER JOIN DBS
  ON TBLS.DB_ID = DBS.DB_ID
    AND DBS.NAME = '${DB_NAME}'
  INNER JOIN PARTITION_KEY_VALS FILTER0
  ON FILTER0.PART_ID = PARTITIONS.PART_ID
    AND FILTER0.INTEGER_IDX = ${INDEX1}
  INNER JOIN PARTITION_KEY_VALS FILTER1
  ON FILTER1.PART_ID = PARTITIONS.PART_ID
    AND FILTER1.INTEGER_IDX = ${INDEX2}
  INNER JOIN PARTITION_KEY_VALS FILTER2
  ON FILTER2.PART_ID = PARTITIONS.PART_ID
    AND FILTER2.INTEGER_IDX = ${INDEX3}
WHERE FILTER0.PART_KEY_VAL = '${PART_KEY}'
  AND CASE 
    WHEN FILTER1.PART_KEY_VAL <> '__HIVE_DEFAULT_PARTITION__' THEN CAST(FILTER1.PART_KEY_VAL AS decimal(21, 0))
    ELSE NULL
  END = 10
  AND FILTER2.PART_KEY_VAL = '068';

在測試中透過控制併發重放該型別的SQL,隨著併發的增加,各個API的平均耗時也會增長,且重放的SQL查詢耗時隨著併發的增加查詢平均耗時達到100s以上,雖然TiDB及HMS在壓測期間沒有出現任何異常,但顯然這種查詢效率會讓使用者很難接受。DBA分析該查詢沒有選擇合適的索引導致查詢走了全表掃描,建議對PARTITION_KEY_VALS的PARTITION_KEY_VAL欄位新增了額外的索引以加速查詢,最終該型別的查詢得到了極大的最佳化,即使加大併發到100的情況下平均耗時在500ms內,對此我們曾嘗試對PARTITION_KEY_VALS新增上述索引操作。

但線上上實際的查詢中,那些沒有產生慢查詢的分割槽查詢操作其實都是按天分割槽的進行一級分割槽查詢的,其SQL類似如下:

SELECT "PARTITIONS"."PART_ID"
FROM "PARTITIONS"
  INNER JOIN "TBLS"
  ON "PARTITIONS"."TBL_ID" = "TBLS"."TBL_ID"
    AND "TBLS"."TBL_NAME" = 'tb1'
  INNER JOIN "DBS"
  ON "TBLS"."DB_ID" = "DBS"."DB_ID"
    AND "DBS"."NAME" = 'db1'
  INNER JOIN "PARTITION_KEY_VALS" "FILTER0"
  ON "FILTER0"."PART_ID" = "PARTITIONS"."PART_ID"
    AND "FILTER0"."INTEGER_IDX" = 0
  INNER JOIN "PARTITION_KEY_VALS" "FILTER1"
  ON "FILTER1"."PART_ID" = "PARTITIONS"."PART_ID"
    AND "FILTER1"."INTEGER_IDX" = 1
WHERE "FILTER0"."PART_KEY_VAL" = '2021-12-28'
  AND CASE 
    WHEN "FILTER1"."PART_KEY_VAL" <> '__HIVE_DEFAULT_PARTITION__' THEN CAST("FILTER1"."PART_KEY_VAL" AS decimal(21, 0))
    ELSE NULL
  END = 10;

由於對PARTITION_KEY_VALS的PARTITION_KEY_VAL欄位新增了索引做查詢最佳化,會導致該類查詢生成的執行計劃中同樣會使用idx_PART_KEY_VAL索引進行資料掃描,該執行計劃如下:

圖片

走idx_PART_KEY_VAL索引執行計劃

新增的idx_PART_KEY_VAL索引在該欄位的具有相同值的資料較少時,使用該索引能檢索較少的資料提升查詢效率。在hive中的表一級分割槽基本是按天進行分割槽的,據統計每天天分割槽的增量為26w左右,如果使用idx_PART_KEY_VAL索引,按這個數值計算,查詢條件為day>=2021-12-21 and day<2021-12-26的查詢需要檢索將近160w條資料,這顯然不是一個很好的執行計劃。

若執行計劃不走idx_PART_KEY_VAL索引,TiDB可透過dbs、tbls檢索出所有關聯partition資料,在根據part_id和過濾條件掃描PARTITION_KEY_VALS資料並返回。此類執行計劃掃描的資料量和需要查詢的表的分割槽總量有關,如果該表只有少數的分割槽,則查詢能夠迅速響應,但如果查詢的表有上百萬的分割槽,則該類執行計劃對於該類查詢不是最優解。

圖片

不走idx_PART_KEY_VAL索引執行計劃

針對不同執行計劃的特性,整理了以下對比點:

圖片

在實際生產中後設資料基本都是按天分割槽為主,每天增長大概有26w左右,且範圍查詢的使用場景較多,使用idx_PART_KEY_VAL索引查詢的執行計劃不太適合線上場景,故該索引需不適合新增到線上環境。

4.3 TiDB記憶體突增導致當機問題

在剛上線TiDB服務初期,曾數次面臨TiDB記憶體溢位的問題,每次出現的時間都隨機不確定,出現的時候記憶體突增幾乎在一瞬間,若期間TiDB的記憶體抗住了突增量,突增部分記憶體釋放在很長時間都不會得到釋放,最終對HMS服務穩定性帶來抖動。

圖片

TiDB記憶體突增情況

透過和TiDB開發、DBA聯合分析下,確認TiDB記憶體飆高的原因為使用者在使用Dashboard功能分析慢查詢引起;在分析慢查詢過程中,TiDB需要載入本地所有的slow-query日誌到記憶體,如果這些日誌過大,則會造成TiDB記憶體突增,此外,如果在分析期間,使用者點選了取消按鈕,則有可能會造成TiDB的記憶體洩漏。針對該問題制定如下解決方案:

  1. 使用大記憶體機器替換原小記憶體機器,避免分析慢查詢時記憶體不夠
  2. 調大慢查詢閾值為3s,減少日誌產生
  3. 定時mv慢查詢日誌到備份目錄

4.4 locate函式查詢不走索引導致TiKV負異常

在HMS中存在部分透過JDO的方式去獲取分割槽的查詢,該類查詢的過濾條件中用locate函式過濾PART_NAME資料,在TiDB中透過函式作用在欄位中是不會觸發索引查詢的,所以在該類查詢會載入對應表的所有資料到TiDB端計算過濾,TiKV則需不斷掃描全表並傳輸資料到TiDB段,從而導致TiKV負載異常。

圖片

locate函式導致全表掃描

然而上述的查詢條件可以透過like方式去實現,透過使用like語法,查詢可以成功使用到PARTITIONS表的UNIQUEPARTITION索引過濾,進而在TiKV端進行索引過濾降低負載。

圖片

like語法走索引過濾

透過實現將locate函式查詢轉換為like語法查詢,有效降低了TiKV端的負載情況。在HMS端完成變更後,TiKV的CPU使用率降低了將近一倍,由於在KV端進行索引過濾,相應的io使用率有所上升,但網路傳輸則有明顯的下降,由平均1G降低到200M左右。

圖片

變更前後TiKV的負載情況

除TiKV負載有明顯的降低,TiDB的整體效能也得到明顯的提升,各項操作耗時呈量級降低。以下整理了TiDB增刪改查的天平均耗時情況:

圖片

TiDB P999天平均耗時統計

4.5 get_all_functions最佳化

隨著hive udf的不斷增長,HMS的get_all_functions api平均耗時增長的也越來越久,平均在40-90s,而該api在hive shell中首次執行查詢操作時會被呼叫註冊所有的udf,過長的耗時會影響使用者對hive引擎的使用體驗,例如執行簡單的show database需要等待一分鐘甚至更久才能返回結果。

圖片

原get_all_functions api平均耗時

導致該api耗時嚴重的主要原因是HMS透過JDO方式獲取所有的Function,在獲取所有的udf時後臺會遍歷每條func去關聯DBS、FUNC_RU兩個表,獲取效能極低。而使用directSQL的方式去獲取所有udf資料,響應耗時都在1秒以內完成,效能提升相當明顯。以下為directSQL的SQL實現邏輯:

select FUNCS.FUNC_NAME,
  DBS.NAME,
  FUNCS.CLASS_NAME,
  FUNCS.OWNER_NAME,
  FUNCS.OWNER_TYPE,
  FUNCS.CREATE_TIME,
  FUNCS.FUNC_TYPE,
  FUNC_RU.RESOURCE_URI,
  FUNC_RU.RESOURCE_TYPE
from FUNCS
left join FUNC_RU on FUNCS.FUNC_ID = FUNC_RU.FUNC_ID
left join DBS on FUNCS.DB_ID = DBS.DB_ID

五、總結

我們從2021年7月份開始對TiDB進行調研,在經歷數個月的測試於同年11月末將MySQL引擎切換到TiDB。由於前期測試主要集中在相容性和效能測試上,忽略了TiDB自身可能潛在的問題,在上線初期經歷了數次因慢查詢日誌將TiDB記憶體打爆的情況,在這特別感謝我們的DBA團隊、平臺運營團隊及TiDB官方團隊幫忙分析、解決問題,得以避免該問題的再次發生;與此同時,由於當前HMS使用的版本較低,加上大資料的元件在不斷的升級演進,我們也需要去相容升級帶來的變動,如HDFS升級到3.x後對EC檔案讀取的支援,SPARK獲取分割槽避免全表掃描改造等;此外由於TiDB的latin字符集支援中文字元的寫入,該特性會導致使用者誤寫入錯誤的中文分割槽,對於此型別資料無法透過現有API進行刪除,還需要在應用層去禁止該型別錯誤分割槽寫入,避免無用資料累積。

經歷了一年多的實際生產環境檢驗,TiDB記憶體整體使用在10%以內,TiKV CPU使用平穩,使用峰值均在30核內,暫不存在系統瓶頸;HMS服務的穩定性整體可控,關鍵API效能指標滿足業務的實際需求,為業務的增長提供可靠支援。在未來三年內,我們將保持該架構去支撐整個大資料平臺元件的穩定執行,期間我們也將持續關注行業內的變動,吸收更多優秀經驗應用到我們的生產環境中來,包括但不限於對效能更好的高版本TiDB嘗試,HMS的效能最佳化案例。

相關文章