下午好,我叫 Danil Lipovoy,我們 Sbertech 的團隊開始使用 HBase 作為營運資料儲存。在學習過程中,我累積了一些經驗,希望將其係統化並描述出來(希望對大家有用)。以下所有實驗均使用 HBase 版本 1.2.0-cdh5.14.2 和 2.0.0-cdh6.0.0-beta1 進行。
- 通用架構
- 將資料寫入HBASE
- 從HBASE讀取數據
- 資料快取
- MultiGet/MultiPut 批次資料處理
- 表拆分為區域(溢出)的策略
- 容錯、壓縮和資料局部性
- 設定和性能
- 壓力測試
- 發現
1. 總體架構

備份 Master 監聽節點上活動 ZooKeeper 的心跳,並在 Master 消失的情況下接手其功能。
2. 寫入資料到HBASE
首先,讓我們考慮最簡單的情況-使用 put(rowkey) 將鍵值物件寫入表中。客戶端必須先找到根區域伺服器 (RRS) 的位置,該伺服器儲存了 hbase:meta 表。他從 ZooKeeper 獲取此資訊。然後,它存取 RRS 並讀取 hbase:meta 表,從中提取有關哪個 RegionServer (RS) 負責儲存它感興趣的表中給定行鍵的資料的資訊。為了將來使用,客戶端會快取元表,以便後續呼叫速度更快,直接呼叫 RS。
接下來,RS 在收到請求後,首先將其寫入 WriteAheadLog (WAL),這對於發生崩潰時的恢復是必需的。然後將資料儲存在MemStore中。它是記憶體中的一個緩衝區,其中包含給定區域的一組已排序的鍵。表可以分為多個區域(分區),每個區域包含一組不相交的鍵。透過將區域放置在不同的伺服器上,您可以獲得更高的效能。然而,儘管這個說法很明顯,但我們稍後會看到,它並不在所有情況下都有效。
記錄放入MemStore後,會向客戶端傳回記錄保存成功的回應。實際上,它只儲存在緩衝區中,只有經過一段時間或填充新資料後才會寫入磁碟。

執行「刪除」操作時,不會發生實體資料刪除。它們只是被標記為已刪除,而銷毀本身發生在呼叫主要壓縮函數時,這將在第 7 段中詳細描述。
HFile 檔案儲存在 HDFS 中,有時會執行一個小的壓縮過程,該過程只是將小檔案貼到大檔案中,而不會刪除任何內容。隨著時間的推移,這將成為一個僅在讀取資料時才會顯現的問題(我們稍後會討論這個問題)。
除了上面描述的載入過程之外,還有一個更有效率的過程,這也許是這個資料庫最強的點 - BulkLoad。它包括我們獨立形成 HFile 並將它們放在磁碟上,這使我們能夠完美地擴展並實現非常好的速度。其實這裡的限制不是HBase,而是硬體的能力。以下是在由16個RegionServers和16個NodeManagers組成的YARN(CPU Xeon E5-2680 v4 @ 2.40GHz * 64執行緒)叢集上載入的結果,HBase版本1.2.0-cdh5.14.2。

這裡您可以看到,透過增加表中分區(區域)的數量以及 Spark 執行器的數量,我們可以提高載入速度。速度也取決於錄音音量。在其他所有條件相同的情況下,大塊可以增加 MB/秒的測量值,小塊可以增加單位時間內插入的記錄數。
您還可以同時運行兩個表的加載並獲得兩倍的速度。下面你可以看到,同時將 10 KB 區塊寫入兩個表的速度大約為每張 600 MB/秒(總共 1275 MB/秒),這與寫入一張表的速度 623 MB/秒相同(參見上面的第 11 條)

但第二次啟動50KB記錄時,發現下載速度只略有增長,表示正在接近極限值。重要的是要記住,這裡 HBASE 本身實際上沒有任何負載,它所需要做的就是首先從 hbase:meta 提供數據,並在 HFiles 支援之後重置 BlockCache 數據並將 MemStore 緩衝區保存到磁碟(如果它不為空)。
3.從HBASE讀取數據
如果我們假設來自 hbase:meta 的所有資訊都已經在客戶端中(請參閱第 2 點),那麼請求將直接轉到儲存所需金鑰的 RS。首先,在MemCache中執行搜尋。無論是否有數據,搜尋也會在 BlockCache 緩衝區中執行,如果需要,也會在 HFile 中執行。如果在文件中找到數據,則將其放置在 BlockCache 中,並將在下一次請求時更快地返回。 HFile中的搜尋相對較快,因為使用了Bloom過濾器,也就是在讀取少量資料後,立即判斷這個檔案是否包含所需的key,如果不包含,請轉到下一個檔案。

從這三個來源收到資料後,RS 會產生回應。特別是,如果客戶端請求版本控制,它可以一次傳輸物件的多個版本。
4.資料緩存
MemStore 和 BlockCache 緩衝區佔用了分配的堆上 RS 記憶體的 80%(其餘部分保留用於 RS 服務任務)。如果典型的使用模式是進程寫入並立即讀取相同的數據,那麼減少 BlockCache 並增加 MemStore 是有意義的,因為當將資料寫入快取進行讀取時,它不會到達那裡,因此 BlockCache 的使用頻率會較低。 BlockCache 緩衝區由兩部分組成:LruBlockCache(始終位於堆上)和 BucketCache(通常位於堆外或 SSD 上)。當讀取請求很多,LruBlockCache容不下時,就需要使用BucketCache,因為這樣會導致垃圾收集器開始運作。同時,您不應該期望使用讀取快取會大幅提高效能,但我們將在第 8 點中再次討論這一點。

整個 RS 有一個 BlockCache,每個表都有自己的 MemStore(每個 Column Family 一個)。
如 理論上,當資料寫入快取時,它並沒有到達那裡,事實上,表的CACHE_DATA_ON_WRITE和RS的「寫入時快取資料」等參數都設定為false。然而,在實踐中,如果我們將資料寫入 MemStore,然後將其刷新到磁碟(從而清除它),然後刪除生成的文件,那麼透過執行 get 請求我們將成功接收資料。此外,即使你完全停用 BlockCache 並用新資料填充表,然後將 MemStore 重置到磁碟,刪除它們並從另一個會話請求它們,它們仍然會從某個地方檢索到。所以HBase儲存的不只是數據,還有神秘的奧秘。
hbase(main):001:0> create 'ns:magic', 'cf'
Created table ns:magic
Took 1.1533 seconds
hbase(main):002:0> put 'ns:magic', 'key1', 'cf:c', 'try_to_delete_me'
Took 0.2610 seconds
hbase(main):003:0> flush 'ns:magic'
Took 0.6161 seconds
hdfs dfs -mv /data/hbase/data/ns/magic/* /tmp/trash
hbase(main):002:0> get 'ns:magic', 'key1'
cf:c timestamp=1534440690218, value=try_to_delete_me
“讀取時快取資料”參數設定為 false。如果您有任何想法,請在評論中隨意討論。
5.批量資料處理MultiGet/MultiPut
處理單一請求(Get/Put/Delete)是一項相當昂貴的操作,因此您應該盡可能將它們組合成一個列表或列表,這可以顯著提高效能。對於寫入操作尤其如此,但讀取時存在以下陷阱。下圖顯示了從 MemStore 讀取 50 筆記錄的時間。讀取在一個執行緒中完成,橫軸顯示查詢中的鍵數。這裡可以看到,當一次請求中增加到一千個鍵時,執行時間會下降,即速度會增加。然而,在預設啟用MSLAB模式的情況下,超過這個閾值後,效能開始急劇下降,記錄中的資料量越大,運行時間越長。

測試在虛擬機器、8核心、HBase 版本 2.0.0-cdh6.0.0-beta1 上進行。
MSLAB 模式旨在減少因新一代和舊一代資料混合而發生的堆疊碎片。為了解決這個問題,當啟用 MSLAB 時,資料被放入相對較小的單元(區塊)並分成部分處理。因此,當請求的資料包量超出分配的大小時,效能就會急劇下降。另一方面,停用此模式也是不可取的,因為它會導致在資料密集處理期間因 GC 而停止。一個好的解決方案是增加單元容量,以便同時透過 put 進行主動寫入和讀取。值得注意的是,如果在錄製之後執行將 MemStore 刷新到磁碟的 flush 命令,或使用 BulkLoad 加載,則不會出現此問題。下表顯示,來自 MemStore 的較大(和相同數量)資料的查詢會導致速度變慢。然而,透過增加區塊大小,我們可以將處理時間恢復正常。

除了增加區塊大小之外,按區域拆分資料也有幫助,例如表拆分。這樣一來,到達每個區域的請求就會減少,而且如果這些請求適合該單元,則回應仍然良好。
6. 表格分區策略(spilling)
由於 HBase 是一個鍵值存儲,並且按鍵進行分區,因此在所有區域之間均勻劃分資料非常重要。例如,將這樣的表格分區為三個部分將導致資料分成三個區域:

如果稍後載入的資料是長值且大多以相同的數字開頭,這有時會導致速度急劇下降,例如:
1000001
1000002
...
1100003
由於鍵儲存為位元組數組,因此它們都以相同的方式開始並屬於儲存該範圍鍵的相同區域 #1。有多種分區策略:
HexStringSplit – 將鍵轉換為十六進位編碼的字串,範圍在「00000000」=>「FFFFFFFF」內,左側用零填滿。
UniformSplit – 將金鑰轉換為一個位元組數組,其十六進位編碼範圍為“00”=>“FF”,右側用零填充。
此外,您可以指定任意範圍或一組鍵進行拆分並配置自動拆分。然而,最簡單、最有效的方法之一是 UniformSplit 和使用雜湊連接,例如透過 CRC32(rowkey) 函數運行鍵得到的高級位元組對和行鍵本身:
哈希+行鍵
然後所有數據將均勻分佈在各個區域。讀取時,前兩個位元組被丟棄,原始密鑰保留。 RS 還控制區域中的資料和金鑰的數量,如果超出限制,則自動將其分成幾個部分。
7. 容錯與資料局部性
由於每個金鑰集僅負責一個區域,因此與 RS 崩潰或退役相關的問題的解決方案是將所有必要的資料儲存在 HDFS 中。當 RS 發生故障時,主伺服器會透過 ZooKeeper 節點上缺少心跳來偵測到這種情況。然後,它將要服務的區域分配給另一個 RS,並且由於 HFile 儲存在分散式檔案系統中,新所有者會讀取它們並繼續提供資料。但是,由於部分資料可能在 MemStore 中,而沒有時間到達 HFiles,因此使用同樣儲存在 HDFS 中的 WAL 來恢復操作歷史記錄。在變更推出之後,RS 能夠回應請求,但此舉會導致部分資料及其服務流程位於不同的節點上,即本地性正在降低。
解決這個問題的方法是進行大規模壓縮——此過程將檔案移至負責它們的節點(它們的區域所在位置),因此在此過程中網路和磁碟上的負載會急劇增加。然而,在未來,數據存取速度將明顯加快。此外,major_compaction 會將某個區域內的所有 HFile 合併為一個文件,也會根據表格設定清理資料。例如,您可以指定必須儲存的物件的版本數或其生命週期,此後該物件將被物理刪除。
此過程可以對 HBase 的性能產生非常積極的影響。下圖顯示了由於大量資料寫入而導致的效能下降情況。這裡您可以看到 40 個執行緒如何寫入一個表格以及 40 個執行緒如何同時讀取資料。寫入執行緒產生越來越多的 HFile,由其他執行緒讀取。結果,越來越多的資料需要從記憶體中刪除,最終 GC 開始工作,這實際上會使所有工作陷入癱瘓。大規模壓實的啟動清理了累積的堵塞物並恢復了生產力。

測試在 3 個 DataNode 和 4 個 RS(CPU Xeon E5-2680 v4 @ 2.40GHz * 64 執行緒)上進行。 HBase 版本 1.2.0-cdh5.14.2
值得注意的是,主要壓縮是在「即時」表上啟動的,資料被主動寫入和讀取到該表。網路上有說法稱,這可能會導致讀取資料時出現錯誤的回應。為了測試這一點,我們啟動了一個產生新資料並將其寫入表的進程。之後我立即讀取並檢查收到的值是否與寫入的值相符。在這個過程的操作過程中,大約進行了200次大規模壓實,沒有記錄到一次失敗。也許該問題很少發生,並且僅在高負載期間發生,因此定期停止寫入和讀取過程並執行清理以防止此類 GC 下降是更安全的做法。
此外,主要壓縮不會影響 MemStore 的狀態;要將其刷新到磁碟並壓縮它,您需要使用 flush (connection.getAdmin().flush(TableName.valueOf(tblName)))。
8. 設定和效能
如上所述,HBase 最成功的地方在於,在執行 BulkLoad 時它不需要做任何事情。然而,這適用於大多數系統和人。但是,此工具更適合大塊的批量資料放置,而如果該過程需要多個並發的讀寫請求,則使用上面描述的 Get 和 Put 命令。為了確定最佳參數,使用表格參數和設定的各種組合進行了運行:
- 10 個執行緒連續 3 次同時啟動(我們稱之為執行緒區塊)。
- 將一個區塊內所有執行緒的運行時間取平均值,作為該區塊運行的最終結果。
- 所有線程都使用同一個表。
- 在每個執行緒區塊啟動之前,都會進行主要壓縮。
- 每個區塊僅執行以下操作之一:
- 放
- 得到
— 取得 + 放置
- 每個區塊重複執行 50 次操作。
- 一個區塊中的記錄大小為100字節,1000位元組或10000位元組(隨機)。
- 這些區塊是在請求不同數量的金鑰的情況下啟動的(一個金鑰或 10 個)。
- 這些區塊採用不同的表格設定來運作。參數改變:
— BlockCache = 開啟或關閉
— 區塊大小 = 65 KB 或 16 KB
— 分區 = 1、5 或 30
— MSLAB = 啟用或停用
因此該塊看起來像這樣:
一個。 MSLAB 模式已開啟/關閉。
b.建立了一個表,並設定了以下參數:BlockCache = true/none、BlockSize = 65/16 Kb、Partitions = 1/5/30。
c.已設定 GZ 壓縮。
d.同時啟動 10 個線程,對該表執行 1/10 的 put/get/get+put 操作,記錄大小為 100/1000/10000 字節,連續執行 50 個請求(隨機鍵)。
e.點d重複三次。
f.所有執行緒的運行時間都是平均的。
所有可能的組合都經過了測試。可以預見的是,隨著記錄大小的增加,速度會下降,或停用快取會導致速度變慢。然而,目標是了解每個參數的影響程度和重要性,因此收集的數據被輸入到線性回歸函數的輸入中,這使得可以使用 t 統計量評估可靠性。以下是執行 Put 操作的區塊的結果。完整的組合數為 2*2*3*2*3 = 144 個選項 + 72 個(因為有些組合執行了兩次)。因此總共有 216 次發射:

測試在由 3 個 DataNode 和 4 個 RS(CPU Xeon E5-2680 v4 @ 2.40GHz * 64 執行緒)組成的微叢集上進行。 HBase 版本 1.2.0-cdh5.14.2。
在停用 MSLAB 模式、在一個分割區的表上、啟用 BlockCache、BlockSize = 3.7、16 位元組記錄、每批 100 筆的情況下,獲得了 10 秒的最高插入速度。
在啟用 MSLAB 模式、具有一個分區的表上、啟用 BlockCache、BlockSize = 82.8、有 16 位元組記錄(每個 10000 個)的情況下,獲得了 1 秒的最低插入速度。
現在我們來看模型。根據 R2,我們看到模型品質良好,但很明顯,外推在這裡是禁忌的。系統在改變參數時的實際行為將不是線性的;這個模型不是為了預測,而是為了了解在給定參數內發生的情況。例如,這裡我們從 Student 的標準看到,BlockSize 和 BlockCache 參數對於 Put 操作並不重要(這通常是可以預測的):

但增加分區數量卻導致效能下降這一事實有些出乎意料(我們在 BulkLoad 過程中已經看到了增加分區數量帶來的正面影響),儘管這是可以理解的。首先,對於處理來說,需要向 30 個地區而不是一個地區發出請求,而且資料量並不大,因此這樣做並不會有什麼好處。其次,總的運行時間由最慢的RS決定,而且由於DataNode的數量少於RS的數量,所以有些區域的局部性為零。好吧,讓我們來看看前五名:

現在讓我們評估執行 Get 區塊的結果:

分區的數量已經失去了其重要性,這可能是因為資料被很好地緩存並且讀取快取是最重要的(統計上)參數。當然,增加請求中的訊息數量對於效能也非常有用。最佳結果:

好了,最後我們看一下先執行get再執行put的區塊的模型:

這裡的所有參數都很重要。領先者的成績如下:

9. 負載測試
好吧,最後,我們將發射或多或少不錯的負載,但當有東西可以比較時總是更有趣。在 Cassandra 的主要開發者 DataStax 的網站上, 許多 NoSQL 儲存的 NT,包括 HBase 版本 0.98.6-1。下載使用40個執行緒、資料大小100位元組、SSD磁碟進行。測試讀取-修改-寫入操作的結果顯示如下。

據我了解,讀取是按 100 筆記錄的區塊進行的,對於 16 個 HBase 節點,DataStax 測試顯示每秒 10 次操作的效能。
幸運的是我們的叢集也有 16 個節點,但就沒那麼「幸運」了,每個節點都有 64 個核心(線程),而在 DataStax 測試中只有 4 個。另一方面,他們有 SSD 磁碟,我們有 HDD 和較新版本的 HBase,載入期間的 CPU 使用率實際上並沒有顯著增加(視覺上增加了 5-10%)。但是,讓我們嘗試在這個配置上運行。預設的表設定是在 0 到 50 萬個鍵的範圍內隨機讀取(即每次基本上都是一個新的)。表格包含50萬筆記錄,分為64個分區。密鑰使用 crc32 進行散列。表格設定為默認,MSLAB 已啟用。啟動 40 個線程,每個線程讀取一組 100 個隨機金鑰並立即將產生的 100 個位元組寫回這些金鑰。

標準:16 個 DataNode 和 16 個 RS(CPU Xeon E5-2680 v4 @ 2.40GHz * 64 執行緒)。 HBase 版本 1.2.0-cdh5.14.2。
平均結果接近每秒 40 萬次操作,明顯優於 DataStax 測試。然而,為了實驗目的,條件可以稍微改變。所有工作都只用一個表來完成,並且只使用唯一的鍵,這是不太可能的。我們假設有一組「熱」鍵會產生主要負載。因此,讓我們嘗試建立一批更大的記錄(10 KB),同樣以 100 個為一批,分成 4 個不同的表,並將請求的鍵的範圍限制為 50 萬個。下圖顯示了啟動 40 個線程,每個線程讀取一組 100 個鍵並立即為這些鍵寫回隨機 10 KB。

標準:16 個 DataNode 和 16 個 RS(CPU Xeon E5-2680 v4 @ 2.40GHz * 64 執行緒)。 HBase 版本 1.2.0-cdh5.14.2。
在載入過程中,會執行多次 major compaction,如上所示,如果沒有這個過程,效能會逐漸下降,但在執行過程中,也會發生額外的載入。虧損是由多種原因造成的。有時執行緒會終止並且在重新啟動時會出現暫停,有時第三方應用程式會在叢集上產生負載。
立即讀寫是 HBase 最苛刻的工作流程之一。如果你只發出較小的 put 請求,例如每個 100 字節,將它們組合成 10 萬到 50 萬個批次,則每秒可以獲得數十萬次操作,只讀請求的情況類似。值得注意的是,結果比 DataStax 獲得的結果好得多,這主要歸功於 50 萬個區塊中的查詢。

標準:16 個 DataNode 和 16 個 RS(CPU Xeon E5-2680 v4 @ 2.40GHz * 64 執行緒)。 HBase 版本 1.2.0-cdh5.14.2。
10。結論
系統配置起來相當靈活,但大量參數的影響仍未知。其中一些已經過測試但未包含在最終的測試集中。例如,初步實驗表明,DATA_BLOCK_ENCODING等參數意義不大,該參數使用來自相鄰單元的值對資訊進行編碼,這對於隨機生成的資料是可以理解的。當使用大量重複物件時,收益會非常顯著。整體而言,可以說 HBase 給人的印像是一個相當嚴肅且經過深思熟慮的資料庫,在操作大量資料區塊時效率非常高。尤其是如果有機會及時分離閱讀和寫作過程。
如果您認為某些內容沒有充分介紹,我願意向您詳細介紹。我們邀請您分享您的經驗或討論您是否有不同意見。
來源: www.habr.com
