一、HBase Get 流程
1.1馁启、客戶端流程解析
- 客戶端首先會根據(jù)配置文件中zookeeper地址連接zookeeper熔号,并讀取/<hbase-rootdir>/meta-region-server節(jié)點信息秕磷,該節(jié)點信息存儲HBase元數(shù)據(jù)(hbase:meta)表所在的RegionServer地址以及訪問端口等信息曲秉。用戶可以通過zookeeper命令(get /<hbase-rootdir>/meta-region-server)查看該節(jié)點信息。
- 根據(jù)hbase:meta所在RegionServer的訪問信息熊赖,客戶端會將該元數(shù)據(jù)表加載到本地并進(jìn)行緩存来屠。然后在表中確定待檢索rowkey所在的RegionServer信息。
- 根據(jù)數(shù)據(jù)所在RegionServer的訪問信息震鹉,客戶端會向該RegionServer發(fā)送真正的數(shù)據(jù)讀取請求俱笛。
- 如果集群發(fā)生某些變化導(dǎo)致hbase:meta元數(shù)據(jù)更改,客戶端再根據(jù)本地元數(shù)據(jù)表請求的時候就會發(fā)生異常传趾,此時客戶端需要重新加載一份最新的元數(shù)據(jù)表到本地迎膜。
1.2、服務(wù)器端流程解析
RegionServer接收到客戶端的get/scan請求之后浆兰,先后做了兩件事情:構(gòu)建scanner體系(排序放入堆中)磕仅,在此體系基礎(chǔ)上一行一行檢索珊豹。
scanner體系的核心在于三層scanner:RegionScanner、StoreScanner以及StoreFileScanner榕订。三者是層級的關(guān)系店茶,一個RegionScanner由多個StoreScanner構(gòu)成,一張表由多個列族組成劫恒,就有多少個StoreScanner負(fù)責(zé)該列族的數(shù)據(jù)掃描贩幻。一個StoreScanner又是由多個StoreFileScanner組成。每個Store的數(shù)據(jù)由內(nèi)存中的MemStore和磁盤上的StoreFile文件組成兼贸,相對應(yīng)的段直,StoreScanner對象會雇傭一個MemStoreScanner和N個StoreFileScanner來進(jìn)行實際的數(shù)據(jù)讀取,每個StoreFile文件對應(yīng)一個StoreFileScanner溶诞,注意:StoreFileScanner和MemstoreScanner是整個scan的最終執(zhí)行者。
- RegionScanner 會根據(jù)列族構(gòu)建StoreScanner决侈,有多少列族就構(gòu)建多少StoreScanner螺垢,用于負(fù)責(zé)該列族的數(shù)據(jù)檢索
- 構(gòu)建StoreFileScanner:每個StoreScanner會為當(dāng)前該Store中每個HFile構(gòu)造一個StoreFileScanner,用于實際執(zhí)行對應(yīng)文件的檢索赖歌。同時會為對應(yīng)Memstore構(gòu)造一個MemstoreScanner枉圃,用于執(zhí)行該Store中Memstore的數(shù)據(jù)檢索。
- 過濾淘汰StoreFileScanner:根據(jù)Time Range以及RowKey Range對StoreFileScanner以及MemstoreScanner進(jìn)行過濾庐冯,淘汰肯定不存在待檢索結(jié)果的Scanner孽亲。
- Seek rowkey:所有StoreFileScanner開始做準(zhǔn)備工作,在負(fù)責(zé)的HFile中定位到滿足條件的起始Row展父。Seek過程(此處略過Lazy Seek優(yōu)化)是一個很核心的步驟返劲,它主要包含下面三步:
- 定位Block Offset:在Blockcache中讀取該HFile的索引樹結(jié)構(gòu),根據(jù)索引樹檢索對應(yīng)RowKey所在的Block Offset和Block Size
- Load Block:根據(jù)BlockOffset首先在BlockCache中查找Data Block栖茉,如果不在緩存篮绿,再在HFile中加載
- Seek Key:在Data Block內(nèi)部通過二分查找的方式定位具體的RowKey
- StoreFileScanner合并構(gòu)建最小堆:將該Store中所有StoreFileScanner和MemstoreScanner合并形成一個heap(最小堆),所謂heap是一個優(yōu)先級隊列吕漂,隊列中元素是所有scanner亲配,排序規(guī)則按照scanner seek到的keyvalue大小由小到大進(jìn)行排序。
- 構(gòu)建Scanner體系是為了更好地執(zhí)行scan查詢惶凝。scan查詢總是一行一行查詢的吼虎,先查第一行的所有數(shù)據(jù),再查第二行的所有數(shù)據(jù)苍鲜,但每一行的查詢流程卻沒有什么本質(zhì)區(qū)別思灰。
1.3、不同 KeyValue 之間如何進(jìn)行大小比較坡贺?
KeyValue中Key由RowKey官辈,ColumnFamily箱舞,Qualifier ,TimeStamp拳亿,KeyType等5部分組成晴股,HBase設(shè)定Key大小:
- 首先比較RowKey肺魁,RowKey越小Key就越械缦妗;
- RowKey如果相同就看CF鹅经,CF越小Key越屑徘骸;
- CF如果相同看Qualifier瘾晃,Qualifier越小Key越写尽;
- Qualifier如果相同再看Timestamp蹦误,Timestamp越大表示時間越新劫拢,對應(yīng)的Key越小;
- 如果Timestamp還相同,就看KeyType眨猎,KeyType按照DeleteFamily -> DeleteColumn -> Delete -> Put 順序依次對應(yīng)的Key越來越大豌蟋。
1.4、為什么 Scanner 需要由小到大排序?
最直接的解釋是scan的結(jié)果需要由小到大輸出給用戶,當(dāng)然,這并不全面牵寺,最合理的解釋是只有由小到大排序才能使得scan效率最高。舉個簡單的例子哆料,HBase支持?jǐn)?shù)據(jù)多版本缸剪,假設(shè)用戶只想獲取最新版本,那只需要將這些數(shù)據(jù)由最新到最舊進(jìn)行排序东亦,然后取隊首元素返回就可以杏节。那么,如果不排序典阵,就只能遍歷所有元素奋渔,查看符不符合用戶查詢條件。這就是排隊的意義壮啊。
1.5嫉鲸、Get 實例說明
下圖是一張表的邏輯視圖,該表有兩個列族cf1和cf2(我們只關(guān)注cf1)歹啼,cf1只有一個列name玄渗,表中有5行數(shù)據(jù)座菠,其中每個cell基本都有多個版本。cf1的數(shù)據(jù)假如實際存儲在三個區(qū)域藤树,memstore中有r2和r4的最新數(shù)據(jù)浴滴,hfile1中是最早的數(shù)據(jù)。現(xiàn)在需要查詢RowKey=r2的數(shù)據(jù)岁钓,按照上文的理論對應(yīng)的Scanner指向就如圖所示:
這三個Scanner組成的heap為<MemstoreScanner升略,StoreFileScanner2, StoreFileScanner1>,Scanner由小到大排列屡限。查詢的時候首先pop出heap的堆頂元素品嚣,即MemstoreScanner,得到keyvalue = r2:cf1:name:v3:name23的數(shù)據(jù)钧大,拿到這個keyvalue之后翰撑,需要進(jìn)行如下判定:
- 檢查該KeyValue的KeyType是否是Deleted/DeletedCol等,如果是就直接忽略該列所有其他版本拓型,跳到下列(列族)
- 檢查該KeyValue的Timestamp是否在用戶設(shè)定的Timestamp Range范圍额嘿,如果不在該范圍,忽略
- 檢查該KeyValue是否滿足用戶設(shè)置的各種filter過濾器劣挫,如果不滿足,忽略
- 檢查該KeyValue是否滿足用戶查詢中設(shè)定的版本數(shù)东帅,比如用戶只查詢最新版本压固,則忽略該cell的其他版本;反正如果用戶查詢所有版本靠闭,則還需要查詢該cell的其他版本帐我。
1.6、讀數(shù)據(jù) 與 Bloom Filter
基于HBase的架構(gòu)愧膀,一條數(shù)據(jù)可能存在RegionServer中的三個不同位置:
- 對于剛讀取過的數(shù)據(jù)拦键,將會被緩存到BlockCache中
- 對于剛寫入的數(shù)據(jù),其存在Memstore中
- 對于之前已經(jīng)從Memstore刷寫到磁盤的檩淋,其存在于HFiles中
RegionServer接收到的一條數(shù)據(jù)查詢請求芬为,只需要從以上三個地方檢索到數(shù)據(jù)即可,在HBase中的檢索順序依次是:BlockCache -> Memstore -> HFiles蟀悦。
其中媚朦,BlockCache、Memstore都是直接在內(nèi)存中進(jìn)行高性能的數(shù)據(jù)檢索日戈。
而HFiles則是真正存儲在HDFS上的數(shù)據(jù):
- 檢索HFiles時會產(chǎn)生真實磁盤的IO操作
- Memstore不停刷寫的過程中询张,將會產(chǎn)生大量的HFile
如何在大量的HFile中快速找到所需要的數(shù)據(jù)呢?
為了提高檢索HFiles的性能浙炼,HBase支持使用 Bloom Fliter 對HFiles進(jìn)行快讀定位份氧。
Bloom Filter(布隆過濾器)是一種數(shù)據(jù)結(jié)構(gòu)唯袄,常用于大規(guī)模數(shù)據(jù)查詢場景,其能夠快速判斷一個元素一定不在集合中蜗帜,或者可能在集合中恋拷。
HBase中支持使用以下兩種Bloom Filter:
- ROW:基于 Rowkey 創(chuàng)建的Bloom Filter
- ROWCOL:基于 Rowkey+Column 創(chuàng)建的Bloom Filter
兩者的區(qū)別僅僅是:是否使用列信息作為Bloom Filter的條件。
使用ROWCOL時钮糖,可以讓指定列的查詢更快梅掠,因為其通過Rowkey與列信息來過濾不存在數(shù)據(jù)的HFile,但是相應(yīng)的店归,產(chǎn)生的Bloom Filter數(shù)據(jù)會更加龐大阎抒。
而只通過Rowkey進(jìn)行檢索的查詢,即使指定了ROWCOL也不會有其他效果消痛,因為沒有攜帶列信息且叁。
通過Bloom Filter(如果有的話)快速定位到當(dāng)前的Rowkey數(shù)據(jù)存儲于哪個HFile之后(或者不存在直接返回),通過HFile攜帶的 Data Block Index 等元數(shù)據(jù)信息可快速定位到具體的數(shù)據(jù)塊起始位置秩伞,讀取并返回(加載到緩存中)逞带。
這就是Bloom Filter在HBase檢索數(shù)據(jù)的應(yīng)用場景:
- 高效判斷key是否存在
- 高效定位key所在的HFile
當(dāng)然,如果沒有指定創(chuàng)建Bloom Filter纱新,RegionServer將會花費比較多的力氣一個個檢索HFile來判斷數(shù)據(jù)是否存在展氓。
二、HBase Put 流程
2.1脸爱、客戶端流程解析
- 用戶提交put請求后遇汞,HBase客戶端會將put請求添加到本地buffer中,符合一定條件就會通過AsyncProcess異步批量提交簿废。HBase默認(rèn)設(shè)置autoflush=true空入,表示put請求直接會提交給服務(wù)器進(jìn)行處理;用戶可以設(shè)置autoflush=false族檬,這樣的話put請求會首先放到本地buffer歪赢,等到本地buffer大小超過一定閾值(默認(rèn)為2M,可以通過配置文件配置)之后才會提交单料。很顯然埋凯,后者采用group commit機制提交請求,可以極大地提升寫入性能看尼,但是因為沒有保護(hù)機制递鹉,如果客戶端崩潰的話會導(dǎo)致提交的請求丟失。
- 在提交之前藏斩,HBase會在元數(shù)據(jù)表.meta.中根據(jù)rowkey找到它們歸屬的region server躏结,這個定位的過程是通過HConnection 的locateRegion方法獲得的。如果是批量請求的話還會把這些rowkey按照HRegionLocation分組狰域,每個分組可以對應(yīng)一次RPC請求媳拴。
- HBase 會為每個HRegionLocation 構(gòu)造一個遠(yuǎn)程RPC 請求MultiServerCallable<Row> 黄橘, 然后通過rpcCallerFactory.<MultiResponse> newCaller()執(zhí)行調(diào)用,忽略掉失敗重新提交和錯誤處理屈溉,客戶端的提交操作到此結(jié)束塞关。
2.2、服務(wù)器端流程解析
服務(wù)器端RegionServer接收到客戶端的寫入請求后子巾,首先會反序列化為Put對象帆赢,然后執(zhí)行各種檢查操作,比如檢查region是否是只讀线梗、memstore大小是否超過blockingMemstoreSize等椰于。檢查完成之后,就會執(zhí)行如下核心操作:
- 獲取行鎖仪搔、Region更新共享鎖: HBase中使用行鎖保證對同一行數(shù)據(jù)的更新都是互斥操作瘾婿,用以保證更新的原子性,要么更新成功烤咧,要么失敗偏陪。
- 開始寫事務(wù):獲取 write number,用于實現(xiàn) MVCC煮嫌,實現(xiàn)數(shù)據(jù)的非鎖定讀笛谦,在保證讀寫一致性的前提下提高讀取性能。
- 寫緩存 memstore:HBase中每個列族都會對應(yīng)一個store昌阿,用來存儲該列族數(shù)據(jù)揪罕。每個store都會有個寫緩存memstore,用于緩存寫入數(shù)據(jù)宝泵。HBase并不會直接將數(shù)據(jù)落盤,而是先寫入緩存轩娶,等緩存滿足一定大小之后再一起落盤儿奶。
- Append HLog:HBase使用WAL機制保證數(shù)據(jù)可靠性,即首先寫日志再寫緩存鳄抒,即使發(fā)生宕機闯捎,也可以通過恢復(fù)HLog還原出原始數(shù)據(jù)。該步驟就是將數(shù)據(jù)構(gòu)造為WALEdit對象许溅,然后順序?qū)懭際Log中瓤鼻,此時不需要執(zhí)行sync操作。
- 釋放行鎖以及共享鎖
- Sync HLog真正sync到HDFS贤重,在釋放行鎖之后執(zhí)行sync操作是為了盡量減少持鎖時間茬祷,提升寫性能。如果Sync失敗并蝗,執(zhí)行回滾操作將memstore中已經(jīng)寫入的數(shù)據(jù)移除祭犯。
- 結(jié)束寫事務(wù):此時該線程的更新操作才會對其他讀請求可見秸妥,更新才實際生效。
- flush memstore:當(dāng)寫緩存滿128M之后沃粗,會啟動flush線程將數(shù)據(jù)刷新到硬盤粥惧。
三、HBase 架構(gòu)設(shè)計
3.1最盅、Hbase的rowKey的設(shè)計原則
RowKey 的唯一原則
由于在 HBase 中數(shù)據(jù)存儲是 Key-Value 形式突雪,若 HBase 中同一表插入相同Rowkey,則原先的數(shù)據(jù)會被覆蓋掉(如果表的 version 設(shè)置為1的話)涡贱,所以務(wù)必保證 Rowkey 的唯一性咏删。
RowKey 的排序原則
HBase 的 RowKey 是按照 ASCII 有序設(shè)計的,在設(shè)計 RowKey 時要充分利用這點盼产。比如視頻網(wǎng)站上的彈幕信息饵婆,這個彈幕是按照時間倒排序展示視頻里,這個時候設(shè)計的 Rowkey 要和時間順序相關(guān)戏售,可以使用 “Long.MAX_VALUE - 彈幕發(fā)表時間” 的 long 值作為 Rowkey 的前綴侨核。
RowKey 的長度原則
RowKey 是一個二進(jìn)制,RowKey 的長度建議設(shè)計在 10~100 個字節(jié)灌灾,越短越好搓译。
原因有兩點:
- HBase 的持久化文件 HFile 是按照 KeyValue 存儲的,如果 RowKey 過長锋喜,比如 500 個字節(jié)些己,1000 萬列數(shù)據(jù)光 RowKey 就要占用500*1000 萬 = 50 億個字節(jié),將近 1G 數(shù)據(jù)嘿般,這會極大影響HFile的存儲效率段标;
- MemStore 緩存部分?jǐn)?shù)據(jù)到內(nèi)存,如果 RowKey 字段過長內(nèi)存的有效利用率會降低炉奴,系統(tǒng)無法緩存更多的數(shù)據(jù)逼庞,這會降低檢索效率。
- 其實不僅 RowKey 的長度是越短越好瞻赶,列族名赛糟、列名等也應(yīng)該盡量使用短名字,因為 HBase 屬于列式數(shù)據(jù)庫砸逊,這些名字都是會寫入到 HBase 的持久化文件 HFile 中去璧南,過長的 RowKey、列族师逸、列名都會導(dǎo)致整體的存儲量成倍增加司倚。
RowKey 散列原則
設(shè)計的 RowKey 應(yīng)均勻的分布在各個 HBase節(jié)點上。
拿時間戳舉例,假如 RowKey 是按系統(tǒng)時間戳的方式遞增对湃,時間戳信息作為RowKey 的第一部分崖叫,將造成所有新數(shù)據(jù)都在一個 RegionServer 上堆積,也就是通常說的 Region 熱點問題拍柒。
熱點發(fā)生在大量的 client 直接訪問集中在個別 RegionServer 上(訪問可能是讀心傀,寫或者其他操作),導(dǎo)致單個 RegionServer 機器自身負(fù)載過高拆讯,引起性能下降甚至 Region 不可用脂男,常見的是發(fā)生 jvm full gc 或者顯示 region too busy 異常情況,當(dāng)然這也會影響同一個 RegionServer 上的其他 Region种呐。
3.2宰翅、Zookeeper 在 Hbase 中作用
- 保證任何時候集群中只有一個活躍的Master,實現(xiàn)HMaster的故障恢復(fù)與自動切換爽室。
- 存儲所有的Region的尋址入口汁讼,知道哪個Region在哪臺機器上,為Client提供元數(shù)據(jù)表的存儲信息阔墩。
- 實時監(jiān)控RegionServer的狀態(tài)嘿架,將RegionServer的上下線的信息匯報給HMaster,RegionServer不直接向HMaster匯報信息啸箫,減輕HMaster的壓力耸彪,而是通過向ZK發(fā)送信息。
- 存儲 HBase 的元數(shù)據(jù)結(jié)構(gòu)(schema)忘苛,知道集群中有哪些Table蝉娜,每個Table有哪些Column Family。
HMaster扎唾、RegionServer 啟動之后將會在 Zookeeper 上注冊并創(chuàng)建節(jié)點(/hbasae/master 與 /hbase/rs/*)召川,同時 Zookeeper 通過 Heartbeat 的心跳機制來維護(hù)與監(jiān)控節(jié)點狀態(tài),一旦節(jié)點丟失心跳胸遇,則認(rèn)為該節(jié)點宕機或者下線扮宠,將清除該節(jié)點在 Zookeeper 中的注冊信息。
當(dāng) Zookeeper 中任一 RegionServer 節(jié)點狀態(tài)發(fā)生變化時狐榔,HMaster 都會收到通知,并作出相應(yīng)處理获雕,例如 RegionServer 宕機薄腻,HMaster 重新分配 Regions 至其他 RegionServer 以保證集群整體可用性。
當(dāng) HMaster 宕機時(Zookeeper監(jiān)測到心跳超時)届案,Zookeeper 中的 /hbasae/master 節(jié)點將會消失庵楷,同時 Zookeeper 通知其他備用 HMaster 節(jié)點,重新創(chuàng)建 /hbasae/master 并轉(zhuǎn)化為 active master。
3.3尽纽、hbase:meta 表
hbase:meta 表存儲了集群中所有Region的位置信息咐蚯。
- Rowkey格式:tableName,regionStartKey,regionId
- 第一個region的regionStartKey為空
- 示例:ns1:testTable,xxxxreigonid
- 只有一個列族info,包含三個列:
- regioninfo:RegionInfo的proto序列化格式弄贿,包含regionId,tableName,startKey,endKey,offline,split,replicaId等信息
- server:RegionServer對應(yīng)的server:port
- serverstartcode:RegionServer的啟動時間戳
3.4春锋、HBase WAL 持久化等級
HBase中可以通過設(shè)置WAL的持久化等級決定是否開啟WAL機制、以及HLog的落盤方式差凹。WAL的持久化等級分為如下四個等級:
- SKIP_WAL:只寫緩存期奔,不寫HLog日志。這種方式因為只寫內(nèi)存危尿,因此可以極大的提升寫入性能呐萌,但是數(shù)據(jù)有丟失的風(fēng)險。在實際應(yīng)用過程中并不建議設(shè)置此等級谊娇,除非確認(rèn)不要求數(shù)據(jù)的可靠性肺孤。
- ASYNC_WAL:異步將數(shù)據(jù)寫入HLog日志中。
- SYNC_WAL:同步將數(shù)據(jù)寫入日志文件中济欢,需要注意的是數(shù)據(jù)只是被寫入文件系統(tǒng)中赠堵,并沒有真正落盤。
- FSYNC_WAL:同步將數(shù)據(jù)寫入日志文件并強制落盤船逮。最嚴(yán)格的日志寫入等級顾腊,可以保證數(shù)據(jù)不會丟失,但是性能相對比較差挖胃。
- USER_DEFAULT:默認(rèn)如果用戶沒有指定持久化等級杂靶,HBase使用SYNC_WAL等級持久化數(shù)據(jù)。
用戶可以通過客戶端設(shè)置WAL持久化等級酱鸭,代碼:put.setDurability(Durability. SYNC_WAL );
3.5吗垮、HMaster
HBase整體架構(gòu)中HMaster的功能與職責(zé)如下:
- 管理RegionServer,監(jiān)聽其狀態(tài)凹髓,保證集群負(fù)載均衡且高可用烁登。
- 管理Region,如新Region的分配蔚舀、RegionServer宕機時該節(jié)點Region的分配與遷移饵沧。
- 接收客戶端的DDL操作,如創(chuàng)建與刪除表赌躺、列簇等信息狼牺。
- 權(quán)限控制。
3.6礼患、RegionServer
RegionServer在HBase集群中的功能與職責(zé):
- 根據(jù)HMaster的region分配請求是钥,存放和管理Region
- 接受客戶端的讀寫請求掠归,檢索與寫入數(shù)據(jù)栖博,產(chǎn)生大量IO
一個RegionServer中存儲并管理者多個Region绩鸣,是HBase集群中真正 存儲數(shù)據(jù)、接受讀寫請求 的地方冀自,是HBase架構(gòu)中最核心弹囚、同時也是最復(fù)雜的部分厨相。
BlockCache為RegionServer中的 讀緩存,一個RegionServer共用一個BlockCache余寥。
3.7领铐、HFile 存儲格式
HFile是HDFS上的文件(或大或小都有可能),現(xiàn)在HBase面臨的一個問題就是如何在HFile中 快速檢索獲得指定數(shù)據(jù)宋舷?
HBase隨機查詢的高性能很大程度上取決于底層HFile的存儲格式绪撵,所以這個問題可以轉(zhuǎn)化為 HFile的存儲格式該如何設(shè)計,才能滿足HBase 快速檢索 的需求祝蝠。
3.7.1音诈、生成一個HFile
Memstore內(nèi)存中的數(shù)據(jù)在刷寫到磁盤時,將會進(jìn)行以下操作:
- 會先現(xiàn)在內(nèi)存中創(chuàng)建 空的Data Block數(shù)據(jù)塊 包含 預(yù)留的Header空間绎狭。而后细溅,將Memstore中的KVs一個個順序?qū)憹M該Block(一般默認(rèn)大小為64KB)。
- 如果指定了壓縮或者加密算法儡嘶,Block數(shù)據(jù)寫滿之后將會對整個數(shù)據(jù)區(qū)做相應(yīng)的壓縮或者加密處理喇聊。
- 隨后在預(yù)留的Header區(qū)寫入該Block的元數(shù)據(jù)信息,如 壓縮前后大小蹦狂、上一個block的offset誓篱、checksum 等。
- 內(nèi)存中的準(zhǔn)備工作完成之后凯楔,通過HFile Writer輸出流將數(shù)據(jù)寫入到HDFS中窜骄,形成磁盤中的Data Block。
- 為輸出的Data Block生成一條索引數(shù)據(jù)摆屯,包括 {startkey邻遏、offset、size} 信息虐骑,該索引數(shù)據(jù)會被暫時記錄在內(nèi)存中的Block Index Chunk中准验。
至此,已經(jīng)完成了第一個Data Block的寫入工作廷没,Memstore中的 KVs 數(shù)據(jù)將會按照這個過程不斷進(jìn)行 寫入內(nèi)存中的Data Block -> 輸出到HDFS -> 生成索引數(shù)據(jù)保存到內(nèi)存中的Block Index Chunk 流程沟娱。
如果啟用了Bloom Filter,那么 Bloom Filter Data(位圖數(shù)據(jù)) 與 Bloom元數(shù)據(jù)(哈希函數(shù)與個數(shù)等) 將會和 KVs 數(shù)據(jù)一樣被處理:寫入內(nèi)存中的Block -> 輸出到HDFS Bloom Data Block -> 生成索引數(shù)據(jù)保存到相對應(yīng)的內(nèi)存區(qū)域中腕柜。
由此可以知道,HFile寫入過程中,Data Block 和 Bloom Data Block 是交叉存在的盏缤。
隨著輸出的Data Block越來越多砰蠢,內(nèi)存中的索引數(shù)據(jù)Block Index Chunk也會越來越大。
達(dá)到一定大小之后(默認(rèn)128KB)將會經(jīng)過類似Data Block的輸出流程寫入到HDFS中唉铜,形成 Leaf Index Block (和Data Block一樣台舱,Leaf Index Block也有對應(yīng)的Header區(qū)保留該Block的元數(shù)據(jù)信息)。
同樣的潭流,也會生成一條該 Leaf Index Block 對應(yīng)的索引記錄竞惋,保存在內(nèi)存中的 Root Block Index Chunk。
Root Index -> Leaf Data Block -> Data Block 的索引關(guān)系類似 B+樹 的結(jié)構(gòu)灰嫉。得益于多層索引拆宛,HBase可以在不讀取整個文件的情況下查找數(shù)據(jù)。
隨著內(nèi)存中最后一個 Data Block讼撒、Leaf Index Block 寫入到HDFS浑厚,形成 HFile 的 Scanned Block Section。
Root Block Index Chunk 也會從內(nèi)存中寫入HDFS根盒,形成 HFile 的 Load-On-Open Section 的一部分钳幅。
至此,一個完整的HFile已經(jīng)生成炎滞,如下圖所示:
3.7.2敢艰、檢索HFile
生成HFile之后該如何使用呢?
HFile的索引數(shù)據(jù)(包括 Bloom Filter索引和數(shù)據(jù)索引信息)會在 Region Open 的時候被加載到讀緩存中册赛,之后數(shù)據(jù)檢索經(jīng)過以下過程:
- 所有的讀請求钠导,如果讀緩存和Memstore中不存在,那么將會檢索HFile索引
- 通過Bloom Filter索引(如果有設(shè)置Bloom Filter的話)檢索Bloom Data以 快速定位HFile是否存在 所需數(shù)據(jù)
- 定位到數(shù)據(jù)可能存在的HFile之后击奶,讀取該HFile的 三層索引數(shù)據(jù)辈双,檢索數(shù)據(jù)是否存在
- 存在則根據(jù)索引中的 元數(shù)據(jù) 找到具體的 Data Block 讀入內(nèi)存,取出所需的KV數(shù)據(jù)
同時柜砾,得益于 分層索引 與 分塊存儲湃望,在Region Open加載索引數(shù)據(jù)的時候,再也不必和老版本(0.9甚至更早痰驱,HFile只有一層數(shù)據(jù)索引并且統(tǒng)一存儲)一樣加載所有索引數(shù)據(jù)到內(nèi)存中導(dǎo)致啟動緩慢甚至卡機等問題证芭。
四、HBase Split
4.1担映、為什么需要 Split废士?
數(shù)據(jù)分布不均勻。同一 region server 上數(shù)據(jù)文件越來越大蝇完,讀請求也會越來越多官硝。一旦所有的請求都落在同一個 region server 上矗蕊,尤其是很多熱點數(shù)據(jù),必然會導(dǎo)致很嚴(yán)重的性能問題氢架。
compaction性能損耗嚴(yán)重傻咖。compaction本質(zhì)上是一個排序合并的操作,合并操作需要占用大量內(nèi)存岖研,因此文件越大卿操,占用內(nèi)存越多。另一方面孙援,compaction有可能需要遷移遠(yuǎn)程數(shù)據(jù)到本地進(jìn)行處理(balance之后的compaction就會存在這樣的場景)害淤,如果需要遷移的數(shù)據(jù)是大文件的話,帶寬資源就會損耗嚴(yán)重拓售。
太大文件讀取效率也會受到影響窥摄。
通過切分,一個region變?yōu)閮蓚€近似相同大小的子region邻辉,再通過balance機制均衡到不同 region server上溪王,才能使系統(tǒng)資源使用更加均衡。
4.2值骇、Region Split 觸發(fā)條件
一是用戶通過 Api 請求 split莹菱,HBase Admin 手動執(zhí)行 split 命令時,會觸發(fā) Split吱瘩;
二是非用戶請求:
- HBase中寫請求會先寫入memstore道伟,region server 會為每個 region 分配一個默認(rèn)大小為 128M(可通過hbase.hregion.memstore.flush.size參數(shù)配置) 的 memstore,當(dāng)memstore寫滿之后使碾,會啟動flush刷新到磁盤蜜徽。flush完成之后會判斷是否需要進(jìn)行切分操作。
- 系統(tǒng)在很多場景下執(zhí)行壓縮操作(compaction操作)票摇,將多個小文件合并成一個大文件拘鞋,執(zhí)行完成之后也會判斷是否需要進(jìn)行切分操作。
這兩類操作之后都會針對相應(yīng)region生成一個requestSplit請求矢门,requestSplit首先會執(zhí)行checkSplit盆色,檢測store size是否達(dá)到閾值(具體算法見下面分析),如果超過閾值祟剔,就進(jìn)行切分隔躲。
4.3、Region 切分策略
目前已經(jīng)的支持觸發(fā)策略多達(dá)6種物延,每種觸發(fā)策略都有各自的適用場景宣旱,可以根據(jù)業(yè)務(wù)在表級別(Column family 級別)選擇不同的切分觸發(fā)策略。一般情況下使用默認(rèn)切分策略即可叛薯。
- ConstantSizeRegionSplitPolicy:0.94版本前默認(rèn)切分策略浑吟。
一個 Region 中最大 Store 的大小大于設(shè)置閾值之后才會觸發(fā)切分笙纤,Store 大小為壓縮后的文件大小(啟用壓縮的場景)组力。切分策略對于大表和小表沒有明顯的區(qū)分粪糙。 - IncreasingToUpperBoundRegionSplitPolicy:0.94版本~2.0版本默認(rèn)切分策略。
和 ConstantSizeRegionSplitPolicy 思路相同忿项,一個 Region 中最大 Store 大小大于設(shè)置閾值就會觸發(fā)切分,區(qū)別是這個閾值并不像 ConstantSizeRegionSplitPolicy 是一個固定的值城舞,而是會在不斷調(diào)整轩触。
調(diào)整規(guī)則和 Region 所屬表在當(dāng)前 RegionServer 上的 Region 個數(shù)有關(guān)系 :(#regions) * (#regions) * (#regions) * flush_size * 2,最大值為用戶設(shè)置的 MaxRegionFileSize家夺。
能夠自適應(yīng)大表和小表脱柱,這種策略下很多小表會在大集群中產(chǎn)生大量小 Region,分散在整個集群中拉馋。 - SteppingSplitPolicy:2.0版本默認(rèn)切分策略榨为。
相比 IncreasingToUpperBoundRegionSplitPolicy 簡單了一些,依然和待分裂 Region 所屬表在當(dāng)前 RegionServer 上的 Region 個數(shù)有關(guān)系:如果 Region 個數(shù)等于1煌茴,切分閾值為 flush_size * 2随闺,否則為 MaxRegionFileSize。 - KeyPrefixRegionSplitPolicy:切分策略依然依據(jù)默認(rèn)切分策略蔓腐,根據(jù) Rowkey 指定長度的前綴來切分 Region矩乐,保證相同的前綴的行保存在同一個 Region 中。由 KeyPrefixRegionSplitPolicy.prefix_length 屬性指定 Rowkey 前綴長度回论。按此長度對splitPoint進(jìn)行截取散罕。
此種策略比較適合有固定前綴的 Rowkey。當(dāng)沒有設(shè)置前綴長度傀蓉,切分效果等同與 IncreasingToUpperBoundRegionSplitPolicy欧漱。 - DelimitedKeyPrefixRegionSplitPolicy:切分策略依然依據(jù)默認(rèn)切分策略,同樣是保證相同 RowKey 前綴的數(shù)據(jù)在一個Region中葬燎,但是是以指定分隔符前面的前綴為來切分 Region误甚。
4.4、Region 切分流程
將一個region切分為兩個近似大小的子region萨蚕,首先要確定切分點靶草。切分操作是基于region執(zhí)行的,每個region有多個store(對應(yīng)多個column famliy)岳遥。系統(tǒng)首先會遍歷所有store奕翔,找到其中最大的一個,再在這個store中找出最大的HFile浩蓉,定位這個文件中心位置對應(yīng)的rowkey派继,作為region的切分點宾袜。
找到切分點之后,切分線程會初始化一個SplitTransaction對象驾窟,從字面上就可以看出來split流程是一個類似‘事務(wù)’的過程庆猫,之所以稱為類似’事務(wù)’,因為它不滿足事務(wù)的很多特性绅络,比如隔離性月培。但它卻嘗試使用寫journal的方式實現(xiàn)數(shù)據(jù)的原子性和一致性。整個過程分為三個階段:prepare - execute - (rollback) 恩急,操作模版如下:1)prepare階段:在內(nèi)存中初始化兩個子region杉畜,具體是生成兩個HRegionInfo對象,包含tableName衷恭、regionName此叠、startkey、endkey等随珠。同時會生成一個transaction journal灭袁,這個對象用來記錄切分的進(jìn)展,具體見rollback階段窗看。
2)execute階段:切分的核心操作茸歧。region server 更改ZK節(jié)點 /region-in-transition 中該region的狀態(tài)為SPLITING。
master通過watch節(jié)點/region-in-transition檢測到region狀態(tài)改變烤芦,并修改內(nèi)存中region的狀態(tài)举娩,在master頁面RIT模塊就可以看到region執(zhí)行split的狀態(tài)信息。
region 在存儲目錄下新建臨時文件夾.split保存split后的daughter region信息构罗。
parent region關(guān)閉數(shù)據(jù)寫入并觸發(fā)flush操作铜涉,將寫入region的數(shù)據(jù)全部持久化到磁盤,此后短時間內(nèi)客戶端落在父region上的請求都會拋出異常NotServingRegionException遂唧。
-
核心分裂步驟:在.split文件夾下新建兩個子文件夾芙代,稱之為daughter A、daughter B盖彭,并在文件夾中生成引用文件纹烹,分別指向父region中對應(yīng)文件。生成reference文件名如下所示:
reference文件是一個引用文件(并非linux鏈接文件)召边,文件內(nèi)容很顯然不是用戶數(shù)據(jù)铺呵。文件內(nèi)容其實非常簡單, 主要有兩部分構(gòu)成: 其一是切分點splitkey隧熙, 其二是一個boolean類型的變量( true 或者false)片挂,true表示該reference文件引用的是父文件的上半部分(top),而false表示引用的是下半部分 (bottom)。
將daughter A音念、daughter B拷貝到HBase根目錄下沪饺,形成兩個新的region。
-
parent region通知修改 hbase.meta 表后下線闷愤,不再提供服務(wù)整葡。下線后parent region在meta表中的信息并不會馬上刪除, 而是標(biāo)注split列讥脐、offline列為true遭居,并記錄兩個子region。
開啟daughter A旬渠、daughter B兩個子region魏滚。
-
通知修改 hbase.meta 表,正式對外提供服務(wù)坟漱。
3)rollback階段:如果execute階段出現(xiàn)異常,則執(zhí)行rollback操作更哄。為了實現(xiàn)回滾芋齿,整個切分過程被分為很多子階段,回滾程序會根據(jù)當(dāng)前進(jìn)展到哪個子階段清理對應(yīng)的垃圾數(shù)據(jù)成翩。代碼中使用 JournalEntryType 來表征各個子階段觅捆,具體見下圖:
4.5、通過 reference 文件如何查找到對應(yīng)的數(shù)據(jù)
根據(jù)文件名來判斷是否是 reference 文件:
- reference 文件的命名規(guī)則為前半部分為父 Region 對應(yīng)的 HFile 的文件名麻敌,后半部分是父 Region 的名稱栅炒,因此讀取的時候也根據(jù)前半部分和后半部分來定位文件。
- 根據(jù) reference 文件的內(nèi)容來確定掃描的范圍术羔,reference 的內(nèi)容包含兩部分:一部分是切分點 splitkey赢赊,另一部分是 boolean 類型的變量,如果為 true 則掃描文件的上半部分级历,反之則掃描文件的下半部分
- 接下來確定了掃描的文件释移,以及文件的掃描范圍,那就按照正常的文件檢索了
4.6寥殖、Split 對其他模塊的影響
執(zhí)行 Region Split 過程不涉及數(shù)據(jù)的移動玩讳,所以可以很快完成。新生成的子 Region 文件中沒有任何用戶數(shù)據(jù)嚼贡,而是一個 reference 文件熏纯,文件中存儲的是一些元數(shù)據(jù)信息,包括切分點的 Rowkey 等粤策。引入了以下問題:
- 父 Region 的數(shù)據(jù)什么時候會遷移到子 Region 目錄
子 Region 發(fā)生 major_compaction 時樟澜。將父 Region 目錄中屬于該子 Region 的所有數(shù)據(jù)讀出來并寫入子 Region 數(shù)據(jù)文件目錄中,這一操作符合 compaction 本身的處理邏輯掐场,因此在 compaction 中操作往扔。 - 父 Region 什么時候會被刪除
HMaster 會啟動一個線程定期檢查所有處于 splitting 狀態(tài)的父 Region贩猎,確定其是否可以被清理。檢測線程首先會在 .META. 表中找到 splitting region萍膛,并找出其生成的兩個子 Region(.META. 表中 splitA 和 splitB 列)吭服。然后檢查兩個子 Region 是否保留引用文件,如果都不存在就認(rèn)為該 splitting region 可以被刪除和下線蝗罗。
五艇棕、HBase Compaction
Compaction 會從一個region的一個store中選擇一些hfile文件進(jìn)行合并。合并說來原理很簡單串塑,先從這些待合并的數(shù)據(jù)文件中讀出KeyValues沼琉,再按照由小到大排列后寫入一個新的文件中。
HBase Compaction操作是為了數(shù)據(jù)讀取做的優(yōu)化桩匪,總的來說是以犧牲磁盤io來換取讀性能的基本穩(wěn)定打瘪。Compaction操作分為minor compaction與major compaction,其中major compaction消耗資源較大傻昙、對讀寫請求有一定影響闺骚,因此一般是禁用自動周期性執(zhí)行而選擇業(yè)務(wù)低峰期時手動執(zhí)行。
5.1妆档、為什么要執(zhí)行 Compaction僻爽?
HBase 是基于一種LSM-Tree(Log-Structured Merge Tree)存儲模型設(shè)計的,寫入路徑上是先寫入WAL(Write-Ahead-Log)即預(yù)寫日志贾惦,再寫入 memstore 緩存胸梆,滿足一定條件后執(zhí)行 flush 操作將緩存數(shù)據(jù)刷寫到磁盤,生成一個 HFile 數(shù)據(jù)文件须板。隨著數(shù)據(jù)不斷寫入碰镜,磁盤 HFile 文件就會越來越多,文件太多會影響HBase查詢性能习瑰,主要體現(xiàn)在查詢數(shù)據(jù)的 io 次數(shù)增加洋措。為了優(yōu)化查詢性能,HBase 會合并小的 HFile 以減少文件數(shù)量杰刽,這種合并 HFile 的操作稱為 Compaction菠发,這也是為什么要進(jìn)行 Compaction 的原因。
5.2贺嫂、Compaction 分類
HBase Compaction分為兩種:Minor Compaction 與 Major Compaction:
5.2.1滓鸠、Minor Compaction
Minor Compaction(0.96版本后默認(rèn)使用ExploringCompactionPolicy策略):是指選取一些小的、相鄰的StoreFile將他們合并成一個更大的StoreFile第喳,在這個過程中不會處理已經(jīng) Deleted 或 Expired 的 Cel糜俗。默認(rèn)情況,Minor操作只用來做部分文件的合并操作以及包括minVersion=0并且設(shè)置ttl的過期版本清理,不做任何刪除數(shù)據(jù)悠抹、多版本數(shù)據(jù)的清理工作珠月。
Minor Compaction 只執(zhí)行簡單的文件合并操作,選取較小的HFiles楔敌,將其中的數(shù)據(jù)順序?qū)懭胄碌腍File后啤挎,替換老的HFiles。
但是如何在眾多HFiles中選擇本次Minor Compaction要合并的文件卻有不少講究:
首先排除掉文件大小 大于 hbase.hstore.compaction.max.size 值的HFile
將HFiles按照 文件年齡排序(older to younger)卵凑,并從older file開始選擇
如果該文件大小 小于 hbase.hstore.compaction.min.size 則直接加入Minor Compaction中
如果該文件大小 小于其后面 hbase.hstore.compaction.max(默認(rèn)為10)個 HFile大小之和 * hbase.hstore.compaction.ratio(默認(rèn)為1.2)庆聘,則將該文件加入Minor Compaction中
掃描過程中柒竞,如果需要合并的 HFile 文件數(shù)達(dá)到 hbase.hstore.compaction.max(默認(rèn)為10) 則開始合并過程
掃描結(jié)束后竭沫,如果需要合并的HFile的文件數(shù) 大于 hbase.hstore.compaction.min(默認(rèn)為3) 則開始合并過程
通過 hbase.offpeak.start.hour、hbase.offpeak.end.hour 設(shè)置高峰衔憨、非高峰時期黑忱,使 hbase.hstore.compaction.ratio的值在不同時期靈活變化(高峰值1.2宴抚、非高峰值5)
Minor Compaction不會合并過大的HFile,合并的HFile數(shù)量也有嚴(yán)格的限制甫煞,以避免產(chǎn)生太大的IO操作酱塔,Minor Compaction經(jīng)常在Memstore Flush后觸發(fā),但不會對線上讀寫請求造成太大延遲影響危虱。
5.2.2、Major Compaction
相對于Minor Compaction 只合并選擇的一部分HFile合并唐全、合并時只簡單合并數(shù)據(jù)文件的特點埃跷,Major Compaction:指將所有的 StoreFile 合并成一個 StoreFile(會過濾到達(dá) maxSize 的 HFile),這個過程會清理三類沒有意義的數(shù)據(jù):被刪除的數(shù)據(jù)邮利、TTL過期數(shù)據(jù)弥雹、版本號超過設(shè)定版本號的數(shù)據(jù)。
另外延届,一般情況下剪勿,major compaction時間會持續(xù)比較長,整個過程會消耗大量系統(tǒng)資源方庭,對上層業(yè)務(wù)有比較大的影響厕吉。因此線上業(yè)務(wù)都會將關(guān)閉自動觸發(fā)major compaction 功能,改為手動在業(yè)務(wù)低峰期觸發(fā)械念。
Major Compaction定期執(zhí)行的條件由以下兩個參數(shù)控制:
- hbase.hregion.majorcompaction:默認(rèn)7天
- hbase.hregion.majorcompaction.jitter:默認(rèn)為0.2
集群中各個RegionServer將會在 hbase.hregion.majorcompaction ± hbase.hregion.majorcompaction * hbase.hregion.majorcompaction.jitter 的區(qū)間浮動進(jìn)行Major Compaction头朱,以避免過多RegionServer同時進(jìn)行,造成較大影響龄减。
5.3项钮、Compaction 的觸發(fā)時機
HBase觸發(fā)Compaction的條件有三種:memstore Flush、后臺線程周期性檢查、手動觸發(fā)烁巫。
Memstore Flush:
應(yīng)該說 compaction 操作的源頭就來自 flush 操作署隘,memstore flush 會產(chǎn)生 HFile 文件,文件越來越多就需要 compact亚隙。因此在每次執(zhí)行完 Flush 操作之后磁餐,都會對當(dāng)前 Store 中的文件數(shù)等條件進(jìn)行判斷,一旦滿足條件 恃鞋,就會觸發(fā) compaction崖媚。需要說明的是,compaction 都是以 Store 為單位進(jìn)行的恤浪,而在 Flush 觸發(fā)條件下畅哑,整個 Region 的所有 Store 都會執(zhí)行 compact,所以會在短時間內(nèi)執(zhí)行多次compaction水由。
后臺線程周期性檢查:
后臺線程 CompactionChecker 會定期檢查是否需要執(zhí)行compaction荠呐,檢查周期為hbase.server.thread.wakefrequency * hbase.server.compactchecker.interval.multiplier
,這里主要考慮的是一段時間內(nèi)沒有寫入請求仍然需要做compact檢查砂客。
其中參數(shù) hbase.server.thread.wakefrequency 默認(rèn)值 10000 即 10s泥张,是HBase服務(wù)端線程喚醒時間間隔,用于log roller鞠值、memstore flusher等操作周期性檢查媚创;參數(shù) hbase.server.compactchecker.interval.multiplier 默認(rèn)值1000,是compaction操作周期性檢查乘數(shù)因子彤恶。10 * 1000 s 時間上約等于2hrs, 46mins, 40sec钞钙。
和 Memstore flush 不同的是,該線程優(yōu)先檢查文件數(shù)声离,一旦達(dá)到閾值就會觸發(fā) minor compaction芒炼。
public boolean needsCompaction(final Collection<StoreFile> storeFiles,
final List<StoreFile> filesCompacting) {
int numCandidates = storeFiles.size() - filesCompacting.size();
return numCandidates >= comConf.getMinFilesToCompact();
}
如果不滿足,它會接著檢查是否滿足 major compaction 條件术徊,簡單來說本刽,如果當(dāng)前 store 中 hfile 的最早更新時間早于某個值 mcTime,就會觸發(fā) major compaction赠涮,HBase 預(yù)想通過這種機制定期刪除過期數(shù)據(jù)子寓。上文mcTime 是一個浮動值,浮動區(qū)間默認(rèn)為[7-7*0.5笋除,7+7*0.5]
别瞭,其中7為hbase.hregion.majorcompaction
,0.5為hbase.hregion.majorcompaction.jitter
株憾,可見默認(rèn)在7天左右就會執(zhí)行一次major compaction蝙寨。用戶如果想禁用major compaction晒衩,只需要將參數(shù)hbase.hregion.majorcompaction設(shè)為0。
手動觸發(fā):
一般來講墙歪,手動觸發(fā)compaction通常是為了執(zhí)行major compaction听系,原因有三:
其一是因為很多業(yè)務(wù)擔(dān)心自動major compaction影響讀寫性能,因此會選擇低峰期手動觸發(fā)虹菲;
其二也有可能是用戶在執(zhí)行完alter操作之后希望立刻生效靠胜,執(zhí)行手動觸發(fā)major compaction;
其三是HBase管理員發(fā)現(xiàn)硬盤容量不夠的情況下手動觸發(fā)major compaction刪除大量過期數(shù)據(jù)毕源;無論哪種觸發(fā)動機浪漠,一旦手動觸發(fā),HBase會不做很多自動化檢查霎褐,直接執(zhí)行合并址愿。
5.4、Compaction 參數(shù)解析
Major Compaction 參數(shù)
Major Compaction涉及的參數(shù)比較少冻璃,主要有大合并時間間隔與一個抖動參數(shù)因子响谓,如下:
1.hbase.hregion.majorcompaction
Major compaction周期性時間間隔,默認(rèn)值604800000省艳,單位ms娘纷。表示major compaction默認(rèn)7天調(diào)度一次,HBase 0.96.x及之前默認(rèn)為1天調(diào)度一次跋炕。設(shè)置為 0 時表示禁用自動觸發(fā)major compaction赖晶。需要強調(diào)的是一般major compaction持續(xù)時間較長、系統(tǒng)資源消耗較大辐烂,對上層業(yè)務(wù)也有比較大的影響遏插,一般生產(chǎn)環(huán)境下為了避免影響讀寫請求,會禁用自動觸發(fā)major compaction棉圈。
2.hbase.hregion.majorcompaction.jitter
Major compaction抖動參數(shù),默認(rèn)值0.5眷蜓。這個參數(shù)是為了避免major compaction同時在各個regionserver上同時發(fā)生分瘾,避免此操作給集群帶來很大壓力。 這樣節(jié)點major compaction就會在 + 或 - 兩者乘積的時間范圍內(nèi)隨機發(fā)生吁系。
Minor Compaction 參數(shù)
Minor compaction涉及的參數(shù)比major compaction要多德召,各個參數(shù)的目標(biāo)是為了選擇合適的HFile,具體參數(shù)如下:
1.hbase.hstore.compaction.min
一次minor compaction最少合并的HFile數(shù)量汽纤,默認(rèn)值 3上岗。表示至少有3個符合條件的HFile,minor compaction才會啟動蕴坪。一般情況下不建議調(diào)整該參數(shù)肴掷。
如果要調(diào)整敬锐,不建議調(diào)小該參數(shù),這樣會帶來更頻繁的壓縮呆瞻,調(diào)大該參數(shù)的同時其他相關(guān)參數(shù)也應(yīng)該做調(diào)整台夺。早期參數(shù)名稱為 hbase.hstore.compactionthreshold。
2.hbase.hstore.compaction.max
一次minor compaction最多合并的HFile數(shù)量痴脾,默認(rèn)值 10颤介。這個參數(shù)也是控制著一次壓縮的時間。一般情況下不建議調(diào)整該參數(shù)赞赖。調(diào)大該值意味著一次compaction將會合并更多的HFile滚朵,壓縮時間將會延長。
3.hbase.hstore.compaction.min.size
文件大小 < 該參數(shù)值的HFile一定是適合進(jìn)行minor compaction 的文件前域,默認(rèn)值 128M(memstore flush size)辕近。意味著小于該大小的HFile將會自動加入(automatic include)壓縮隊列。一般情況下不建議調(diào)整該參數(shù)话侄。
但是亏推,在write-heavy就是寫壓力非常大的場景,可能需要微調(diào)該參數(shù)年堆、減小參數(shù)值吞杭,假如每次memstore大小達(dá)到1~2M時就會flush生成HFile,此時生成的每個HFile都會加入壓縮隊列变丧,而且壓縮生成的HFile仍然可能小于該配置值會再次加入壓縮隊列芽狗,這樣將會導(dǎo)致壓縮隊列持續(xù)很長。
4.hbase.hstore.compaction.max.size
文件大小 > 該參數(shù)值的HFile將會被排除痒蓬,不會加入minor compaction童擎,默認(rèn)值Long.MAX_VALUE,表示沒有什么限制攻晒。一般情況下也不建議調(diào)整該參數(shù)顾复。
5.hbase.hstore.compaction.ratio
這個ratio參數(shù)的作用是判斷文件大小 > hbase.hstore.compaction.min.size 的HFile是否也是適合進(jìn)行 minor compaction 的,默認(rèn)值1.2鲁捏。更大的值將壓縮產(chǎn)生更大的HFile芯砸,建議取值范圍在1.0~1.4之間。大多數(shù)場景下也不建議調(diào)整該參數(shù)给梅。
6.hbase.hstore.compaction.ratio.offpeak
此參數(shù)與 compaction ratio 參數(shù)含義相同假丧,是在原有文件選擇策略基礎(chǔ)上增加了一個非高峰期 的ratio 控制,默認(rèn)值5.0动羽。這個參數(shù)受另外兩個參數(shù) hbase.offpeak.start.hour 與 hbase.offpeak.end.hour 控制包帚,這兩個參數(shù)值為[0, 23]的整數(shù),用于定義非高峰期時間段运吓,默認(rèn)值均為-1表示禁用非高峰期ratio設(shè)置渴邦。
5.5疯趟、Compaction 流程
一旦觸發(fā),HBase 會將該 Compaction 交由一個獨立的線程處理几莽,該線程首先會從對應(yīng) store 中選擇合適的 hfile 文件進(jìn)行合并迅办,這一步是整個 Compaction 的核心,選取文件需要遵循很多條件章蚣,比如文件數(shù)不能太多站欺、不能太少、文件大小不能太大等等纤垂,最理想的情況是矾策,選取那些承載 IO 負(fù)載重、文件小的文件集峭沦,實際實現(xiàn)中贾虽,HBase提供了多個文件選取算法:RatioBasedCompactionPolicy、ExploringCompactionPolicy 和 StripeCompactionPolicy 等吼鱼,用戶也可以通過特定接口實現(xiàn)自己的 Compaction 算法蓬豁;選出待合并的文件后,HBase會根據(jù)這些hfile文件總大小挑選對應(yīng)的線程池處理菇肃,最后對這些文件執(zhí)行具體的合并操作地粪。可以通過下圖簡單地梳理上述流程:
流程詳解:
CompactionChecker 是 RS 上的工作線程(Chore)琐谤,設(shè)置執(zhí)行周期是通過threadWakeFrequency 指定蟆技,大小通過 hbase.server.thread.wakefrequency 配置(默認(rèn)10000),然后乘以默認(rèn)倍數(shù)multiple(1000),毫秒時間轉(zhuǎn)換為秒斗忌。因此质礼,在不做參數(shù)修改的情況下,CompactionChecker大概是2hrs, 46mins, 40sec執(zhí)行一次织阳。
首先眶蕉,對于HRegion里的每個HStore進(jìn)行一次判斷,needsCompaction()判斷是否足夠多的文件觸發(fā)了Compaction的條件唧躲。
條件為:HStore 中 StoreFIles 的個數(shù) – 正在執(zhí)行 Compacting 的文件個數(shù) > minFilesToCompact
操作:以最低優(yōu)先級提交Compaction申請造挽。
步驟1:選出待執(zhí)行 Compact 的 storefiles。由于在 Store 中的文件可能已經(jīng)在進(jìn)行 Compacting惊窖,因此刽宪,這里取出未執(zhí)行 Compacting 的文件厘贼,將其加入到 Candidates 中界酒。
步驟2:執(zhí)行 compactSelection 算法,在Candidates 中選出需要進(jìn)行 compact 的文件嘴秸,并封裝成 CompactSelection 對象當(dāng)中毁欣。
- 選出過期的 store files庇谆。過濾minVersion=0,并且 storefile.maxTimeStamp + store.ttl < now_timestamp凭疮。這意味著整個文件最大的時間戳的kv饭耳,都已經(jīng)過期了,從而證明整個storefile都已經(jīng)過期了执解。CompactSelection如果發(fā)現(xiàn)這樣的storefile寞肖,會優(yōu)先選擇出來,作為Min然后提交給Store進(jìn)行處理衰腌。
這部分具體操作被封裝在ScanQueryMatcher下的ColumnTracker中新蟆,在StoreScanner的遍歷過程,ScannerQueryMatcher負(fù)責(zé)kv的過濾右蕊。這里的ScanType包括(MAJOR_COMPACT,MINOR_COMPACT,USER_SCAN)琼稻,compact操作是對選出的文件執(zhí)行一次標(biāo)識ScanType為MAJOR_COMPACT或者M(jìn)INOR_COMPACT類型的scan操作,然后將最終符合標(biāo)準(zhǔn)的kv存儲在一個新的文件中饶囚。
應(yīng)用重要參考:根據(jù)應(yīng)用的需求設(shè)置ttl帕翻,并且設(shè)置minVersions=0,根據(jù)selectCompation優(yōu)選清理過期不保留版本的文件的策略萝风,這樣會使得這部分?jǐn)?shù)據(jù)在CompactionChecker的周期內(nèi)被清理嘀掸。
誤區(qū):在CompactSplitThread有兩個配置項
hbase.regionserver.thread.compaction.large:配置largeCompactions線程池的線程個數(shù),默認(rèn)個數(shù)為1闹丐。
hbase.regionserver.thread.compaction.small:配置smallCompactions線程池的線程個數(shù)横殴,默認(rèn)個數(shù)為1。
這兩個線程池負(fù)責(zé)接收處理CR(CompactionRequest)卿拴,這兩個線程池不是根據(jù)CR來自于Major Compaction和Minor Compaction來進(jìn)行區(qū)分衫仑,而是根據(jù)一個配置hbase.regionserver.thread.compaction.throttle的設(shè)置值(一般在hbase-site.xml沒有該值的設(shè)置),而是采用默認(rèn)值2 * minFilesToCompact * memstoreFlushSize堕花,如果cr需要處理的storefile文件的大小總和文狱,大于throttle的值,則會提交到largeCompactions線程池進(jìn)行處理缘挽,反之亦然瞄崇。
應(yīng)用重要參考:可以稍微調(diào)大一些largeCompactions和smallCompactions線程池內(nèi)線程的個數(shù),建議都設(shè)置成5壕曼。
-
判斷是否需要進(jìn)行majorCompaction苏研,這是很多判斷條件的合成,其中最為重要的一個是
hbase.hregion.majorcompaction設(shè)置的值腮郊,也就是判斷上次進(jìn)行majorCompaction到當(dāng)前的時間間隔摹蘑,如果超過設(shè)置值,則滿足一個條件轧飞,同時另外一個條件是compactSelection.getFilesToCompact().size() < this.maxFilesToCompact衅鹿。
因此撒踪,通過設(shè)置hbase.hregion.majorcompaction = 0可以關(guān)閉CompactionChecke觸發(fā)的major compaction,但是無法關(guān)閉用戶調(diào)用級別的mc大渤。
過濾對于大文件進(jìn)行Compaction 操作制妄。判斷fileToCompact隊列中的文件是否超過了maxCompactSize,如果超過泵三,則過濾掉該文件耕捞,避免對于大文件進(jìn)行compaction。
如果確定 Minor Compaction 方式執(zhí)行烫幕,會檢查經(jīng)過過濾過的fileToCompact的大小是否滿足minFilesToCompact 最低標(biāo)準(zhǔn)砸脊,如果不滿足,忽略本次操作纬霞。確定執(zhí)行的Minor Compaction的操作時凌埂,會使用一個smart算法,從filesToCompact當(dāng)中選出匹配的storefiles诗芜。
通過selectCompaction 選出的文件瞳抓,加入到filesCompacting隊列中。
創(chuàng)建compactionRequest伏恐,提交請求孩哑。
5.6、Compaction HBase做了什么翠桦?
第一步:獲取需要合并的HFile列表
獲取列表的時候需要排除掉帶鎖的HFile横蜒。
鎖分兩種:寫鎖(write lock)和讀鎖(read lock)。當(dāng)HFile正在進(jìn)行以下操作的時候會上鎖:
- 用戶正在scan查詢:上Region讀鎖(region read lock)销凑。
- Region正在切分(split):此時Region會先關(guān)閉丛晌,然后上Region寫鎖(region write lock)。
- Region關(guān)閉:上Region寫鎖(region write lock)斗幼。
- Region批量導(dǎo)入:上Region寫鎖(region write lock)澎蛛。
第二步:由列表創(chuàng)建出StoreFileScanner
HRegion會創(chuàng)建出一個Scanner,用這個Scanner來讀取本次要合并的所有StoreFile上的數(shù)據(jù)蜕窿。
第三步:把數(shù)據(jù)從這些HFile中讀出谋逻,并放到tmp目錄(臨時文件夾)
HBase會在臨時目錄中創(chuàng)建新的HFile,并使用之前建立的Scanner從舊HFile上讀取數(shù)據(jù)桐经,放入新HFile毁兆。以下兩種數(shù)據(jù)不會被讀取出來:如果數(shù)據(jù)過期了(達(dá)到TTL所規(guī)定的時間),那么這些數(shù)據(jù)不會被讀取出來阴挣。
如果是 Major Compaction气堕,那么數(shù)據(jù)帶了墓碑標(biāo)記也不會被讀取出來。
第四步:用合并后的HFile來替換合并前的那些HFile
最后用臨時文件夾內(nèi)合并后的新HFile來替換掉之前的那些HFile文件。過期的數(shù)據(jù)由于沒有被讀取出來送巡,所以就永遠(yuǎn)地消失了。如果本次合并是Major Compaction盒卸,那么帶有墓碑標(biāo)記的文件也因為沒有被讀取出來骗爆,就真正地被刪除掉了。
5.7蔽介、Major Compaction 真正刪除數(shù)據(jù)
其實HBase一直拖到majorCompaction的時候才真正把帶墓碑標(biāo)記的數(shù)據(jù)刪掉摘投,并不是因為性能要求,而是之前真的做不到虹蓄。
HBase是建立在HDFS這種只有增加刪除而沒有修改的文件系統(tǒng)之上的犀呼,所以就連用戶刪除這個動作,在底層都是由新增實現(xiàn)的:
- 用戶增加一條數(shù)據(jù)就在HFile上增加一條KeyValue薇组,類型是PUT外臂。
- 用戶刪除一條數(shù)據(jù)還是在HFile上增加一條KeyValue,類型是DELETE律胀,這就是墓碑標(biāo)記宋光。所以墓碑標(biāo)記沒有什么神秘的,它也就只是另外一個KeyValue炭菌,只不過value沒有值罪佳,而類型是DELETE。
現(xiàn)在會遇到一個問題:當(dāng)用戶刪除數(shù)據(jù)的時候之前的數(shù)據(jù)已經(jīng)被刷寫到磁盤上的另外一個HFile了黑低。這種情況很常見赘艳,也就是說,墓碑標(biāo)記和原始數(shù)據(jù)這兩個KeyValue 壓根可能就不在同一個HFile上克握。
在查詢的時候Scan指針其實是把所有的HFile都看過了一遍蕾管,它知道了有這條數(shù)據(jù),也知道它有墓碑標(biāo)記菩暗,而在返回數(shù)據(jù)的時候選擇不把數(shù)據(jù)返回給用戶娇掏,這樣在用戶的Scan操作看來這條數(shù)據(jù)就是被刪掉了。
如果你可以帶上RAW=>true參數(shù)來Scan勋眯,你就可以查詢到這條被打上墓碑標(biāo)記的數(shù)據(jù)婴梧。
5.8、TTL 的數(shù)據(jù)被 Minor Compaction 刪除
這是因為當(dāng)數(shù)據(jù)達(dá)到TTL的時候客蹋,并不需要額外的一個KeyValue來記錄塞蹭。合并時創(chuàng)建的Scan在查詢數(shù)據(jù)的時候,根據(jù)以下公式來判斷cell是否過期:
當(dāng)前時間 - cell 的 timestamp > TTL
如果過期了就不返回這條數(shù)據(jù)讶坯。這樣當(dāng)合并完成后番电,過期的數(shù)據(jù)因為沒有被寫入新文件,自然就消失了。
六漱办、HBase Flush
6.1这刷、Memstore Flush 觸發(fā)條件
1、Memstore 級別限制:當(dāng)Region中任意一個MemStore的大小達(dá)到了上限(hbase.hregion.memstore.flush.size娩井,默認(rèn)128MB)暇屋,會觸發(fā)Memstore刷新。
2洞辣、Region 級別限制:當(dāng)Region中所有Memstore的大小總和達(dá)到了上限(hbase.hregion.memstore.block.multiplier * hbase.hregion.memstore.flush.size咐刨,默認(rèn) 2* 128M = 256M),會觸發(fā)memstore刷新扬霜。
3定鸟、Region Server 級別限制:當(dāng)一個Region Server中所有Memstore的大小總和達(dá)到了上限(hbase.regionserver.global.memstore.upperLimit * hbase_heapsize,默認(rèn) 40%的JVM內(nèi)存使用量),會觸發(fā)部分Memstore刷新。Flush順序是按照Memstore由大到小執(zhí)行乞娄,先Flush Memstore最大的Region娜扇,再執(zhí)行次大的,直至總體Memstore內(nèi)存使用量低于閾值(hbase.regionserver.global.memstore.lowerLimit * hbase_heapsize,默認(rèn) 38%的JVM內(nèi)存使用量)。
4、當(dāng)一個Region Server中HLog數(shù)量達(dá)到上限(可通過參數(shù)hbase.regionserver.maxlogs配置)時麦向,系統(tǒng)會選取最早的一個 HLog對應(yīng)的一個或多個Region進(jìn)行flush
5、HBase定期刷新Memstore:默認(rèn)周期為1小時客叉,確保Memstore不會長時間沒有持久化诵竭。為避免所有的MemStore在同一時間都進(jìn)行flush導(dǎo)致的問題,定期的flush操作有20000左右的隨機延時卵慰。
6、手動執(zhí)行flush:用戶可以通過shell命令 flush ‘tablename’或者flush ‘region name’分別對一個表或者一個Region進(jìn)行flush裳朋。
七吓著、性能優(yōu)化
7.1、Region規(guī)劃
對于Region的大小绑莺,HBase官方文檔推薦單個在10G-30G 之間暖眼,單臺RegionServer的數(shù)量控制在20-300之間(當(dāng)然,這僅僅是參考值)纺裁。
Region過大過小都會有不良影響:
- 過大的Region
- 優(yōu)點:遷移速度快诫肠、減少總RPC請求
- 缺點:compaction的時候資源消耗非常大司澎、可能會有數(shù)據(jù)分散不均衡的問題
- 過小的Region
- 優(yōu)點:集群負(fù)載平衡、HFile比較少compaction影響小
- 缺點:遷移或者balance效率低栋豫、頻繁flush導(dǎo)致頻繁的compaction挤安、維護(hù)開銷大
規(guī)劃Region的大小與數(shù)量時可以參考以下算法:
0. 計算HBase可用磁盤空間(單臺RegionServer)
1. 設(shè)置region最大與最小閾值,region的大小在此區(qū)間選擇丧鸯,如10-30G
2. 設(shè)置最佳region數(shù)(這是一個經(jīng)驗值)蛤铜,如單臺RegionServer 200個
3. 從region最小值開始,計算 HBase可用磁盤空間 / (region_size * hdfs副本數(shù)) = region個數(shù)
4. 得到的region個數(shù)如果 > 200骡送,則增大region_size(step可設(shè)置為5G),繼續(xù)計算直至找到region個數(shù)最接近200的region_size大小
5. region大小建議不小于10G
比如當(dāng)前可用磁盤空間為18T絮记,選擇的region大小范圍為10-30G摔踱,最佳region個數(shù)為300。
那么最接近 最佳Region個數(shù)300的 region_size 值為30G怨愤。
得到以下配置項:
- hbase.hregion.max.filesize=30G
- 單節(jié)點最多可存儲的Region個數(shù)約為300
7.2派敷、內(nèi)存規(guī)劃
RegionServer中的BlockCache有兩種實現(xiàn)方式:
- LRUBlockCache:On-Heap
- BucketCache:Off-Heap
我們可以根據(jù)可用內(nèi)存大小來判斷使用哪種內(nèi)存模式。
1)先看 超小內(nèi)存(假設(shè)8G以下) 和 超大內(nèi)存(假設(shè)128G以上) 兩種極端情況:
對于超小內(nèi)存來說撰洗,即使可以使用BucketCache來利用堆外內(nèi)存,但是使用堆外內(nèi)存的主要目的是避免GC時不穩(wěn)定的影響试躏,堆外內(nèi)存的效率是要比堆內(nèi)內(nèi)存低的颠蕴。由于內(nèi)存總體較小犀被,即使讀寫緩存都在堆內(nèi)內(nèi)存中,GC時也不會造成太大影響西轩,所以可以直接選擇LRUBlockCache遭商。
對于超大內(nèi)存來說劫流,在超大內(nèi)存上使用LRUBlockCache將會出現(xiàn)我們所擔(dān)憂的情況:GC時對線上造成很不穩(wěn)定的延遲影響仍秤。這種場景下诗力,應(yīng)該盡量利用堆外內(nèi)存作為讀緩存,減小堆內(nèi)內(nèi)存的壓力瓣窄,所以可以直接選擇BucketCache俺夕。
2)在兩邊的極端情況下,我們可以根據(jù)內(nèi)存大小選擇合適的內(nèi)存模式映九,那么如果內(nèi)存大小在合理氯迂、正常的范圍內(nèi)該如何選擇呢嚼蚀?
此時我們應(yīng)該主要關(guān)注業(yè)務(wù)應(yīng)用的類型。
當(dāng)業(yè)務(wù)主要為寫多讀少型應(yīng)用時导帝,寫緩存利用率高您单,應(yīng)該使用LRUBlockCache盡量提高堆內(nèi)寫緩存的使用率虐秦。
當(dāng)業(yè)務(wù)主要為寫少讀多型應(yīng)用時蜈彼,讀緩存利用率高(通常也意味著需要穩(wěn)定的低延遲響應(yīng))幸逆,應(yīng)該使用BucketCache盡量提高堆外讀緩存的使用率。
3)對于不明確或者多種類型混合的業(yè)務(wù)應(yīng)用拍顷,建議使用BucketCache菇怀,保證讀請求的穩(wěn)定性同時,堆內(nèi)寫緩存效率并不會很低匆背。
如果 HBase可使用的內(nèi)存高達(dá)153G钝尸,故將選擇BucketCache的內(nèi)存模型來配置HBase,該模式下能夠最大化利用內(nèi)存猪叙,減少GC影響穴翩,對線上的實時服務(wù)較為有利芒帕。
得到配置項:
- hbase.bucketcache.ioengine=offheap: 使用堆外緩存
7.3鉴分、內(nèi)存與磁盤比
從 HBase集群規(guī)劃 引入一個Disk / JavaHeap Ratio 的概念來幫助我們設(shè)置內(nèi)存相關(guān)的參數(shù)冠场。
理論上我們假設(shè) 最優(yōu) 情況下 硬盤維度下的Region個數(shù) 和 JavaHeap維度下的Region個數(shù) 相等。
相應(yīng)的計算公式為:
- 硬盤容量維度下Region個數(shù): DiskSize / (RegionSize * ReplicationFactor)
- JavaHeap維度下Region個數(shù): JavaHeap * HeapFractionForMemstore / (MemstoreSize / 2 )
其中默認(rèn)配置下:
- RegionSize = 10G:Region大小舔株,配置項:hbase.hregion.max.filesize
- ReplicationFactor = 3:HDFS的副本數(shù)载慈,配置項:dfs.replication
- HeapFractionForMemstore = 0.4:JavaHeap寫緩存大小,即RegionServer內(nèi)存中Memstore的總大小寡具,配置項:hbase.regionserver.global.memstore.lowerLimit
- MemstoreSize = 128M:Memstore刷寫大小童叠,配置項:hbase.hregion.memstore.flush.size
現(xiàn)在我們已知條件 硬盤維度和JavaHeap維度相等厦坛,求 1 bytes的JavaHeap大小需要搭配多大的硬盤大小 。
已知:
DiskSize / (RegionSize * ReplicationFactor) = JavaHeap * HeapFractionForMemstore / (MemstoreSize / 2 )
求:
DiskSize / JavaHeap
進(jìn)行簡單的交換運算可得:
DiskSize / JavaHeap = RegionSize / MemstoreSize * ReplicationFactor * HeapFractionForMemstore * 2
10G / 128M * 3 * 0.4 * 2 = 192撬碟,即理想狀態(tài)下 RegionServer上 1 bytes的Java內(nèi)存大小需要搭配192bytes的硬盤大小最合適小作。
7.4、內(nèi)存布局
BucketCache模式下坝撑,RegionServer的內(nèi)存劃分如下圖:
簡化版:
7.4.1扶认、寫緩存
從架構(gòu)原理中知道辐宾,Memstore有6種情況的Flush叠纹,需要我們關(guān)注的是 Memstore、Region和RegionServer級別的刷寫持偏。
其中Memstore和Region級別的刷寫并不會對線上造成太大影響鸿秆,但可以控制其閾值和刷寫頻次來進(jìn)一步提高性能谬莹。
而RegionServer級別的刷寫將會阻塞請求直至刷寫完成,對線上影響巨大井誉,需要盡量避免颗圣。
配置項:
-
hbase.hregion.memstore.flush.size=256M: 控制的Memstore大小默認(rèn)值為128M,太過頻繁的刷寫會導(dǎo)致IO繁忙蔽午,刷新隊列阻塞等及老。
設(shè)置太高也有壞處食铐,可能會較為頻繁的觸發(fā)RegionServer級別的Flush虐呻,這里設(shè)置為256M铃慷。 - hbase.hregion.memstore.block.multiplier=3: 控制的Region flush上限默認(rèn)值為2,意味著一個Region中最大同時存儲的Memstore大小為2 * MemstoreSize 馋缅,如果一個表的列族過多將頻繁觸發(fā)萤悴,該值視情況調(diào)整覆履。
7.4.2、讀緩存
讀緩存由 堆內(nèi)的LRU元數(shù)據(jù) 與 堆外的數(shù)據(jù)緩存 組成伟众,兩部分占比一般為 1:9(經(jīng)驗值)
而對于總體的堆內(nèi)內(nèi)存凳厢,存在以下限制先紫,如果超出此限制則應(yīng)該調(diào)低比例:
LRUBlockCache + MemStore < 80% * JVM_HEAP
配置堆外緩存涉及到的相關(guān)參數(shù)如下:
- hbase.bucketcache.size=111 * 1024M: 堆外緩存大小遮精,單位為M
- hbase.bucketcache.percentage.in.combinedcache=0.9: 堆外讀緩存所占比例仑鸥,剩余為堆內(nèi)元數(shù)據(jù)緩存大小
- hfile.block.cache.size=0.15: 校驗項意狠,+upperLimit需要小于0.8