20張圖帶你到HBase的世界遨游
本文轉(zhuǎn)載自微信公眾號(hào)「sowhat1412」,作者SoWhat1412 。轉(zhuǎn)載本文請(qǐng)聯(lián)系sowhat1412公眾號(hào)。
1 HBase 淺析
1.1 HBase 是啥
HBase 是一款面向列存儲(chǔ),用于存儲(chǔ)處理海量數(shù)據(jù)的 NoSQL 數(shù)據(jù)庫(kù)。它的理論原型是Google 的 BigTable 論文。你可以認(rèn)為 HBase 是一個(gè)高可靠性、高性能、面向列、可伸縮的分布式存儲(chǔ)系統(tǒng)。
HBase 的存儲(chǔ)是基于HDFS的,HDFS 有著高容錯(cuò)性的特點(diǎn),被設(shè)計(jì)用來(lái)部署在低廉的硬件上,基于 Hadoop 意味著 HBase 與生俱來(lái)的超強(qiáng)的擴(kuò)展性和吞吐量。
HBase 采用的時(shí)key/value的存儲(chǔ)方式,這意味著,即使隨著數(shù)據(jù)量的增大,也幾乎不會(huì)導(dǎo)致查詢性能的下降。HBase 又是一個(gè)面向列存儲(chǔ)的數(shù)據(jù)庫(kù),當(dāng)表的字段很多時(shí),可以把其中幾個(gè)字段獨(dú)立出來(lái)放在一部分機(jī)器上,而另外幾個(gè)字段放到另一部分機(jī)器上,充分分散了負(fù)載的壓力。如此復(fù)雜的存儲(chǔ)結(jié)構(gòu)和分布式的存儲(chǔ)方式,帶來(lái)的代價(jià)就是即便是存儲(chǔ)很少的數(shù)據(jù),也不會(huì)很快。
HBase 并不是足夠快,只是數(shù)據(jù)量很大的時(shí)候慢的不明顯。HBase主要用在以下兩種情況:
單表數(shù)據(jù)量超過(guò)千萬(wàn),而且并發(fā)量很大。
數(shù)據(jù)分析需求較弱,或者不需要那么實(shí)時(shí)靈活。
1.2 HBase 的由來(lái)
我們知道 Mysql 是一個(gè)關(guān)系型數(shù)據(jù)庫(kù),學(xué)數(shù)據(jù)庫(kù)的時(shí)第一個(gè)接觸的就是 MySQL 了。但是 MySQL 的性能瓶頸是很大的,一般單個(gè)table行數(shù)不宜超過(guò)500萬(wàn)行,大小不宜超過(guò)2G。
我們以互聯(lián)網(wǎng)公司最核心用戶表為例,當(dāng)數(shù)據(jù)量達(dá)到千萬(wàn)甚至億級(jí)別時(shí)候,盡管你可以通過(guò)各種優(yōu)化來(lái)提速查詢,但是對(duì)單條數(shù)據(jù)的檢索耗時(shí)還是會(huì)超出你的預(yù)期!看下這個(gè)User表:
假如查詢 id=1 這條數(shù)據(jù)對(duì)應(yīng)的用戶name,系統(tǒng)會(huì)給我們返回aa。但由于MySQL是以行為位單位存儲(chǔ)的,當(dāng)查 name 時(shí)卻需要查詢一整行的數(shù)據(jù),連 age 和 email 也會(huì)被查出來(lái)!如果列非常多,那么查詢效率可想而知了。
我們稱列過(guò)多的表為寬表,優(yōu)化方法一般就是對(duì)列進(jìn)行豎直拆分:
此時(shí)查找 name 時(shí)只需要查找 user_basic 表,沒有多余的字段,查詢效率就會(huì)很快。如果一張表的行過(guò)多,會(huì)影響查詢效率,我們將這樣的表稱之為高表,可以采用水平拆表的方式提高效率:
這種水平拆分應(yīng)用比較多的 場(chǎng)景就是日志表,日志信息每天產(chǎn)生很多,可以按月/按日進(jìn)行水平拆分,這樣就實(shí)現(xiàn)了高表變矮。
上述的拆分方式貌似可以解決寬表跟高表問題,但是如果有一天公司業(yè)務(wù)變更,比如原來(lái)沒有微信,現(xiàn)在需加入用戶的微信字段。這時(shí)候需要改變表的結(jié)構(gòu)信息,該怎么辦?最簡(jiǎn)單的想法是多加一列,像這樣:
但是你要知道不是所有用戶都要微信號(hào)的,微信號(hào)這一列是設(shè)置默認(rèn)值還是采取其他的做法就得權(quán)衡一下了。如果需擴(kuò)展很多列出來(lái),但不是所有的用戶都有這些屬性,那么拓展起來(lái)就更加復(fù)雜了。這時(shí)可以用下JSON格式的字符串,將若干可選擇填寫信息匯總,而且屬性字段可以動(dòng)態(tài)拓展,于是有了下邊做法:
至此你可能認(rèn)為這樣存儲(chǔ)數(shù)據(jù)它不挺好的嘛,用 HBase 出來(lái)干嘛?Mysql 有個(gè)致命缺點(diǎn),就是當(dāng)數(shù)據(jù)達(dá)到一定的閾值,無(wú)論怎么優(yōu)化,它都無(wú)法達(dá)到高性能的發(fā)揮。而大數(shù)據(jù)領(lǐng)域的數(shù)據(jù),動(dòng)輒 PB 級(jí)數(shù)據(jù)量,這種存儲(chǔ)應(yīng)用明顯是不能很好的滿足需求的!并且針對(duì)上邊的問題,HBase 都有很好的解決方案~~。
1.3 HBase 設(shè)計(jì)思路
接著上邊說(shuō)到的幾個(gè)問題:高表、寬表、數(shù)據(jù)列動(dòng)態(tài)擴(kuò)展,把提到的幾個(gè)解決辦法:水平切分、垂直切分、列擴(kuò)展方法 雜糅在一起。
有張表,你怕它又寬又高跟動(dòng)態(tài)擴(kuò)展列,那么在設(shè)計(jì)之初,就把這個(gè)表給拆開,為了列的動(dòng)態(tài)拓展,直接存儲(chǔ)JSON格式:
這樣就解決了寬表跟列擴(kuò)展問題,高表怎么辦呢?一個(gè)表按行切分成partition,各存一部分行:
解決了高表、寬表、動(dòng)態(tài)擴(kuò)展列 的問題后你會(huì)發(fā)現(xiàn)數(shù)據(jù)量大了速度不夠快咋辦?用緩存唄,查詢出的數(shù)據(jù)放緩存中,下次直接從緩存拿數(shù)據(jù)。插入數(shù)據(jù)怎么辦呢?也可以這樣理解,我把要插入的數(shù)據(jù)放進(jìn)緩存中,再也不用管了,直接由數(shù)據(jù)庫(kù)從緩存拿數(shù)據(jù)插入到數(shù)據(jù)庫(kù)。此時(shí)程序不需要等待數(shù)據(jù)插入成功,提高了并行工作的效率。
你用緩存的考慮服務(wù)器宕機(jī)后緩存中數(shù)據(jù)沒來(lái)得及插入到數(shù)據(jù)庫(kù)中造成丟數(shù)據(jù)咋辦?參考 Redis 的持久化策略,可以插入數(shù)據(jù)這個(gè)操作添加一個(gè)操作日志,用于持久化插入操作,宕機(jī)重啟后從日志恢復(fù)。這樣設(shè)計(jì)架構(gòu)就變成了這個(gè)樣子:
這就是 HBase 實(shí)現(xiàn)的大致思路。接下來(lái)正式進(jìn)入 HBase 設(shè)計(jì)解析。
2 Hbase 簡(jiǎn)介
Hbase 官網(wǎng):http://hbase.apache.org
2.1 HBase 特點(diǎn)
海量存儲(chǔ)
HBase適合存儲(chǔ) PB 級(jí)別的海量數(shù)據(jù),能在幾十到百毫秒內(nèi)返回?cái)?shù)據(jù)。
列式存儲(chǔ)
HBase是根據(jù)列族來(lái)存儲(chǔ)數(shù)據(jù)的。列族下面可以有非常多的列,在創(chuàng)建表的時(shí)候列族就必須指定。
高并發(fā)
在并發(fā)的情況下,HBase的單個(gè)IO延遲下降并不多,能獲得高并發(fā)、低延遲的服務(wù)。
稀疏性
HBase的列具有靈活性,在列族中,你可以指定任意多的列,在列數(shù)據(jù)為空的情況下,是不會(huì)占用存儲(chǔ)空間的。
極易擴(kuò)展
基于 RegionServer 的擴(kuò)展,通過(guò)橫向添加 RegionSever 的機(jī)器,進(jìn)行水平擴(kuò)展,提升 HBase 上層的處理能力,提升HBase服務(wù)更多 Region 的能力。
基于存儲(chǔ)的擴(kuò)展(HDFS)。
2.2 HBase 邏輯結(jié)構(gòu)
邏輯思維層面 HBase的存儲(chǔ)模型如下:
Table(表):
表由一個(gè)或者多個(gè)列族構(gòu)成。數(shù)據(jù)的屬性如name、age、TTL(超時(shí)時(shí)間)等都在列族里邊定義。定義完列族的表是個(gè)空表,只有添加了數(shù)據(jù)行以后,表才有數(shù)據(jù)。
Column (列):
HBase 中的每個(gè)列都由 Column Family(列族) 和 Column Qualifier(列限定符)進(jìn)行限定,例如 info:name、info:age。建表時(shí)只需指明列族,而列限定符無(wú)需預(yù)先定義。
Column Family(列族):
多個(gè)列組合成一個(gè)列族。建表時(shí)不用創(chuàng)建列,在 HBase 中列是可增減變化的!唯一要確定的是列族,表有幾個(gè)列族在開始創(chuàng)建時(shí)就定好的。表的很多屬性,比如數(shù)據(jù)過(guò)期時(shí)間、數(shù)據(jù)塊緩存以及是否使用壓縮等都是定義在列族上的。
HBase 會(huì)把相同列族的幾個(gè)列數(shù)據(jù)盡量放在同一臺(tái)機(jī)器上。
Row(行):
一行包含多個(gè)列,這些列通過(guò)列族來(lái)分類。行中的數(shù)據(jù)所屬的列族從該表所定義的列族中選取。由于HBase是一個(gè)面向列存儲(chǔ)的數(shù)據(jù)庫(kù),所以一個(gè)行中的數(shù)據(jù)可以分布在不同的服務(wù)器上。
RowKey(行鍵):
RowKey 類似 MySQL 中的主鍵,在 HBase 中 RowKey 必須有且 RowKey 是按照字典排序的,如果用戶不指定 RowKey 系統(tǒng)會(huì)自動(dòng)生成不重復(fù)字符串。查詢數(shù)據(jù)時(shí)只能根據(jù) RowKey 進(jìn)行檢索,所以 Table 的 RowKey 設(shè)計(jì)十分重要。
Region(區(qū)域):
Region 就是若干行數(shù)據(jù)的集合。HBase 中的 Region 會(huì)根據(jù)數(shù)據(jù)量的大小動(dòng)態(tài)分裂,Region是基于HDFS實(shí)現(xiàn)的,關(guān)于Region的存取操作都是調(diào)用HDFS客戶端完成的。同一個(gè)行鍵的 Region 不會(huì)被拆分到多個(gè) Region 服務(wù)器上。
Region 有一點(diǎn)像關(guān)系型數(shù)據(jù)的分區(qū),數(shù)據(jù)存放在Region中,當(dāng)然Region下面還有很多結(jié)構(gòu),確切來(lái)說(shuō)數(shù)據(jù)存放在MemStore和HFile中。訪問HBase 時(shí)先去HBase 系統(tǒng)表查找定位這條記錄屬于哪個(gè)Region ,然后定位到這個(gè)Region 屬于哪個(gè)服務(wù)器,然后就到哪個(gè)服務(wù)器里面查找對(duì)應(yīng)Region 中的數(shù)據(jù)。
RegionServer:
RegionServer 就是存放Region的容器,直觀上說(shuō)就是服務(wù)器上的一個(gè)服務(wù)。負(fù)責(zé)管理維護(hù) Region。
2.3 HBase 物理存儲(chǔ)
以上只是一個(gè)基本的邏輯結(jié)構(gòu),底層的物理存儲(chǔ)結(jié)構(gòu)才是重中之重的內(nèi)容,看下圖
NameSpace:
命名空間,類似關(guān)系型數(shù)據(jù)庫(kù) DatabBase 概念,每個(gè)命名空間下有多個(gè)表。HBase有兩個(gè)自帶的命名空間,分別是hbase和default,hbase 中存放的是 HBase 內(nèi)置的表,default 表是用戶默認(rèn)使用的命名空間。
TimeStamp:
時(shí)間戳,用于標(biāo)識(shí)數(shù)據(jù)的不同版本(version),每條數(shù)據(jù)寫入時(shí)如果不指定時(shí)間戳,系統(tǒng)會(huì)自動(dòng)添加為其寫入 HBase 的時(shí)間。并且讀取數(shù)據(jù)的時(shí)候一般只拿出數(shù)據(jù)的Type符合,時(shí)間戳最新的數(shù)據(jù)。之所以按照Type取數(shù)據(jù)是因?yàn)镠Base的底層HDFS支持增刪查,但不支持改。
Cell:
單元格,由 {rowkey, column Family:column Qualifier, time Stamp} 唯一確定的單元。cell 中的數(shù)據(jù)是沒有類型的,全部是字節(jié)碼形式存儲(chǔ)。
3 HBase 底層架構(gòu)
3.1 Client
Client 包含了訪問 Hbase 的接口,另外 Client 還維護(hù)了對(duì)應(yīng)的 cache 來(lái)加速 Hbase 的訪問,比如緩存元數(shù)據(jù)的信息。
3.2 Zookeeper
HBase 通過(guò) Zookeeper 來(lái)做 Master 的高可用、RegionServer 的監(jiān)控、元數(shù)據(jù)的入口以及集群配置的維護(hù)等工作。Zookeeper 職責(zé)如下:
通過(guò)Zoopkeeper來(lái)保證集群中只有1個(gè)Master 在運(yùn)行,如果Master 發(fā)生異常會(huì)通過(guò)競(jìng)爭(zhēng)機(jī)制產(chǎn)生新的Master 來(lái)提供服務(wù)。
通過(guò) Zoopkeeper 來(lái)監(jiān)控 RegionServer 的狀態(tài),當(dāng)RegionSevrer有異常的時(shí)候,通過(guò)回調(diào)的形式通知MasterRegionServer上下線的信息。
通過(guò) Zoopkeeper 存儲(chǔ)元數(shù)據(jù) hbase:meata 的統(tǒng)一入口地址。
3.3 Master
Master 在 HBase 中的地位比其他類型的集群弱很多!數(shù)據(jù)的讀寫操作與他沒有關(guān)系,它掛了之后,集群照樣運(yùn)行。但是Master 也不能宕機(jī)太久,有很多必要的操作,比如創(chuàng)建表、修改列族配置等DDL跟Region的分割與合并都需要它的操作。
- 負(fù)責(zé)啟動(dòng)的時(shí)候分配Region到具體的 RegionServer。
- 發(fā)現(xiàn)失效的 Region,并將失效的 Region 分配到正常的 RegionServer 上。
- 管理HRegion服務(wù)器的負(fù)載均衡,調(diào)整HRegion分布。
- 在HRegion分裂后,負(fù)責(zé)新HRegion的分配。
HBase 中可以啟動(dòng)多個(gè)Master,通過(guò) Zookeeper 的 Master Election 機(jī)制保證總有一個(gè) Master 運(yùn)行。
3.4 RegionServer
HregionServer 直接對(duì)接用戶的讀寫請(qǐng)求,是真正的干活的節(jié)點(diǎn)。它的功能概括如下:
- 管理Master為其分配的Region。
- 處理來(lái)自客戶端的讀寫請(qǐng)求。
- 負(fù)責(zé)和底層HDFS的交互,存儲(chǔ)數(shù)據(jù)到HDFS。
- 負(fù)責(zé)Region變大以后的拆分。
- 負(fù)責(zé)StoreFile的合并工作。
ZooKeeper 會(huì)監(jiān)控 RegionServer 的上下線情況,當(dāng) ZK 發(fā)現(xiàn)某個(gè) HRegionServer 宕機(jī)之后會(huì)通知 Master 進(jìn)行失效備援。下線的 RegionServer 所負(fù)責(zé)的 Region 暫時(shí)停止對(duì)外提供服務(wù),Master 會(huì)將該 RegionServer 所負(fù)責(zé)的 Region 轉(zhuǎn)移到其他 RegionServer 上,并且會(huì)對(duì) 下線RegionServer 上存在 MemStore 中還未持久化到磁盤中的數(shù)據(jù)由 WAL重播進(jìn)行恢復(fù)。
3.5 WAL
WAL (Write-Ahead-Log) 預(yù)寫日志是 HBase 的 RegionServer 在處理數(shù)據(jù)插入和刪除的過(guò)程中用來(lái)記錄操作內(nèi)容的一種日志。每次Put、Delete等一條記錄時(shí),首先將其數(shù)據(jù)寫入到 RegionServer 對(duì)應(yīng)的HLog文件中去。只有當(dāng)WAL日志寫入成功的時(shí)候,客戶端才會(huì)被告訴提交數(shù)據(jù)成功。如果寫WAL失敗會(huì)告知客戶端提交失敗,這其實(shí)就是數(shù)據(jù)落地的過(guò)程。
WAL是保存在HDFS上的持久化文件。數(shù)據(jù)到達(dá) Region 時(shí)先寫入WAL,然后被加載到MemStore中。這樣就算Region宕機(jī)了,操作沒來(lái)得及執(zhí)行持久化,也可以再重啟的時(shí)候從WAL加載操作并執(zhí)行。跟Redis的AOF類似。
- 在一個(gè) RegionServer 上的所有 Region 都共享一個(gè) HLog,一次數(shù)據(jù)的提交先寫入WAL,寫入成功后,再寫入MenStore之中。當(dāng)MenStore的值達(dá)到一定的時(shí)候,就會(huì)形成一個(gè)個(gè)StoreFile。
- WAL 默認(rèn)是開啟 的,也可以手動(dòng)關(guān)閉它,這樣增刪改操作會(huì)快一點(diǎn)。但是這樣做犧牲的是數(shù)據(jù)的安全性。如果不想關(guān)閉WAL,又不想每次都耗費(fèi)那么大的資源,每次改動(dòng)都調(diào)用HDFS客戶端,可以選擇異步的方式寫入WAL(默認(rèn)間隔1秒寫入)
- 如果你學(xué)過(guò) Hadoop 中的 Shuffle(edits文件) 機(jī)制的就可以猜測(cè)到 HBase 中的 WAL 也是一個(gè)滾動(dòng)的日志數(shù)據(jù)結(jié)構(gòu),一個(gè)WAL實(shí)例包含多個(gè)WAL文件,WAL被觸發(fā)滾動(dòng)的條件如下。
- WAL的大小超過(guò)了一定的閾值。
- WAL文件所在的HDFS文件塊快要滿了。
- WAL歸檔和刪除。
3.5 Region
每一個(gè) Region 都有起始 RowKey 和結(jié)束 RowKey,代表了存儲(chǔ)的Row的范圍。從大圖中可知一個(gè)Region有多個(gè)Store,一個(gè)Store就是對(duì)應(yīng)一個(gè)列族的數(shù)據(jù),Store 由 MemStore 和 HFile 組成的。
3.6 Store
Store 由 MemStore 跟 HFile 兩個(gè)重要的部分。
3.6.1 MemStore
每個(gè) Store 都有一個(gè) MemStore 實(shí)例,數(shù)據(jù)寫入到 WAL 之后就會(huì)被放入 MemStore 中。MemStore是內(nèi)存的存儲(chǔ)對(duì)象,當(dāng) MemStore 的大小達(dá)到一個(gè)閥值(默認(rèn)64MB)時(shí),MemStore 會(huì)被 flush到文件,即生成一個(gè)快照。目前HBase 會(huì)有一個(gè)線程來(lái)負(fù)責(zé)MemStore 的flush操作。
3.6.2 StoreFile
MemStore 內(nèi)存中的數(shù)據(jù)寫到文件后就是StoreFile,StoreFile底層是以 HFile 的格式保存。HBase以Store的大小來(lái)判斷是否需要切分Region。
3.6.3 HFile
在Store中有多個(gè)HFile,每次刷寫都會(huì)形成一個(gè)HFile文件落盤在HDFS上。HFile文件也會(huì)動(dòng)態(tài)合并,它是數(shù)據(jù)存儲(chǔ)的實(shí)體。
這里提出一點(diǎn)疑問:操作到達(dá)Region時(shí),數(shù)據(jù)進(jìn)入HFile之前就已經(jīng)被持久化到WAL了,而WAL就是在HDFS上的,為什么還要從WAL加載到MemStore中,再刷寫成HFile呢?
由于HDFS支持文件創(chuàng)建、追加、刪除,但不能修改!但對(duì)數(shù)據(jù)庫(kù)來(lái)說(shuō),數(shù)據(jù)的順序非常重要!
第一次WAL的持久化是為了保證數(shù)據(jù)的安全性,無(wú)序的。
再讀取到MemStore中,是為了排序后存儲(chǔ)。
所以MemStore的意義在于維持?jǐn)?shù)據(jù)按照RowKey的字典序排列,而不是做一個(gè)緩存提高寫入效率。
3.7 HDFS
HDFS 為 HBase 提供最終的底層數(shù)據(jù)存儲(chǔ)服務(wù),HBase 底層用HFile格式 (跟hadoop底層的數(shù)據(jù)存儲(chǔ)格式類似) 將數(shù)據(jù)存儲(chǔ)到HDFS中,同時(shí)為HBase提供高可用(Hlog存儲(chǔ)在HDFS)的支持,具體功能概括如下:
提供元數(shù)據(jù)和表數(shù)據(jù)的底層分布式存儲(chǔ)服務(wù)
數(shù)據(jù)多副本,保證的高可靠和高可用性
4 HBase 讀寫
在HBase集群中如果我們做 DML 操作是不需要關(guān)心 HMaster 的,只需要從 ZooKeeper 中獲得hbase:meta 數(shù)據(jù)地址,然后從RegionServer中增刪查數(shù)據(jù)即可。
4.1 HBase 寫流程
- Client 先訪問 zookeeper,訪問 /hbase/meta-region-server 獲取 hbase:meta 表位于哪個(gè) Region Server。
- 訪問對(duì)應(yīng)的 Region Server,獲取 hbase:meta 表,根據(jù)讀請(qǐng)求的 namespace:table/rowkey,查詢出目標(biāo)數(shù)據(jù)位于哪個(gè) Region Server 中的哪個(gè) Region 中。并將該 table 的 Region 信息以及 meta 表的位置信息緩存在客戶端的 meta cache,方便下次訪問。
- 與目標(biāo) Region Server 進(jìn)行通訊。
- 將數(shù)據(jù)順序?qū)懭?追加)到 WAL。
- 將數(shù)據(jù)寫入對(duì)應(yīng)的 MemStore,數(shù)據(jù)會(huì)在 MemStore 進(jìn)行排序。
- 向客戶端發(fā)送 ack,此處可看到數(shù)據(jù)不是必須落盤的。
- 等達(dá)到 MemStore 的刷寫時(shí)機(jī)后,將數(shù)據(jù)刷寫到 HFile
- 在web頁(yè)面查看的時(shí)候會(huì)隨機(jī)的給每一個(gè)Region生成一個(gè)隨機(jī)編號(hào)。
4.2 HBase 讀流程
- Client 先訪問 ZooKeeper,獲取 hbase:meta 表位于哪個(gè) Region Server。
- 訪問對(duì)應(yīng)的 Region Server,獲取 hbase:meta 表,根據(jù)讀請(qǐng)求的 namespace:table/rowkey, 查詢出目標(biāo)數(shù)據(jù)位于哪個(gè) Region Server 中的哪個(gè) Region 中。并將該 table 的 region 信息以 及 meta 表的位置信息緩存在客戶端的 meta cache,方便下次訪問。
- 與目標(biāo) Region Server 進(jìn)行通訊。
- 分別在 Block Cache(讀緩存),MemStore 和 Store File(HFile)中查詢目標(biāo)數(shù)據(jù),并將 查到的所有數(shù)據(jù)進(jìn)行合并。此處所有數(shù)據(jù)是指同一條數(shù)據(jù)的不同版本(time stamp)或者不同的類型(Put/Delete)。
- 將從文件HFile中查詢到的數(shù)據(jù)塊(Block,HFile 數(shù)據(jù)存儲(chǔ)單元,默認(rèn)大小為 64KB)緩存到 Block Cache。
- 將合并后的最終結(jié)果,然后返回時(shí)間最新的數(shù)據(jù)返回給客戶端。
4.2.1 Block Cache
HBase 在實(shí)現(xiàn)中提供了兩種緩存結(jié)構(gòu) MemStore(寫緩存) 和 BlockCache(讀緩存)。寫緩存前面說(shuō)過(guò)不再重復(fù)。
HBase 會(huì)將一次文件查找的 Block塊 緩存到 Cache中,以便后續(xù)同一請(qǐng)求或者鄰近數(shù)據(jù)查找請(qǐng)求,可以直接從內(nèi)存中獲取,避免昂貴的IO操作。
BlockCache是Region Server級(jí)別的,
一個(gè)Region Server只有一個(gè)Block Cache,在 Region Server 啟動(dòng)的時(shí)候完成 Block Cache 的初始化工作。
HBase對(duì)Block Cache的管理分為如下三種。
LRUBlockCache 是最初的實(shí)現(xiàn)方案,也是默認(rèn)的實(shí)現(xiàn)方案,將所有數(shù)據(jù)都放入JVM Heap中,交給JVM進(jìn)行管理。
SlabCache 實(shí)現(xiàn)的是堆外內(nèi)存存儲(chǔ),不再由JVM管理數(shù)據(jù)內(nèi)存。一般跟第一個(gè)組合使用,單它沒有改善 GC 弊端,引入了堆外內(nèi)存利用率低。
BucketCache 緩存淘汰不再由 JVM 管理 降低了Full GC 發(fā)生的頻率。
重點(diǎn):
讀數(shù)據(jù)時(shí)不要理解為先從 MemStore 中讀取,讀不到再讀 BlockCache 中,還讀不到再?gòu)腍File中讀取,然后將數(shù)據(jù)寫入到 BlockCache 中。因?yàn)槿绻藶樵O(shè)置導(dǎo)致磁盤數(shù)據(jù)new,內(nèi)存數(shù)據(jù)old。你讀取的時(shí)候會(huì)出錯(cuò)的!
結(jié)論:
HBase 把磁盤跟內(nèi)存數(shù)據(jù)一起讀,然后把磁盤數(shù)據(jù)放到 BlockCache中,BlockCache 是磁盤數(shù)據(jù)的緩存。HBase 是個(gè)讀比寫慢的工具。
4.3 HBase 為什么寫比讀快
HBase 能提供實(shí)時(shí)計(jì)算服務(wù)主要原因是由其架構(gòu)和底層的數(shù)據(jù)結(jié)構(gòu)決定的,即由LSM-Tree(Log-Structured Merge-Tree) + HTable(Region分區(qū)) + Cache決定的。
HBase 寫入速度快是因?yàn)閿?shù)據(jù)并不是真的立即落盤,而是先寫入內(nèi)存,隨后異步刷入HFile。所以在客戶端看來(lái),寫入速度很快。
HBase 存儲(chǔ)到內(nèi)存中的數(shù)據(jù)是有序的,內(nèi)存數(shù)據(jù)刷寫到HFile時(shí)也是有序的。并且多個(gè)有序的HFile還會(huì)進(jìn)行歸并排序生成更大的有序HFile。性能測(cè)試發(fā)現(xiàn)順序讀寫磁盤速度比隨機(jī)讀寫磁盤快至少三個(gè)數(shù)量級(jí)!
讀取速度快是因?yàn)樗褂昧薒SM樹型結(jié)構(gòu),因?yàn)榇疟P尋址耗時(shí)遠(yuǎn)遠(yuǎn)大于磁盤順序讀取的時(shí)間,HBase的架構(gòu)設(shè)計(jì)導(dǎo)致我們可以將磁盤尋址次數(shù)控制在性能允許范圍內(nèi)。
LSM 樹原理把一棵大樹拆分成N棵小樹,它首先寫入內(nèi)存中,隨著小樹越來(lái)越大,內(nèi)存中的小樹會(huì)flush到磁盤中,磁盤中的樹定期可以做merge操作來(lái)合并成一棵大樹,以優(yōu)化讀性能。
4.3.1查詢舉例
- 根據(jù)RowKey能快速找到行所在的Region,假設(shè)有10億條記錄,占空間1TB。分列成了500個(gè)Region,那讀取2G的記錄,就能找到對(duì)應(yīng)記錄。
- 數(shù)據(jù)是按照列族存儲(chǔ)的,假設(shè)分為3個(gè)列族,每個(gè)列族就是666M, 如果要查詢的東西在其中1個(gè)列族上,1個(gè)列族包含1個(gè)或者多個(gè) HStoreFile,假設(shè)一個(gè)HStoreFile是128M, 該列族包含5個(gè)HStoreFile在磁盤上. 剩下的在內(nèi)存中。
- 內(nèi)存跟磁盤中數(shù)據(jù)是排好序的,你要的記錄有可能在最前面,也有可能在最后面,假設(shè)在中間,我們只需遍歷2.5個(gè)HStoreFile共300M。
- 每個(gè)HStoreFile(HFile的封裝),是以鍵值對(duì)(KV)方式存儲(chǔ),只要遍歷一個(gè)個(gè)數(shù)據(jù)塊中的key的位置,并判斷符合條件可以了。一般key是有限的長(zhǎng)度,假設(shè)KV比是1:19,最終只需要15M就可獲取的對(duì)應(yīng)的記錄,按照磁盤的訪問100M/S,只需0.15秒。加上Block Cache 會(huì)取得更高的效率。
- 大致理解讀寫思路后你會(huì)發(fā)現(xiàn)如果你在讀寫時(shí)設(shè)計(jì)的足夠巧妙當(dāng)然讀寫速度快的很咯。
5 HBase Flush
5.1 Flush
對(duì)于用戶來(lái)說(shuō)數(shù)據(jù)寫到 MemStore 中就算OK,但對(duì)于底層代碼來(lái)說(shuō)只有數(shù)據(jù)刷到硬盤中才算徹底搞定了!因?yàn)閿?shù)據(jù)是要寫入到WAL(Hlog)中再寫入到MemStore中的,flush有如下幾個(gè)時(shí)機(jī)。
- 當(dāng) WAL 文件的數(shù)量超過(guò)設(shè)定值時(shí) Region 會(huì)按照時(shí)間順序依次進(jìn)行刷寫,直到 WAL 文件數(shù)量小于設(shè)定值。
- 當(dāng)Region Server 中 MemStore 的總大小達(dá)到堆內(nèi)存40%時(shí),Region 會(huì)按照其所有 MemStore 的大小順序(由大到小)依次進(jìn)行阻塞刷寫。直到Region Server中所有 MemStore 的總大小減小到上述值以下。當(dāng)阻塞刷寫到上個(gè)參數(shù)的0.95倍時(shí),客戶端可以繼續(xù)寫。
- 當(dāng)某個(gè) MemStore 的大小達(dá)到了128M時(shí),其所在 Region 的所有 MemStore 都會(huì)阻塞刷寫。
- 到達(dá)自動(dòng)刷寫的時(shí)間也會(huì)觸發(fā) MemStore 的 flush。自動(dòng)刷新的時(shí)間間隔默認(rèn)1小時(shí)。
5.2 StoreFile Compaction
由于 MemStore 每次刷寫都會(huì)生成一個(gè)新的 HFile,且同一個(gè)字段的不同版本(timestamp) 和不同類型(Put/Delete)有可能會(huì)分布在不同的 HFile 中,因此查詢時(shí)需要遍歷所有的 HFile。為了減少 HFile 的個(gè)數(shù)跟清理掉過(guò)期和刪除的數(shù)據(jù),會(huì)進(jìn)行 StoreFile Compaction。
Compaction 分為兩種,分別是 Minor Compaction 和 Major Compaction。
- Minor Compaction會(huì)將臨近的若干個(gè)較小的 HFile 合并成一個(gè)較大的 HFile,但不會(huì)清理過(guò)期和刪除的數(shù)據(jù)。
- Major Compaction 會(huì)將一個(gè) Store 下的所有的 HFile 合并成一個(gè)大 HFile,并且會(huì)清理掉過(guò)期和刪除的數(shù)據(jù)。
5.3 Region Split
每個(gè) Table 起初只有一個(gè) Region,隨著不斷寫數(shù)據(jù) Region 會(huì)自動(dòng)進(jìn)行拆分。剛拆分時(shí),兩個(gè)子 Region 都位于當(dāng)前的 Region Server,但出于負(fù)載均衡的考慮, HMaster 有可能會(huì)將某個(gè) Region 轉(zhuǎn)移給其他的 Region Server。
Region Split 時(shí)機(jī):
0.94 版本之前:
- 當(dāng) 1 個(gè) Region 中的某個(gè) Store 下所有 StoreFile 的總大小超過(guò) hbase.hregion.max.filesize(默認(rèn)10G), 該 Region 就會(huì)進(jìn)行拆分。
0.94 版本之后:
- 當(dāng) 1 個(gè) Region 中的某個(gè) Store 下所有 StoreFile 的總大小超過(guò) Min(R^2 * “hbase.hregion.memstore.flush.size=128M”,hbase.hregion.max.filesize"),該 Region 就會(huì)進(jìn)行拆分,其 中 R 為當(dāng)前 Region Server 中屬于該 Table 的個(gè)數(shù)。
舉例:
- 第一次的閾值是128,切分后結(jié)果64 , 64。
- 第二次閾值512M,64,512 ⇒ 54 + 256 + 256
- 最后會(huì)形成一個(gè) 64M…10G 的這樣Region隊(duì)列,會(huì)產(chǎn)生數(shù)據(jù)傾斜問題。
- 解決方法:提前做好Region組的規(guī)劃,0-1k,1k-2k,2k-3k這樣的。
官方不建議用多個(gè)列族,比如有CF1,CF2,CF3,但是 CF1數(shù)據(jù)很多而CF2跟CF3數(shù)據(jù)很少,那么當(dāng)觸發(fā)了region切分的時(shí)候,會(huì)把CF2跟CF3分成若干小份,不利于系統(tǒng)維護(hù)。
6 HBase 常見面試題
6.1 Hbase 中 RowKey 的設(shè)計(jì)原則
RowKey 長(zhǎng)度原則
二進(jìn)制碼流RowKey 最大長(zhǎng)度 64Kb,實(shí)際應(yīng)用中一般為 10-100bytes,以 byte[] 形式保存,一般設(shè)計(jì)定長(zhǎng)。建議越短越好,因?yàn)镠File是按照KV存儲(chǔ)的Key太大浪費(fèi)空間。
RowKey 散列原則
RowKey 在設(shè)計(jì)時(shí)候要盡可能的實(shí)現(xiàn)可以將數(shù)據(jù)均衡的分布在每個(gè) RegionServer 上。
RowKey 唯一原則
RowKey 必須在設(shè)計(jì)上保證其唯一性,RowKey 是按照字典順序排序存儲(chǔ)的,因此設(shè)計(jì) RowKey 時(shí)可以將將經(jīng)常讀取的數(shù)據(jù)存儲(chǔ)到一塊。
6.2 HBase 在大數(shù)據(jù)體系位置
其實(shí)就簡(jiǎn)單的把HBase當(dāng)成大數(shù)據(jù)體系下的DataBase來(lái)用就行,任何可以分析HBase的引擎比如MR、Hive、Spark等框架連接上HBase都可以實(shí)現(xiàn)控制。比如你可以把Hive跟HBase進(jìn)行關(guān)聯(lián),Hive中數(shù)據(jù)不再由HDFS存儲(chǔ)而是存儲(chǔ)到HBase中,并且關(guān)聯(lián)后Hive中添加數(shù)據(jù)在HBase中可看到,HBase中添加數(shù)據(jù)Hive也可看到。
6.3 HBase 優(yōu)化方法
6.3.1 減少調(diào)整
HBase中有幾個(gè)內(nèi)容會(huì)動(dòng)態(tài)調(diào)整,如Region(分區(qū))、HFile。通過(guò)一些方法可以減少這些會(huì)帶來(lái)I/O開銷的調(diào)整。
Region
沒有預(yù)建分區(qū)的話,隨著Region中條數(shù)的增加,Region會(huì)進(jìn)行分裂,這將增加I/O開銷,所以解決方法就是根據(jù)你的RowKey設(shè)計(jì)來(lái)進(jìn)行預(yù)建分區(qū),減少Region的動(dòng)態(tài)分裂。
HFile
MemStore執(zhí)行flush會(huì)生成HFile,同時(shí)HFilewe年過(guò)多時(shí)候也會(huì)進(jìn)行Merge, 為了減少這樣的無(wú)謂的I/O開銷,建議估計(jì)項(xiàng)目數(shù)據(jù)量大小,給HFile設(shè)定一個(gè)合適的值。
6.3.2 減少啟停
數(shù)據(jù)庫(kù)事務(wù)機(jī)制就是為了更好地實(shí)現(xiàn)批量寫入,較少數(shù)據(jù)庫(kù)的開啟關(guān)閉帶來(lái)的開銷,那么HBase中也存在頻繁開啟關(guān)閉帶來(lái)的問題。
關(guān)閉 Compaction。
HBase 中自動(dòng)化的Minor Compaction和Major Compaction會(huì)帶來(lái)極大的I/O開銷,為了避免這種不受控制的意外發(fā)生,建議關(guān)閉自動(dòng)Compaction,在閑時(shí)進(jìn)行compaction。
6.3.3 減少數(shù)據(jù)量
開啟過(guò)濾,提高查詢速度
開啟BloomFilter,BloomFilter是列族級(jí)別的過(guò)濾,在生成一個(gè)StoreFile同時(shí)會(huì)生成一個(gè)MetaBlock,用于查詢時(shí)過(guò)濾數(shù)據(jù)
使用壓縮
一般推薦使用Snappy和LZO壓縮
6.3.4 合理設(shè)計(jì)
HBase 表格中 RowKey 和 ColumnFamily 的設(shè)計(jì)是非常重要,好的設(shè)計(jì)能夠提高性能和保證數(shù)據(jù)的準(zhǔn)確性。
RowKey設(shè)計(jì)
- 散列性:散列性能夠保證相同相似的RowKey聚合,相異的RowKey分散,有利于查詢
- 簡(jiǎn)短性:RowKey作為key的一部分存儲(chǔ)在HFile中,如果為了可讀性將rowKey設(shè)計(jì)得過(guò)長(zhǎng),那么將會(huì)增加存儲(chǔ)壓力.
- 唯一性:rowKey必須具備明顯的區(qū)別性。
- 業(yè)務(wù)性:具體情況具體分析。
列族的設(shè)計(jì)
- 優(yōu)勢(shì):HBase中數(shù)據(jù)是按列進(jìn)行存儲(chǔ)的,那么查詢某一列族的某一列時(shí)就不需要全盤掃描,只需要掃描某一列族,減少了讀I/O。
- 劣勢(shì):多列族意味這一個(gè)Region有多個(gè)Store,一個(gè)Store就有一個(gè)MemStore,當(dāng)MemStore進(jìn)行flush時(shí),屬于同一個(gè)Region的Store中的MemStore都會(huì)進(jìn)行flush,增加I/O開銷。
6.4 HBase 跟關(guān)系型數(shù)據(jù)庫(kù)區(qū)別
指標(biāo) | 傳統(tǒng)關(guān)系數(shù)據(jù)庫(kù) | HBase |
---|---|---|
數(shù)據(jù)類型 | 有豐富的數(shù)據(jù)類型 | 字符串 |
數(shù)據(jù)操作 | 豐富操作,復(fù)雜聯(lián)表查詢 | 簡(jiǎn)單CRUD |
存儲(chǔ)模式 | 基于行存儲(chǔ) | 基于列存儲(chǔ) |
數(shù)據(jù)索引 | 復(fù)雜的多個(gè)索引 | 只有RowKey索引 |
數(shù)據(jù)維護(hù) | 新覆蓋舊 | 多版本 |
可伸縮性 | 難實(shí)現(xiàn)橫向擴(kuò)展 | 性能動(dòng)態(tài)伸縮 |
6.5 HBase 批量導(dǎo)入
- 通過(guò) HBase API進(jìn)行批量寫入數(shù)據(jù)。
- 使用 Sqoop工具批量導(dǎo)數(shù)到HBase集群。
- 使用 MapReduce 批量導(dǎo)入。
- HBase BulkLoad的方式。
- HBase 通過(guò) Hive 關(guān)聯(lián)導(dǎo)入數(shù)據(jù)。
大數(shù)據(jù)導(dǎo)入用 HBase API 跟 MapReduce 寫入效率會(huì)很低,因?yàn)檎?qǐng)求RegionServer 將數(shù)據(jù)寫入,這期間數(shù)據(jù)會(huì)先寫入 WAL 跟 MemStore,MemStore 達(dá)到閾值后會(huì)刷寫到磁盤生成 HFile文件,HFile文件過(guò)多時(shí)會(huì)發(fā)生Compaction,如果Region大小過(guò)大時(shí)也會(huì)發(fā)生Split。
BulkLoad 適合初次數(shù)據(jù)導(dǎo)入,以及HBase與Hadoop為同一集群。BulkLoad 是使用 MapReduce 直接生成 HFile 格式文件后,Region Servers 再將 HFile 文件移動(dòng)到相應(yīng)的Region目錄下。
7 參考
BlockCache講解:https://blog.51cto.com/12445535/2363376?source=dra
LSM 原理:https://www.zhihu.com/question/19887265
HBase教程:http://c.biancheng.net/view/6499.html