一. 概述
HBase 是一個基於 Google BigTable 論文設計的高可靠性、高效能、可伸縮的分散式儲存系統。 網上關於 HBase 的文章很多,官方文件介紹的也比較詳細,本篇文章不介紹HBase基本的細節。
本文從 HBase 寫鏈路開始分析,然後針對少量隨機讀和海量隨機寫入場景入手,全方面量化分析各種資源的開銷, 從而做到以下兩點:
- 在給定業務量級的情況下,預先評估好叢集的合理規模
- 在 HBase 的眾多引數中,選擇合理的配置組合
二. HBase 寫鏈路簡要分析
HBase 的寫入鏈路基於 LSM(Log-Structured Merge-Tree), 基本思想是把使用者的隨機寫入轉化為兩部分寫入:
Memstore 記憶體中的 Map, 儲存隨機的隨機寫入,待 memstore 達到一定量的時候會非同步執行 flush 操作,在 HDFS 中生成 HFile 中。 同時會按照寫入順序,把資料寫入一份到 HDFS 的 WAL(Write Ahead Log)中,用來保證資料的可靠性,即在異常(當機,程式異常退出)的場景下,能夠恢復 Memstore 中還沒來得及持久化成 HFile 的資料.
三. Flush & Compaction
上一節中,介紹了 HBase 的寫路徑,其中 HFile 是 HBase 資料持久化的最終形態, 本節將介紹 HBase 如何生成 HFile 和管理 HFile。關於 HFile, 主要涉及到兩個核心操作:
- Flushing
- Compaction
上一節中提到,HBase 的寫入最先會放入記憶體中,提供實時的查詢,當 Memstore 中資料達到一定量的閾值(128MB),會通過 Flush 操作生成 HFile 持久化到 HDFS 中,隨著使用者的寫入,生成的 HFile 數目會逐步增多,這會影響使用者的讀操作,同時也會系統佔用(HDFS 層 block 的數目, regionserver 伺服器的檔案描述符佔用), region split 操作,region reopen 操作也會受到不同程度影響。 HBase 通過 Compaction 機制將多個 HFile 合併成一個 HFile 以控制每個 Region 內的 HFile 的數目在一定範圍內, 當然 Compaction 還有其他的作用,比如資料本地化率,多版本資料的合併,資料刪除標記的清理等等,本文不做展開。
另外還有一點需要知道的是,HBase 中 Flush 操作和 Compaction 操作和讀寫鏈路是由獨立執行緒完成的,互不干擾。
四. 系統開銷定量分析
為了簡化計算,本節針對事件類資料寫吞吐型場景,對 HBase 系統中的開銷做定量的分析,做以下假設:
- 資料寫入的 Rowkey 是打散的,不存在寫熱點
- 資料寫入量及總量是可評估的,會對資料做預先分割槽,定量分析基於 region 分佈穩定的情況下
- 假設隨機讀的數目很小,小到可以忽略 IO 開銷,且對讀 RT 不敏感
- 資料沒有更新,沒有刪除操作,有生命週期TTL設定
- HBase 寫入鏈路中不存在隨機磁碟,所以隨機 IOPS 不會成為瓶頸
- 一般大資料機型的多個 SATA 盤的順序寫吞吐大於萬兆網路卡
- 忽略掉RPC帶來的額外的頻寬消耗
4.1 系統變數
- 單條資料大小 -> s (bytes)
- 峰值寫 TPS -> T
- HFile 副本數→ R1 (一般為3)
- WAL 副本數 → R2 (一般為3)
- WAL 資料壓縮比 → Cwal (一般是1)
- HFile 壓縮比 → C (採用 DIFF + LZO, 日誌場景壓縮比一般為 0.2左右)
- FlushSize → F (這裡跟 regionserver 的 memstore 記憶體容量,region 數目,寫入是否平均和 flushsize 的配置有關,簡化分析,認為記憶體是足夠的 128MB)
- hbase.hstore.compaction.min → CT (預設是 3, 一般情況下,決定了歸併係數,即每次 compaction 參與的檔案數目,在不存在 compaction 積壓的情況下, 實際執行時也是在 3 左右)
- 資料生命週期 → TTL (決定資料量的大小,一般寫吞吐場景,日誌會有一定的儲存週期, 單位天)
- 單機資料量水位 → D ( 單位 T,這裡指 HDFS 上存放 HFile 資料的資料量平均分擔到每臺機器上)
- MajorCompaction 週期 → M( hbase.hregion.majorcompaction 決定,預設 20 天)
以上 11 個引數,是本次量化分析中需要使用到的變數,系統資源方面主要量化以下兩個指標:
- 磁碟開銷
- 網路開銷
4.2 磁碟容量開銷量化分析
這裡只考慮磁碟空間方面的佔用,相關的變數有:
- 單條資料大小 s
- 峰值寫入 TPS
- HFile 副本數 R1
- HFile 壓縮比 c
- 資料生命週期 TTL
HFile的磁碟容量量化公式
V = TTL * 86400 * T * s * C * R1
假設 s = 1000, TTL = 365, T = 200000, C = 0.2 , R1 = 3 的情況下,HFile 磁碟空間需求是:
V = 30 * 86400 * 200000 * 1000 * 0.2 * 3
= 311040000000000.0 bytes
= 282T
複製程式碼
在這裡我們忽略了其他佔用比較小的磁碟開銷,比如:
- WAL的磁碟開銷,在沒有 Replication,寫入平均的情況下,WAL 的日誌量約定於 (hbase.master.logcleaner.ttl /1000) * s * TPS + totalMemstoreSize
- Compaction 臨時檔案,Split 父 Region 檔案等臨時檔案
- Snapshot 檔案
- 等等
4.3 網路開銷量化分析
HBase中會造成巨大網路開銷的主要由一下三部分組成,他們是相互獨立,非同步進行的,這裡做個比方,HBase 這三個操作和人吃飯很像,這裡做個類比
迴歸正題,下面按照發生順序,從三個角度分別分析:
- 寫路徑
- Flush
- Compaction
4.3.1 寫路徑
寫路徑的網路開銷,主要是寫 WAL 日誌方面, 相關的變數有:
- 單條資料大小 s
- 峰值寫入 TPS
- WAL 副本數 R2
- WAL 壓縮比 Cwal
寫路徑中,產生的網路流量分為兩部分,一部分是寫 WAL 產生的流量,一部分是外部使用者 RPC 寫入的流量, In 流量和 Out 流量計算公式為:
NInWrite = T * s * Cwal * (R2 - 1) + (T * s )
NOutWrite = T * s * Cwal * (R2 - 1)
假設 T = 20W,s = 1000, Cwal = 1.0, R2 = 3
NInwrite = 200000 * 1000 * 1 * (3-1) + 200000 * 1000
= 600000000 bytes/s
= 572MB/s
NOutwrite = 200000 * 1000* 1 * (3-1)
= 400000000 bytes/s
= 381MB/s
複製程式碼
4.3.2 Flush
Flush 的網路開銷,主要是生成 HFile 後,將 HFile 寫入到 HDFS 的過程,相關的變數有:
- 單條資料大小 s
- 峰值寫入 T
- HFIle 副本數 R1
- HFile 壓縮比 C
Flush 產生的 In 流量和 Out 流量計算公式為:
NInWrite = s * T * (R1 - 1) * C
NOutWrite = s * T * (R1 - 1) * C
假設 T = 20W, S = 1000, R1 = 3, C = 0.2
NInwrite = 200000 * 1000 * (3 - 1) * 0.2
= 80000000.0 bytes/s
=76.3MB/s
NOutwrite = 200000 * 1000 * (3 - 1) * 0.2
= 120000000.0 bytes/s
=76.3MB/s
複製程式碼
4.3.3 Compaction
Compaction 比較複雜,在有預分割槽不考慮 Split 的情況下分為兩類:
- Major Compaction
- Minor Compaction
兩者是獨立的,下面將分別針對兩種 Compaction 做分析,最後取和:
4.3.3.1 Major Compaction
Major Compaction 的定義是由全部 HFile 參與的 Compaction, 一般在發生在 Split 後發生,或者到達系統的 MajorCompaction 週期, 預設的 MajorCompaction 週期為 20 天,這裡我們暫時忽略 Split 造成的 MajorCompaction 流量. 最終 Major Compaction 開銷相關的變數是:
- 單機資料量水位 D
- HFIle 副本數 R1
- MajorCompaction 週期 → M (預設 20 天)
這裡假設資料是有本地化的,所以 MajorCompaction 的讀過程,走 ShortCircuit,不計算網路開銷,並且寫 HFile 的第一副本是本地流量,也不做流量計算,所以 MajorCompaction 的網路流量計算公式是:
NInMajor = D * (R1 - 1) / M
NOutMajor = D * (R1 - 1) / M
假設 D = 10T, R1 = 3, M = 20
NInMajor = 10 * 1024 * 1024 * 1024 * 1024 * (3 - 1) / (20 * 86400)
= 12725829bytes/s
= 12MB/s
NOutMajor = 10 * 1024 * 1024 * 1024 * 1024 * (3 - 1) / (20 * 86400)
= 12725829bytes /s
= 12MB/s
複製程式碼
4.3.3.2 Minor Compaction
量化之前,先問一個問題,每條資料在第一次 flush 成為 HFile 之後,會經過多少次 Minor Compaction?
要回答這個問題之前,要先了解現在 HBase 預設的 compaction 的檔案選取策略,這裡不展開,只做簡單分析,MinorCompaction 選擇的檔案物件數目,一般處於 hbase.hstore.compaction.min(預設 3)和 hbase.hstore.compaction.max(預設 10)之間, 總檔案大小小於 hbase.hstore.compaction.max.size(預設 Max), 如果檔案的 Size 小於 hbase.hstore.compaction.min.size(預設是 flushsize), 則一定會被選中; 並且被選中的檔案size的差距不會過大, 這個由引數 hbase.hstore.compaction.ratio 和 hbase.hstore.compaction.ratio.offpeak 控制,這裡不做展開.
所以,在 Compaction 沒有積壓的情況下,每次 compaction 選中的檔案數目會等於 hbase.hstore.compaction.min 並且檔案 size 應該相同量級, 對穩定的表,對每條資料來說,經過的 compaction 次數越多,其檔案會越大. 其中每條資料參與 Minor Compaction 的最大次數可以用公式 math.log( 32000 / 25.6, 3) = 6 得到
這裡用到的兩個變數是:
- FlushSize 預設是 128 MB
- HFile 壓縮比例,假設是 0.2
所以剛剛 Flush 生成的 HFile 的大小在 25.6MB 左右,當集齊三個 25.6MB 的 HFile 後,會觸發第一次 Minor Compaction, 生成一個 76.8MB 左右的 HFile
對於一般情況,單個 Region 的檔案 Size 我們會根據容量預分割槽好,並且控制單個 Region 的 HFile 的總大小 在 32G 以內,對於一個 Memstore 128MB, HFile 壓縮比 0.2, 單個 Region 32G 的表,上表中各個 Size 的 HFile 數目不會超過 2 個(否則就滿足了觸發 Minor Compaction 的條件)
32G = 18.6G + 6.2G + 6.2G + 690MB + 230MB + 76.8MB + 76.8MB
到這裡,我們知道每條寫入的資料,從寫入到 TTL 過期,經過 Minor Compaction 的次數是可以計算出來的。 所以只要計算出每次 Compaction 的網路開銷,就可以計算出,HBase 通過 Minor Compaction 消化每條資料,所佔用的總的開銷是多少,這裡用到的變數有:
- 單條資料大小 s
- 峰值寫入 T
- HFIle 副本數 R1
- HFile 壓縮比 C
計算公式如下:
NInMinor = S * T * (R1-1) * C * 總次數
NOutMinor = S * T * (R1-1) * C * 總次數
假設 S = 1000, T = 20W, R1 = 3, C = 0.2, 總次數 = 6
NInminor = 1000 * 200000 * (3 - 1) * 0.2 * 6
= 480000000.0bytes/s
= 457.8MB/s
NOutminor = 1000 * 200000 * (3 - 1) * 0.2 * 6
= 480000000.0bytes/s
= 457.8MB/s
複製程式碼
4.3.4 網路資源定量分析小結
在使用者寫入 TPS 20W, 單條資料大小 1000 bytes的場景下,整體網路吞吐為:
NIntotal = NInwrite + NInflush + NInmajor + NInminor
= 572MB/s + 76.3MB/s + 12MB/s + 457.8MB/s
= 1118.1MB/s
NOuttotal = NOutwrite + NOutflush + NOutmajor + NOutminor
= 381MB/s + 76.3MB/s + 12MB/s + 457.8MB/s
= 927.1MB
複製程式碼
當然這是理想情況下的最小開銷,有很多種情況,可以導致實際網路開銷超過這個理論值, 以下情況都會導致實際流量的升高:
- 預分割槽不足或者業務量增長,導致 Region 發生 Split, Split 會導致額外的 Compaction 操作
- 分割槽寫入不平均,導致少量 region 不是因為到達了 flushsize 而進行 flush,導致 flush 下來的檔案 Size 偏小
- HFile 因為 balance 等原因導致本地化率低,也會導致 compaciton 產生更多的網路卡開銷
- 預分割槽數目過多,導致全域性 memstore 水位高,memstore 沒辦法到達 flushsize 進行 flush,從而全域性都 flush 出比較小的檔案
- 等等
有了這個量化分析後,我們能做什麼優化呢? 這裡不深入展開,簡單說幾點已經在有贊生產環境得到驗證具有實效的優化點:
- 業務接入初期,協助業務做 Rowkey 的設計,避免寫入熱點
- 增加 hbase.hstore.compaction.min,增加每次 Compaction參加的檔案數,相當於減少了每條資料整個生命週期經歷過的 Compaction 次數
- 根據業務穩態的規模,做好預分割槽,儘量減少 Split 造成的額外開銷
- 對於讀 RT 不敏感的業務,可以設定 hbase.hstore.compaction.max.size 為 4g,儘可能減少過大的檔案做 Compaction,因為大檔案做 compaction 的 ROI 實在太低
- 對於沒有多版本並且有 TTL 的資料,可以關閉系統的 MajorCompaction 週期,資料過期採用檔案整體過期的方式,消除 MajorCompaction 的系統開銷
- 對於吞吐大的場景,使用者在寫入資料的時候就對資料做壓縮,減小寫路徑造成的網路開銷,畢竟 WAL 是不能壓縮的(壓縮功能形同虛設)
- 調整 Memstore 的記憶體比例,保證單機上每個 Region 儘可能的分配到 Flushsize 大小的記憶體,儘可能的 flush 大檔案,從而減少後續 Compaction 開銷
五. 總結
到這裡,HBase 的寫吞吐場景的資源定量分析和優化的介紹就算結束了,本文基於 HBase1.2.6 版本。 對很多 HBase 的細節沒有做展開說明,有些地方因為作者認知有限,難免紕漏,歡迎各位同行指出。
最後打個小廣告,有贊大資料團隊基礎設施團隊,主要負責有讚的資料平臺(DP), 實時計算(Storm, Spark Streaming, Flink),離線計算(HDFS,YARN,HIVE, SPARK SQL),線上儲存(HBase),實時 OLAP(Druid) 等數個技術產品,歡迎感興趣的小夥伴聯絡 hefei@youzan.com
參考文獻