【轉】HBase原理和設計


簡介

HBase —— Hadoop Database的簡稱,Google BigTable的另一種開源實現方式,從問世之初,就為了解決用大量廉價的機器高速存取海量數據、實現數據分布式存儲提供可靠的方案。從功能上來講,HBase不折不扣是一個數據庫,與我們熟悉的Oracle、MySQL、MSSQL等一樣,對外提供數據的存儲和讀取服務。而從應用的角度來說,HBase與一般的數據庫又有所區別,HBase本身的存取接口相當簡單,不支持復雜的數據存取,更不支持SQL等結構化的查詢語言;HBase也沒有除了rowkey以外的索引,所有的數據分布和查詢都依賴rowkey。所以,HBase在表的設計上會有很嚴格的要求。架構上,HBase是分布式數據庫的典范,這點比較像MongoDB的sharding模式,能根據鍵值的大小,把數據分布到不同的存儲節點上,MongoDB根據configserver來定位數據落在哪個分區上,HBase通過訪問Zookeeper來獲取-ROOT-表所在地址,通過-ROOT-表得到相應.META.表信息,從而獲取數據存儲的region位置。

 

架構

上面提到,HBase是一個分布式的架構,除去底層存儲的HDFS外,HBase本身從功能上可以分為三塊:Zookeeper群、Master群和RegionServer群。

  • Zookeeper群:HBase集群中不可缺少的重要部分,主要用於存儲Master地址、協調Master和RegionServer等上下線、存儲臨時數據等等。
  • Master群:Master主要是做一些管理操作,如:region的分配,手動管理操作下發等等,一般數據的讀寫操作並不需要經過Master集群,所以Master一般不需要很高的配置即可。
  • RegionServer群:RegionServer群是真正數據存儲的地方,每個RegionServer由若干個region組成,而一個region維護了一定區間rowkey值的數據,整個結構如下圖:
hbase

HBase結構圖

上圖中,Zookeeper(簡稱ZK)是一個集群,通常有奇數個ZK服務組成。Master為了服務可用性,也建議部署成集群方式,因為Master是整個管理操作的發起者,如果Master一旦發生意外停機,整個集群將會無法進行管理操作,所以Master也必須有多個,當然多個Master也有主從之分,如何區分哪個是主,哪個是從?關鍵看哪個Master能競爭到ZK上對應Master目錄下的鎖,持有該目錄鎖的Master為主Master,其他從Master輪詢競爭該鎖,所以一旦主Master發生意外停機,從Master很快會因為競爭到Master文件夾上的鎖而接管服務。
RegionServer(簡稱RS)在非Replication模式下,整個系統中都是唯一的,也就是說,在整個非Replication的HBase集群中,每台RS上保存的數據都不一樣,所以相對於前面兩者,該模式下的RS並不是高可用的,至少RS可能存在單點故障的問題,但是由於HBase內部數據分region存儲和region可以遷移的機制,RS服務的單點故障可能會在極小代價下很快恢復,但是一旦停掉的RS上有-ROOT-或者.META.表的region,那后果還是比較嚴重,因為數據節點的RS停機,只會在短時間內影響該台RS上的region不可訪問,等到region遷移完成后即可恢復,如果是-ROOT-、.META.所在的RS停機,整個HBase的新的求情都將受到影響,因為需要通過.META.表來路由,從而尋找到region所在RS的地址。

數據組織

整個架構中,ZK用於服務協調和整個集群運行過程中部分信息的保存和-ROOT-表地址定位,Master用於集群內部管理,所以剩下的RS主要用於處理數據。
RS是處理數據的主要場所,那么在RS內部的數據是怎么分布的?其實RS本身只是一個容器,其定義了一些功能線程,比如:數據合並線程(compact thread)、storeFile分割線程(split thread)等等。容器中的主要對象就是region,region是一個表根據自身rowkey范圍划分的一部分,一個表可以被划分成若干部分,也就是若干個region,region可以根據rowkey范圍不同而被分布在不同的RS上(當然也可以在同一個RS上,但不建議這么做)。一個RS上可以包含多個表的region,也可以只包含一個表的部分region,RS和表是兩個不同的概念。
這里還有一個概念——列簇。對HBase有一些了解的人,或多或少聽說過:HBase是一個列式存儲的數據庫,而這個列式存儲中的列,其實是區別於一般數據庫的列,這里的列的概念,就是列簇,列簇,顧名思義就是很多列的集合,而在數據存儲上來講,不同列簇的數據,一定是分開存儲的,即使是在同一個region內部,不同的列簇也存儲在不同的文件夾中,這樣做的好處是,一般我們定義列簇的時候,通常會把類似的數據放入同一個列簇,不同的列簇分開存儲,有利於數據的壓縮,並且HBase本身支持多種壓縮方式。

原理

前面介紹了HBase的一般架構,我們知道了HBase有ZK、Master和RS等組成,本節我們來介紹下HBase的基本原理,從數據訪問、RS路由到RS內部緩存、數據存儲和刷寫再到region的合並和拆分等等功能。

RegionServer定位

訪問HBase通過HBase客戶端(或API)進行,整個HBase提供給外部的地址,其實是ZK的入口,前面也介紹了,ZK中有保存-ROOT-所在的RS地址,從-ROOT-表可以獲取.META.表信息,根據.META.表可以獲取region在RS上的分布,整個region尋址過程大致如下:

direct

RS定位過程

  1. 首先,Client通過訪問ZK來請求目標數據的地址。
  2. ZK中保存了-ROOT-表的地址,所以ZK通過訪問-ROOT-表來請求數據地址。
  3. 同樣,-ROOT-表中保存的是.META.的信息,通過訪問.META.表來獲取具體的RS。
  4. .META.表查詢到具體RS信息后返回具體RS地址給Client。
  5. Client端獲取到目標地址后,然后直接向該地址發送數據請求。

上述過程其實是一個三層索引結構,從ZK獲取-ROOT-信息,再從-ROOT-獲取.META.表信息,最后從.META.表中查到RS地址后緩存。這里有幾個問題:

  • 既然ZK中能保存-ROOT-信息,那么為什么不把.META.信息直接保存在ZK中,而需要通過-ROOT-表來定位?
  • Client查找到目標地址后,下一次請求還需要走ZK  —> -ROOT- —> .META.這個流程么?

先來回答第一個問題:為什么不直接把.META.表信息直接保存到ZK中?主要是為了保存的數據量考慮,ZK中不宜保存大量數據,而.META.表主要是保存Region和RS的映射信息,region的數量沒有具體約束,只要在內存允許的范圍內,region數量可以有很多,如果保存在ZK中,ZK的壓力會很大。所以,通過一個-ROOT-表來轉存到RS中是一個比較理想的方案,相比直接保存在ZK中,也就多了一層-ROOT-表的查詢,對性能來說影響不大。
第二個問題:每次訪問都需要走ZK –> -ROOT- —> .META.的流程么?當然不需要,Client端有緩存,第一次查詢到相應region所在RS后,這個信息將被緩存到Client端,以后每次訪問都直接從緩存中獲取RS地址即可。當然這里有個意外:訪問的region若果在RS上發生了改變,比如被balancer遷移到其他RS上了,這個時候,通過緩存的地址訪問會出現異常,在出現異常的情況下,Client需要重新走一遍上面的流程來獲取新的RS地址。總體來說,region的變動只會在極少數情況下發生,一般變動不會很大,所以在整個集群訪問過程中,影響可以忽略。

Region數據寫入

HBase通過ZK —> -ROOT-  —> .META.的訪問獲取RS地址后,直接向該RS上進行數據寫入操作,整個過程如下圖:

data_write

RegionServer數據操作過程

Client通過三層索引獲得RS的地址后,即可向指定RS的對應region進行數據寫入,HBase的數據寫入采用WAL(write ahead log)的形式,先寫log,后寫數據。HBase是一個append類型的數據庫,沒有關系型數據庫那么復雜的操作,所以記錄HLog的操作都是簡單的put操作(delete/update操作都被轉化為put進行)

HLog

HLog寫入

HLog是HBase實現WAL方式產生的日志信息,其內部是一個簡單的順序日志,每個RS上的region都共享一個HLog,所有對於該RS上的region數據寫入都被記錄到該HLog中。HLog的主要作用就是在RS出現意外崩潰的時候,可以盡量多的恢復數據,這里說是盡量多,因為在一般情況下,客戶端為了提高性能,會把HLog的auto flush關掉,這樣HLog日志的落盤全靠操作系統保證,如果出現意外崩潰,短時間內沒有被fsync的日志會被丟失。

HLog過期

HLog的大量寫入會造成HLog占用存儲空間會越來越大,HBase通過HLog過期的方式進行HLog的清理,每個RS內部都有一個HLog監控線程在運行,其周期可以通過hbase.master.cleaner.interval進行配置。
HLog在數據從memstore flush到底層存儲上后,說明該段HLog已經不再被需要,就會被移動到.oldlogs這個目錄下,HLog監控線程監控該目錄下的HLog,當該文件夾下的HLog達到hbase.master.logcleaner.ttl設置的過期條件后,監控線程立即刪除過期的HLog。

Memstore

數據存儲

memstore是region內部緩存,其大小通過HBase參數hbase.hregion.memstore.flush.size進行配置。RS在寫完HLog以后,數據寫入的下一個目標就是region的memstore,memstore在HBase內部通過LSM-tree結構組織,所以能夠合並大量對於相同rowkey上的更新操作。
正是由於memstore的存在,HBase的數據寫入都是異步的,而且性能非常不錯,寫入到memstore后,該次寫入請求就可以被返回,HBase即認為該次數據寫入成功。這里有一點需要說明,寫入到memstore中的數據都是預先按照rowkey的值進行排序的,這樣有利於后續數據查找。

數據刷盤

memstore中的數據在一定條件下會進行刷寫操作,使數據持久化到相應的存儲設備上,觸發memstore刷盤的操作有多種不同的方式如下圖:

flush

Memstore刷寫流程

以上1,2,3都可以觸發memstore的flush操作,但是實現的方式不同:

  • 1通過全局內存控制,觸發memstore刷盤操作。memstore整體內存占用上限通過參數hbase.regionserver.global.memstore.upperLimit進行設置,當然在達到上限后,memstore的刷寫也不是一直進行,在內存下降到hbase.regionserver.global.memstore.lowerLimit配置的值后,即停止memstore的刷盤操作。這樣做,主要是為了防止長時間的memstore刷盤,會影響整體的性能。
  • 在該種情況下,RS中所有region的memstore內存占用都沒達到刷盤條件,但整體的內存消耗已經到一個非常危險的范圍,如果持續下去,很有可能造成RS的OOM,這個時候,需要進行memstore的刷盤,從而釋放內存。
  • 2手動觸發memstore刷盤操作
  • HBase提供API接口,運行通過外部調用進行memstore的刷盤
  • 3 memstore上限觸發數據刷盤
  • 前面提到memstore的大小通過hbase.hregion.memstore.flush.size進行設置,當region中memstore的數據量達到該值時,會自動觸發memstore的刷盤操作。

刷盤影響

memstore在不同的條件下會觸發數據刷盤,那么整個數據在刷盤過程中,對region的數據寫入等有什么影響?memstore的數據刷盤,對region的直接影響就是:在數據刷盤開始到結束這段時間內,該region上的訪問都是被拒絕的,這里主要是因為在數據刷盤結束時,RS會對改region做一個snapshot,同時HLog做一個checkpoint操作,通知ZK哪些HLog可以被移到.oldlogs下。從前面圖上也可以看到,在memstore寫盤開始,相應region會被加上UpdateLock鎖,寫盤結束后該鎖被釋放。

StoreFile

memstore在觸發刷盤操作后會被寫入底層存儲,每次memstore的刷盤就會相應生成一個存儲文件HFile,storeFile即HFile在HBase層的輕量級分裝。數據量的持續寫入,造成memstore的頻繁flush,每次flush都會產生一個HFile,這樣底層存儲設備上的HFile文件數量將會越來越多。不管是HDFS還是Linux下常用的文件系統如Ext4、XFS等,對小而多的文件上的管理都沒有大文件來的有效,比如小文件打開需要消耗更多的文件句柄;在大量小文件中進行指定rowkey數據的查詢性能沒有在少量大文件中查詢來的快等等。

Compact

大量HFile的產生,會消耗更多的文件句柄,同時會造成RS在數據查詢等的效率大幅度下降,HBase為解決這個問題,引入了compact操作,RS通過compact把大量小的HFile進行文件合並,生成大的HFile文件。
RS上的compact根據功能的不同,可以分為兩種不同類型,即:minor compact和major compact。

  • Minor Compact

minor compact又叫small compact,在RS運行過程中會頻繁進行,主要通過參數hbase.hstore.compactionThreshold進行控制,該參數配置了HFile數量在滿足該值時,進行minor compact,minor compact只選取region下部分HFile進行compact操作,並且選取的HFile大小不能超過hbase.hregion.max.filesize參數設置。

  • Major Compact

相反major compact也被稱之為large compact,major compact會對整個region下相同列簇的所有HFile進行compact,也就是說major compact結束后,同一個列簇下的HFile會被合並成一個。major compact是一個比較長的過程,對底層I/O的壓力相對較大。
major compact除了合並HFile外,另外一個重要功能就是清理過期或者被刪除的數據。前面提到過,HBase的delete操作也是通過append的方式寫入,一旦某些數據在HBase內部被刪除了,在內部只是被簡單標記為刪除,真正在存儲層面沒有進行數據清理,只有通過major compact對HFile進行重組時,被標記為刪除的數據才能被真正的清理。
compact操作都有特定的線程進行,一般情況下不會影響RS上數據寫入的性能,當然也有例外:在compact操作速度跟不上region中HFile增長速度時,為了安全考慮,RS會在HFile達到一定數量時,對寫入進行鎖定操作,直到HFile通過compact降到一定的范圍內才釋放鎖。

Split

compact將多個HFile合並單個HFile文件,隨着數據量的不斷寫入,單個HFile也會越來越大,大量小的HFile會影響數據查詢性能,大的HFile也會,HFile越大,相對的在HFile中搜索的指定rowkey的數據花的時間也就越長,HBase同樣提供了region的split方案來解決大的HFile造成數據查詢時間過長問題。
一個較大的region通過split操作,會生成兩個小的region,稱之為Daughter,一般Daughter中的數據是根據rowkey的之間點進行切分的,region的split過程大致如下圖:

hbase_split

region split流程

  1. region先更改ZK中該region的狀態為SPLITING。
  2. Master檢測到region狀態改變。
  3. region會在存儲目錄下新建.split文件夾用於保存split后的daughter region信息。
  4. Parent region關閉數據寫入並觸發flush操作,保證所有寫入Parent region的數據都能持久化。
  5. 在.split文件夾下新建兩個region,稱之為daughter A、daughter B。
  6. Daughter A、Daughter B拷貝到HBase根目錄下,形成兩個新的region。
  7. Parent region通知修改.META.表后下線,不再提供服務。
  8. Daughter A、Daughter B上線,開始向外提供服務。
  9. 如果開啟了balance_switch服務,split后的region將會被重新分布。

上面1 ~ 9就是region split的整個過程,split過程非常快,速度基本會在秒級內,那么在這么快的時間內,region中的數據怎么被重新組織的?
其實,split只是簡單的把region從邏輯上划分成兩個,並沒有涉及到底層數據的重組,split完成后,Parent region並沒有被銷毀,只是被做下線處理,不再對外部提供服務。而新產生的region Daughter A和Daughter B,內部的數據只是簡單的到Parent region數據的索引,Parent region數據的清理在Daughter A和Daughter B進行major compact以后,發現已經沒有到其內部數據的索引后,Parent region才會被真正的清理。

HBase設計

HBase是一個分布式數據庫,其性能的好壞主要取決於內部表的設計和資源的分配是否合理。

Rowkey設計

rowkey是HBase實現分布式的基礎,HBase通過rowkey范圍划分不同的region,分布式系統的基本要求就是在任何時候,系統的訪問都不要出現明顯的熱點現象,所以rowkey的設計至關重要,一般我們建議rowkey的開始部分以hash或者MD5進行散列,盡量做到rowkey的頭部是均勻分布的。禁止采用時間、用戶id等明顯有分段現象的標志直接當作rowkey來使用。

列簇設計

HBase的表設計時,根據不同需求有不同選擇,需要做在線查詢的數據表,盡量不要設計多個列簇,我們知道,不同的列簇在存儲上是被分開的,多列簇設計會造成在數據查詢的時候讀取更多的文件,從而消耗更多的I/O。

TTL設計

選擇合適的數據過期時間也是表設計中需要注意的一點,HBase中允許列簇定義數據過期時間,數據一旦超過過期時間,可以被major compact進行清理。大量無用歷史數據的殘余,會造成region體積增大,影響查詢效率。

Region設計

一般地,region不宜設計成很大,除非應用對階段性性能要求很多,但是在將來運行一段時間可以接受停服處理。region過大會導致major compact調用的周期變長,而單次major compact的時間也相應變長。major compact對底層I/O會造成壓力,長時間的compact操作可能會影響數據的flush,compact的周期變長會導致許多刪除或者過期的數據不能被及時清理,對數據的讀取速度等都有影響。
相反,小的region意味着major compact會相對頻繁,但是由於region比較小,major compact的相對時間較快,而且相對較多的major compact操作,會加速過期數據的清理。
當然,小region的設計意味着更多的region split風險,region容量過小,在數據量達到上限后,region需要進行split來拆分,其實split操作在整個HBase運行過程中,是被不怎么希望出現的,因為一旦發生split,涉及到數據的重組,region的再分配等一系列問題。所以我們在設計之初就需要考慮到這些問題,盡量避免region的運行過程中發生split。
HBase可以通過在表創建的時候進行region的預分配來解決運行過程中region的split產生,在表設計的時候,預先分配足夠多的region數,在region達到上限前,至少有部分數據會過期,通過major compact進行清理后, region的數據量始終維持在一個平衡狀態。
region數量的設計還需要考慮內存上的限制,通過前面的介紹我們知道每個region都有memstore,memstore的數量與region數量和region下列簇的數量成正比,一個RS下memstore內存消耗:

Memory = memstore大小 * region數量 * 列簇數量

如果不進行前期數據量估算和region的預分配,通過不斷的split產生新的region,容易導致因為內存不足而出現OOM現象。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM