HBase那些事
@(大數(shù)據(jù)工程學(xué)院)[HBase, Hadoop, 優(yōu)化, HadoopChen, hbase]
[TOC]
HBase特性
HBase是什么
HBase(Hadoop Database)妆档,一個(gè)高可靠性事镣、高性能匾旭、面向列褥紫、可伸縮、 實(shí)時(shí)讀寫的分布式數(shù)據(jù)庫(kù)肮之。
選擇特性
- 列式儲(chǔ)存:方便存儲(chǔ)結(jié)構(gòu)化和半結(jié)構(gòu)化數(shù)據(jù)趾访,方便做數(shù)據(jù)壓縮,對(duì)針對(duì)某一列查詢有非常大的IO優(yōu)勢(shì)惩激;
- KV儲(chǔ)存:可以通過(guò)key快速查詢到其value,任意的value格式蟹演;
- 海量數(shù)據(jù):PB級(jí)风钻;
- 低延時(shí):毫秒級(jí)別;
- 高吞吐量:百萬(wàn)查詢/每秒酒请;
- 主鍵索引:基于RowKey的索引骡技,快速檢索KV值;
- Hadoop集成:文件存儲(chǔ)于HDFS之上羞反,高效集成Hive布朦、Spark;
- BigTable:HBase是Google的BigTable架構(gòu)的一個(gè)開源實(shí)現(xiàn);
- CRUD: 支持增刪查改操作昼窗;
- 支持Row級(jí)別事務(wù):HBase本身均是基于Row級(jí)別的操作是趴,所以行鎖即可以保證ACID;
- 稀疏存儲(chǔ): 節(jié)省空間澄惊、提高查詢效率唆途、便于壓縮;
- 支持非(半)結(jié)構(gòu)化:列式儲(chǔ)存和KV結(jié)構(gòu)缤削;
- 容錯(cuò)性:分布式架構(gòu)窘哈,高效錯(cuò)誤轉(zhuǎn)移;
- 硬件成本低廉:類似于 Hadoop 的分布式集群亭敢,硬件成本低廉滚婉;
- 第三方SQL支持:phoenix、hive帅刀、sparkSql等等让腹;
- 開源:基于java開發(fā)的開源軟件,社區(qū)活躍扣溺;
HBase環(huán)境搭建
CDH
CM安裝HBase比較簡(jiǎn)單骇窍,參照安裝步驟即可:
分布式環(huán)境
前提條件
- Hadoop集群:hadoop01,hadoop02锥余, hadoop03
- 用戶互信
- HBase安裝包
- JDK
- Zookeeper
安裝部署
- 解壓安裝包:sudo tar -zxf hbase-1.2.4-bin.tar.gz -C /usr/local/hbase/
- 配置環(huán)境變量:vi /etc/profile ; export HBASE_HOME=/usr/local/hbase/
- 修改配置文件:$HBASE_HOME/conf/hbase-site.xml
<configuration>
<property>
<name>hbase.rootdir</name>
<value>hdfs://nameservice1/hbase</value>
</property>
<property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<property>
<name>hbase.zookeeper.quorum</name>
<value>hadoop01:2181,hadoop02:2181,hadoop03:2181</value>
</property>
</configuration>
- 修改啟動(dòng)腳本:$HBASE_HOME/conf/hbase-env.sh腹纳; export JAVA_HOME=/usr/java/jdk/
- 修改HMaster配置:$HBASE_HOME/conf/regionservers
hadoop01
hadoop02
hadoop03
- 新增備用HMater: $HBASE_HOME/conf/backup-masters
hadoop02
- 啟動(dòng)集群:$HBASE_HOME/bin/start-hbase.sh
- 驗(yàn)證環(huán)境:
create 'hello', 'cf'
put 'hello', 'one', 'cf:a', 'b'
get 'hello', 'one'
HBase架構(gòu)
看圖說(shuō)話
HBase采用Master/Slave架構(gòu)搭建集群,它隸屬于Hadoop生態(tài)系統(tǒng),由一下類型節(jié)點(diǎn)組成:HMaster節(jié)點(diǎn)嘲恍、HRegionServer節(jié)點(diǎn)足画、ZooKeeper集群,而在底層佃牛,它將數(shù)據(jù)存儲(chǔ)于HDFS中淹辞,因而涉及到HDFS的NameNode、DataNode等俘侠,總體結(jié)構(gòu)如下:
- HMaster :
- 管理HRegionServer象缀,實(shí)現(xiàn)其負(fù)載均衡;
- 管理和分配HRegion爷速;實(shí)現(xiàn)DDL操作央星;
- 管理namespace和table的元數(shù)據(jù);權(quán)限控制遍希;
- HRegionServer :
- 存放和管理本地HRegion等曼;
- 讀寫HDFS,管理Table中的數(shù)據(jù)凿蒜;
- Client直接通過(guò)HRegionServer讀寫數(shù)據(jù);
- ZooKeeper :
- 存放整個(gè) HBase集群的元數(shù)據(jù)以及集群的狀態(tài)信息胁黑;
- 實(shí)現(xiàn)HMaster主從節(jié)點(diǎn)的failover废封。
- HRegion:
- HBase表數(shù)據(jù)按行切分成多個(gè)HRegion;
- HRegion按照列簇切分成多個(gè)Store;
- 每個(gè)Store由一個(gè)MemStore和多個(gè)StoreFile(HFile)組成丧蘸;
- 每個(gè)HRegion還包含一個(gè)HLog文件漂洋,用于數(shù)據(jù)恢復(fù);
- Hadoop:
- RegionServer通過(guò)DFS Client讀寫HDFS數(shù)據(jù)力喷;
- RS和DN盡量保證在統(tǒng)一節(jié)點(diǎn)刽漂,確保數(shù)據(jù)本地化,提升讀寫性能弟孟;
- MemStore滿足一定條件下會(huì)Flush到HDFS上的HFile文件贝咙。
HBaseTable
HBase表主要包含namespace(命名空間)、tableName(表名)拂募、rowKey(主鍵)庭猩、columnFamily(列簇)、qualifier(列)陈症、cell(值)蔼水、timeStamp(版本),用結(jié)構(gòu)化的形式展現(xiàn)如下:
| NameSpace | TableName | RowKey | CF1:Name | CF2:Age
| -------- | -----: | :----: |
| Default | HelloWorld | 001 | Allen | 28
| Default | HelloWorld | 002 | Curry | 26
| Default | HelloWorld | 003 | Brant | 20
| Default | HelloWorld | 004 | Sean | 31
- 存儲(chǔ):表HelloWorld數(shù)據(jù)量較小录肯,暫時(shí)會(huì)保存在一個(gè)RegionServer的一個(gè)Region內(nèi)趴腋;
- Split:當(dāng)Region達(dá)到最大Region限制(假定256M)后,則會(huì)Split成兩個(gè)Region,這里假定001和002在RegionA优炬,003和004在RegionB:
讀流程
HBase表數(shù)據(jù)所在Region颁井,Region所在RegionServer,均存放在HBase表hbase:meta穿剖,由于元數(shù)據(jù)較小蚤蔓,一個(gè)2G的HRegion大概可以支持4PB的數(shù)據(jù),所以meta表是不可Split的糊余。Meta表本身是一個(gè)HBase表秀又,該表所在RS的地址存放在ZK,于是讀數(shù)據(jù)的流程如下:
- 需求:從HellWorld表讀取002這條記錄贬芥;
- Client從ZK讀取meta表所在RegionServer地址信息吐辙;
- Client和Meta表所在RS建立連接,獲取HelloWorld表當(dāng)中RowKey=002的記錄所在的RS地址蘸劈;并將該地址緩存在客戶端昏苏;
- Client和002所在RS建立連接,申請(qǐng)查詢002記錄數(shù)據(jù)威沫;
- 002所在RS根據(jù)RowKey獲取Region信息贤惯,并初始化Scaner,Scaner優(yōu)先掃描BlockCache棒掠,然后掃描MemStore孵构,然后掃描StoreFile(HFile),直到找到002記錄為止烟很;
- RS返回結(jié)果數(shù)據(jù)給Client颈墅。
寫流程
- 需求:寫入005到HelloWorld表;
- Client從ZK去讀Meta表所在RS地址雾袱;
- Client讀取Meta表數(shù)據(jù)恤筛,確認(rèn)005應(yīng)該寫入RS地址;
- Client將005數(shù)據(jù)寫入RS02的HLog芹橡,用于災(zāi)備恢復(fù)毒坛,然后寫入RegionB的memStore;此刻寫操作已完成僻族,反饋給client粘驰;
- 當(dāng)memStore滿足一定條件下會(huì)Flush到hdfs,hdfs再根據(jù)寫策略復(fù)制副本述么。
Split和Compaction
HBase擴(kuò)展和負(fù)載均衡的基本單位是Region蝌数。Region從本質(zhì)上說(shuō)是行的集合。當(dāng)Region的大小達(dá)到一定的閾值度秘,該Region會(huì)自動(dòng)分裂(split)顶伞,或者當(dāng)該Region的HFile數(shù)太多饵撑,也會(huì)自動(dòng)合并(compaction)。
對(duì)于一張表(HTable)而言唆貌,初始時(shí)只會(huì)有一個(gè)Region滑潘。表的數(shù)據(jù)量不斷增加,系統(tǒng)會(huì)監(jiān)控此表以確保數(shù)據(jù)量不會(huì)超過(guò)一個(gè)配置的閾值锨咙。如果系統(tǒng)發(fā)現(xiàn)表容量超過(guò)了限制语卤,該Region會(huì)被一分為二。分裂主要看行鍵(row key)酪刀,從Region正中的鍵開始分裂粹舵,并創(chuàng)建容量大致相等的兩個(gè)Region。
根據(jù)上述寫流程會(huì)發(fā)現(xiàn)HBase是一種Log-Structured Merge Tree架構(gòu)模式骂倘,用戶數(shù)據(jù)寫入先寫WAL眼滤,再寫緩存,滿足一定條件后緩存數(shù)據(jù)會(huì)執(zhí)行flush操作真正落盤历涝,形成一個(gè)數(shù)據(jù)文件HFile诅需。隨著數(shù)據(jù)寫入不斷增多,flush次數(shù)也會(huì)不斷增多荧库,進(jìn)而HFile數(shù)據(jù)文件就會(huì)越來(lái)越多堰塌。然而,太多數(shù)據(jù)文件會(huì)導(dǎo)致數(shù)據(jù)查詢IO次數(shù)增多分衫,因此HBase嘗試著不斷對(duì)這些文件進(jìn)行合并蔫仙,這個(gè)合并過(guò)程稱為Compaction。
BlockCache
上述讀流程中RS會(huì)優(yōu)先掃描BlockCache丐箩,BlockCache是一個(gè)讀緩存,即“引用局部性”原理(也應(yīng)用于CPU恤煞,分空間局部性和時(shí)間局部性屎勘,空間局部性是指CPU在某一時(shí)刻需要某個(gè)數(shù)據(jù),那么有很大的概率在一下時(shí)刻它需要的數(shù)據(jù)在其附近居扒;時(shí)間局部性是指某個(gè)數(shù)據(jù)在被訪問(wèn)過(guò)一次后概漱,它有很大的概率在不久的將來(lái)會(huì)被再次的訪問(wèn)),將數(shù)據(jù)預(yù)讀取到內(nèi)存中喜喂,以提升讀的性能瓤摧。
HBase數(shù)據(jù)按照block塊存儲(chǔ),默認(rèn)是64K玉吁,HBase中Block分為四種類型:
- Data Block用于存儲(chǔ)實(shí)際數(shù)據(jù)照弥,通常情況下每個(gè)Data Block可以存放多條KeyValue數(shù)據(jù)對(duì);
- Index Block通過(guò)存儲(chǔ)索引數(shù)據(jù)加快數(shù)據(jù)查找进副;
- Bloom Block通過(guò)一定算法可以過(guò)濾掉部分一定不存在待查KeyValue的數(shù)據(jù)文件这揣,減少不必要的IO操作;
- Meta Block主要存儲(chǔ)整個(gè)HFile的元數(shù)據(jù)。
HBase中提供兩種BlockCache的實(shí)現(xiàn):默認(rèn)on-heap LruBlockCache和BucketCache(通常是off-heap)给赞。通常BucketCache的性能要差于LruBlockCache机打,然而由于GC的影響,LruBlockCache的延遲會(huì)變的不穩(wěn)定片迅,而BucketCache由于是自己管理BlockCache残邀,而不需要GC,因而它的延遲通常比較穩(wěn)定柑蛇,這也是有些時(shí)候需要選用BucketCache的原因芥挣。
- LruBlockCache :HBase默認(rèn)的BlockCache實(shí)現(xiàn)方案。Block數(shù)據(jù)塊都存儲(chǔ)在 JVM heap內(nèi)唯蝶,由JVM進(jìn)行垃圾回收管理九秀。它將內(nèi)存從邏輯上分為了三塊:single-access區(qū)、mutil-access區(qū)粘我、in-memory區(qū)鼓蜒,分別占到整個(gè)BlockCache大小的25%、50%征字、25%都弹。一次隨機(jī)讀中,一個(gè)Block塊從HDFS中加載出來(lái)之后首先放入signle區(qū)匙姜,后續(xù)如果有多次請(qǐng)求訪問(wèn)到這塊數(shù)據(jù)的話畅厢,就會(huì)將這塊數(shù)據(jù)移到mutil-access區(qū)。而in-memory區(qū)表示數(shù)據(jù)可以常駐內(nèi)存氮昧,一般用來(lái)存放訪問(wèn)頻繁框杜、數(shù)據(jù)量小的數(shù)據(jù),比如元數(shù)據(jù)袖肥,用戶也可以在建表的時(shí)候通過(guò)設(shè)置列族屬性IN-MEMORY= true將此列族放入in-memory區(qū)咪辱。很顯然,這種設(shè)計(jì)策略類似于JVM中young區(qū)、old區(qū)以及perm區(qū)。無(wú)論哪個(gè)區(qū)艾船,系統(tǒng)都會(huì)采用嚴(yán)格的Least-Recently-Used算法定续,當(dāng)BlockCache總量達(dá)到一定閾值之后就會(huì)啟動(dòng)淘汰機(jī)制,最少使用的Block會(huì)被置換出來(lái),為新加載的Block預(yù)留空間。
- BucketCache :很明顯LruBlockCache的缺陷是GC,當(dāng)吞吐量徒增磷蛹,GC不及時(shí)則會(huì)造成RS因?yàn)镺OM停止工作;于是BucketCache引入了堆外內(nèi)存來(lái)緩存Block數(shù)據(jù)填渠,當(dāng)然BucketCache通過(guò)配置可以工作在三種模式下:heap弦聂,offheap和file鸟辅。無(wú)論工作在那種模式下,BucketCache都會(huì)申請(qǐng)?jiān)S多帶有固定大小標(biāo)簽的Bucket莺葫,一種Bucket存儲(chǔ)一種指定BlockSize的數(shù)據(jù)塊匪凉,BucketCache會(huì)在初始化的時(shí)候申請(qǐng)14個(gè)不同大小的Bucket,而且即使在某一種Bucket空間不足的情況下捺檬,系統(tǒng)也會(huì)從其他Bucket空間借用內(nèi)存使用再层,不會(huì)出現(xiàn)內(nèi)存使用率低的情況。接下來(lái)再來(lái)看看不同工作模式堡纬,heap模式表示這些Bucket是從JVM Heap中申請(qǐng)聂受,offheap模式使用DirectByteBuffer技術(shù)實(shí)現(xiàn)堆外內(nèi)存存儲(chǔ)管理,而file模式使用類似SSD的高速緩存文件存儲(chǔ)數(shù)據(jù)塊烤镐。
- CombinedBlockCache :實(shí)際實(shí)現(xiàn)中蛋济,HBase將BucketCache和LRUBlockCache搭配使用,稱為CombinedBlockCache炮叶。和DoubleBlockCache不同碗旅,系統(tǒng)在LRUBlockCache中主要存儲(chǔ)Index Block和Bloom Block,而將Data Block存儲(chǔ)在BucketCache中镜悉。因此一次隨機(jī)讀需要首先在LRUBlockCache中查到對(duì)應(yīng)的Index Block祟辟,然后再到BucketCache查找對(duì)應(yīng)數(shù)據(jù)塊。BucketCache通過(guò)更加合理的設(shè)計(jì)極大降低了JVM GC對(duì)業(yè)務(wù)請(qǐng)求的實(shí)際影響侣肄。
MemStore
HBase寫入數(shù)據(jù)會(huì)先寫WAL旧困,再寫緩存,WAL是用于RS故障后的數(shù)據(jù)恢復(fù)稼锅,而緩存MemStore則是為了提高寫入數(shù)據(jù)的性能吼具。但MemStore是基于內(nèi)存,一方面空間有限矩距,一方面數(shù)據(jù)容易丟失馍悟,所以RegionServer會(huì)在滿足一定條件下講MemStore數(shù)據(jù)Flush到HDFS,條件如下:
Memstore級(jí)別限制:當(dāng)Region中任意一個(gè)MemStore的大小達(dá)到了上限(hbase.hregion.memstore.flush.size剩晴,默認(rèn)128MB),會(huì)觸發(fā)Memstore刷新侵状。
Region級(jí)別限制:當(dāng)Region中所有Memstore的大小總和達(dá)到了上限(hbase.hregion.memstore.block.multiplier * hbase.hregion.memstore.flush.size赞弥,默認(rèn) 2* 128M = 256M),會(huì)觸發(fā)memstore刷新趣兄。
Region Server級(jí)別限制:當(dāng)一個(gè)Region Server中所有Memstore的大小總和達(dá)到了上限(hbase.regionserver.global.memstore.upperLimit * hbase_heapsize绽左,默認(rèn) 40%的JVM內(nèi)存使用量),會(huì)觸發(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)存使用量)总棵;HBase1.0后使用新參數(shù)hbase.regionserver.global.memstore.size。
當(dāng)一個(gè)Region Server中HLog數(shù)量達(dá)到上限(可通過(guò)參數(shù)hbase.regionserver.maxlogs配置)時(shí)改含,系統(tǒng)會(huì)選取最早的一個(gè) HLog對(duì)應(yīng)的一個(gè)或多個(gè)Region進(jìn)行flush
HBase定期刷新Memstore:默認(rèn)周期為1小時(shí)情龄,確保Memstore不會(huì)長(zhǎng)時(shí)間沒有持久化。為避免所有的MemStore在同一時(shí)間都進(jìn)行flush導(dǎo)致的問(wèn)題捍壤,定期的flush操作有20000左右的隨機(jī)延時(shí)骤视。
手動(dòng)執(zhí)行flush:用戶可以通過(guò)shell命令 flush ‘tablename’或者flush ‘region name’分別對(duì)一個(gè)表或者一個(gè)Region進(jìn)行flush。
HBase客戶端
HBase提供了許多客戶端鹃觉,例如HBase Shell专酗、JAVA API、REST盗扇、Thrift祷肯、Avro等等,另外MapReduce粱玲、Hive躬柬、Spark等分布式計(jì)算引擎均有接口讀寫HBase數(shù)據(jù),詳細(xì)細(xì)節(jié)參考《HBase In Action》或者《HBase權(quán)威指南》抽减。
HBase優(yōu)化
了解了HBase的基本概念之后允青,在使用過(guò)程中針對(duì)每個(gè)環(huán)節(jié)均有優(yōu)化策略,最常見的優(yōu)化策略如下卵沉。
調(diào)整GC策略
HBase內(nèi)存使用情況參考上文颠锉,當(dāng)吞吐量徒增或者運(yùn)行一定時(shí)長(zhǎng)后,大部分內(nèi)存被BlockCache和MemStore占據(jù)史汗,一旦FGC則會(huì)“stop the world”琼掠,影響RS的正常工作,如果GC時(shí)間超過(guò)ZK的心跳TimeOut時(shí)間停撞,該服務(wù)會(huì)被ZK標(biāo)記為BAD狀態(tài)瓷蛙。所以合理高效的GC策略非常重要。
GC基本知識(shí)請(qǐng)參考《深入理解Java虛擬機(jī)》戈毒,針對(duì)HBase這里給出參考JVM參數(shù):
- -Xms16g艰猬、-Xmx16g:內(nèi)存大小,根據(jù)集群情況設(shè)置埋市,建議不要超過(guò)18G冠桃。
- -Xmn1g:年輕代大小。
- -XX:+UseParNewGC:設(shè)置年輕代為并發(fā)回收
- -XX:+UseConcMarkSweepGC:?jiǎn)⒂肅MS算法
- -XX:CMSInitiatingOccupancyFraction=70:年老代使用了70%時(shí)回收內(nèi)存
- -Djava.net.preferIPv4Stack=true:禁用IPv6
- -XX:-CMSConcurrentMTEnabled(-號(hào)代表否認(rèn))道宅,當(dāng)該標(biāo)志被啟用時(shí)食听,并發(fā)的CMS階段將以多線程執(zhí)行(因此胸蛛,多個(gè)GC線程會(huì)與所有的應(yīng)用程序線程并行工作)。該標(biāo)志已經(jīng)默認(rèn)開啟樱报,如果順序執(zhí)行更好葬项,這取決于所使用的硬件,多線程執(zhí)行可以通過(guò)-XX:-CMSConcurremntMTEnabled禁用肃弟。
- -XX:+CMSIncrementalMode. 在增量模式下玷室,CMS 收集器在并發(fā)階段,不會(huì)獨(dú)占整個(gè)周期笤受,而會(huì)周期性的暫停穷缤,喚醒應(yīng)用線程。收集器把并發(fā)階段工作箩兽,劃分為片段津肛,安排在次級(jí)(minor) 回收之間運(yùn)行。
-Xms10g -Xmx10g -Xmn1g -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:MaxGCPauseMillis=60000 -XX:CMSInitiatingOccupancyFraction=70 -XX:PermSize=64m -XX:MaxPermSize=256m -Djava.net.preferIPv4Stack=true -XX:MaxDirectMemorySize=3072m -XX:+CMSIncrementalMode
BucketCache Off-Heap
如果存在批量讀取HBase數(shù)據(jù)的請(qǐng)求汗贫,BlockCache可能會(huì)被迅速占滿身坐,GC不及時(shí)RS又要悲劇,所以采用BucketCache+Off-Heap是一個(gè)不錯(cuò)的優(yōu)化點(diǎn)落包,或者不顧性能問(wèn)題部蛇,關(guān)閉BlockCache。
- BucketCache Off-Heap配置方法:
<property>
<name>hbase.bucketcache.size</name>
<value>3072</value>
</property>
<property>
<name>hbase.bucketcache.ioengine</name>
<value>offheap</value>
</property>
CombinedBlockCache策略下還需要設(shè)置L1緩存大懈烙:
<property>
<name>hbase.bucketcache.combinedcache.enabled</name>
<value>true</value>
</property>
<property>
<name>hfile.block.cache.size</name>
<value>0.2</value>
</property>
- 內(nèi)存簡(jiǎn)易觀察方案:
jmap -heap pid -- 內(nèi)存使用情況
jmap -histo pid -- 內(nèi)存粗略統(tǒng)計(jì)
jmap -F -dump:format=b,file=dump.file pid -- dump文件
jhat dump.file -- 通過(guò)http://dshbase01:7000查看dump文件,但端口白名單導(dǎo)致無(wú)法訪問(wèn)
線程并發(fā)數(shù)
HBase是一個(gè)高并發(fā)的分布式數(shù)據(jù)庫(kù)涯鲁,牽扯到ZK、RegionServer有序、DataNode等組件抹腿,所以性能提升跟這些組件的并發(fā)線程數(shù)的控制離不開關(guān)系。
- RS線程數(shù):
hbase.regionserver.handler.count=300
hbase.regionserver.metahandler.count=300
該配置定義了每個(gè)Region Server上的RPC Handler的數(shù)量旭寿。Region Server通過(guò)RPC Handler接收外部請(qǐng)求并加以處理警绩。所以提升RPC Handler的數(shù)量可以一定程度上提高HBase接收請(qǐng)求的能力。
- ZK線程數(shù):注意線程數(shù)增加帶來(lái)的內(nèi)存消耗盅称,相應(yīng)提升ZK Heap大屑缦椤;
maxClientCnxns=60
- DataNode:注意線程數(shù)增加帶來(lái)的文件描述符的問(wèn)題缩膝,調(diào)整最大文件描述符最大限制搭幻;
dfs.datanode.max.transfer.threads=8192
dfs.datanode.handler.count=100
Balancer
HBase是一種支持自動(dòng)負(fù)載均衡的分布式KV數(shù)據(jù)庫(kù),在開啟balance的開關(guān)(balance_switch)后逞盆,HBase的HMaster進(jìn)程會(huì)自動(dòng)根據(jù) 指定策略 挑選出一些Region,并將這些Region分配給負(fù)載比較低的RegionServer上松申。官方目前支持兩種挑選Region的策略云芦,一種叫做DefaultLoadBalancer俯逾,另一種叫做StochasticLoadBalancer。由于HBase的所有數(shù)據(jù)(包括HLog/Meta/HStoreFile等)都是寫入到HDFS文件系統(tǒng)中的舅逸, 因此HBase的Region移動(dòng)其實(shí)非常輕量級(jí)桌肴。在做Region移動(dòng)的時(shí)候,保持這個(gè)Region對(duì)應(yīng)的HDFS文件位置不變琉历,只需要將Region的Meta數(shù)據(jù)分配到相關(guān)的RegionServer即可坠七,整個(gè)Region移動(dòng)的過(guò)程取決于RegionClose以及RegionOpen的耗時(shí),這個(gè)時(shí)間一般都很短旗笔。
Region切分
Region Split的依據(jù)是最大Region Size彪置,調(diào)大該參數(shù)可以減少Region數(shù)量,同時(shí)也減少了MemStore數(shù)量和總空間蝇恶。
hbase.hregion.max.filesize=2G
非本地化部署
HBase讀取數(shù)據(jù)時(shí)會(huì)從HDFS上的HFile加載數(shù)據(jù)到BlockCache拳魁,如果是本地?cái)?shù)據(jù)則非常高效,如果不同機(jī)器或不同機(jī)架撮弧,則會(huì)帶來(lái)網(wǎng)絡(luò)消耗潘懊,同樣寫流程當(dāng)中的Flush過(guò)程一樣會(huì)本地化的問(wèn)題,所以建議DataNode和RegionServer部署在同一臺(tái)機(jī)器贿衍,因?yàn)镽S調(diào)用hdfs client去讀寫數(shù)據(jù)授舟,hdfs client優(yōu)先會(huì)讀寫本地磁盤。另外也可以通過(guò)HBase Web UI觀察數(shù)據(jù)本地化情況贸辈。
MemStore Flush
MemStore的頻繁Flush會(huì)消耗大量服務(wù)器資源释树,根據(jù)Flush條件合理控制Flush次數(shù)是一個(gè)經(jīng)常要觀察的優(yōu)化點(diǎn)。
- 調(diào)大全局MemStore大腥雇帧:
<property>
<name>hbase.regionserver.global.memstore.size</name>
<value>0.6</value>
</property>
- 調(diào)大HLog個(gè)數(shù)限制:每個(gè)Region均包含一個(gè)HLog躏哩,所以需要根據(jù)Region個(gè)數(shù)來(lái)動(dòng)態(tài)調(diào)整;
hbase.regionserver.maxlogs=500
壓縮
壓縮通常會(huì)帶來(lái)較好的性能揉燃,因?yàn)镃PU壓縮和解壓消耗的時(shí)間比從磁盤中讀取和寫入數(shù)據(jù)消耗的時(shí)間更短扫尺,HBase支持多種壓縮方式,例如snappy炊汤、lzo正驻、gzip等等,不同壓縮算法壓縮比和效率有一定差異抢腐。開啟表壓縮的方式如下:
disable 'MILESTONE'
alter 'MILESTONE',{NAME=>'PUSH',COMPRESSION=>'snappy'}
alter 'MILESTONE',{NAME=>'BEHAVIOR',COMPRESSION=>'snappy'}
enable 'MILESTONE'
MSLAB
HBase內(nèi)存回收策略CMS一定程度上降低了Stop時(shí)間姑曙,但卻避免不了內(nèi)存碎片的問(wèn)題,HBase提供了以下幾個(gè)參數(shù)迈倍,防止堆中產(chǎn)生過(guò)多碎片伤靠,大概思路也就是參考TLAB,叫做MSLAB啼染,MemStore-Local Allocation Buffer宴合。
一個(gè)regionserver的內(nèi)存里各個(gè)region的數(shù)據(jù)混合在一起焕梅,當(dāng)某個(gè)region被flush到磁盤時(shí),就會(huì)形成很多堆碎片卦洽。其實(shí)這跟java中g(shù)c模型的假設(shè)是沖突的贞言,同一時(shí)間創(chuàng)建的對(duì)象,會(huì)在同一時(shí)間消亡阀蒂。這個(gè)問(wèn)題可以通過(guò)Arena Allocation來(lái)解決该窗,即每次分配內(nèi)存時(shí)都是在一個(gè)更大的叫做arena的內(nèi)存區(qū)域分配。一個(gè)典型的實(shí)現(xiàn)是TLAB蚤霞,即每個(gè)線程維護(hù)自己的arena酗失,每個(gè)線程用到的對(duì)象都在自己的arena區(qū)域分配。其實(shí)争便,jvm早已經(jīng)實(shí)現(xiàn)了TLAB级零,但是這個(gè)對(duì)于hbase不適用,因?yàn)閔base的regionserver使用一個(gè)單獨(dú)的線程來(lái)處理所有region的請(qǐng)求滞乙,就算這個(gè)線程用arena方式分配還是會(huì)把所有region的數(shù)據(jù)混在一起奏纪。因此hbase自己實(shí)現(xiàn)了MSLAB,即每個(gè)region的memStore自己實(shí)現(xiàn)了arena斩启,使各個(gè)region的數(shù)據(jù)分開序调,就不會(huì)形成太細(xì)的碎片。Arena里存放的是KeyValue對(duì)象兔簇,如果這些KeyValue對(duì)象是一樣大的发绢,不會(huì)導(dǎo)致嚴(yán)重碎片,相反這些KeyValue對(duì)象引用的字節(jié)數(shù)組才是引起碎片的主因垄琐,因此要做的就是把這些字節(jié)數(shù)組分配在一起边酒。
hbase.hregion.memstore.mslab.enabled=true // 開啟MSALB
hbase.hregion.memstore.mslab.chunksize=2m // chunk的大小,越大內(nèi)存連續(xù)性越好狸窘,但內(nèi)存平均利用率會(huì)降低
hbase.hregion.memstore.mslab.max.allocation=256K // 通過(guò)MSLAB分配的對(duì)象不能超過(guò)256K墩朦,否則直接在Heap上分配,256K夠大了
RowKey設(shè)計(jì)
HBase是根據(jù)Rowkey來(lái)進(jìn)行檢索的翻擒,所以合理的Rowkey設(shè)計(jì)可以提高查詢效率氓涣。
- Rowkey長(zhǎng)度原則:Rowkey是一個(gè)二進(jìn)制碼流,Rowkey的長(zhǎng)度被很多開發(fā)者建議說(shuō)設(shè)計(jì)在10~100個(gè)字節(jié)陋气,不過(guò)建議是越短越好劳吠,不要超過(guò)16個(gè)字節(jié)。
數(shù)據(jù)的持久化文件HFile中是按照KeyValue存儲(chǔ)的巩趁,如果Rowkey過(guò)長(zhǎng)比如100個(gè)字節(jié)痒玩,1000萬(wàn)列數(shù)據(jù)光Rowkey就要占用100*1000萬(wàn)=10億個(gè)字節(jié),將近1G數(shù)據(jù),這會(huì)極大影響HFile的存儲(chǔ)效率蠢古;
MemStore將緩存部分?jǐn)?shù)據(jù)到內(nèi)存燃观,如果Rowkey字段過(guò)長(zhǎng)內(nèi)存的有效利用率會(huì)降低,系統(tǒng)將無(wú)法緩存更多的數(shù)據(jù)便瑟,這會(huì)降低檢索效率。因此Rowkey的字節(jié)長(zhǎng)度越短越好番川。
目前操作系統(tǒng)是都是64位系統(tǒng)到涂,內(nèi)存8字節(jié)對(duì)齊“涠剑控制在16個(gè)字節(jié)践啄,8字節(jié)的整數(shù)倍利用操作系統(tǒng)的最佳特性。
Rowkey散列原則:如果Rowkey是按時(shí)間戳的方式遞增沉御,不要將時(shí)間放在二進(jìn)制碼的前面屿讽,建議將Rowkey的高位作為散列字段,由程序循環(huán)生成吠裆,低位放時(shí)間字段伐谈,這樣將提高數(shù)據(jù)均衡分布在每個(gè)Regionserver實(shí)現(xiàn)負(fù)載均衡的幾率。如果沒有散列字段试疙,首字段直接是時(shí)間信息將產(chǎn)生所有新數(shù)據(jù)都在一個(gè) RegionServer上堆積的熱點(diǎn)現(xiàn)象诵棵,這樣在做數(shù)據(jù)檢索的時(shí)候負(fù)載將會(huì)集中在個(gè)別RegionServer,降低查詢效率祝旷。
Rowkey唯一原則:必須在設(shè)計(jì)上保證其唯一性履澳,這句是廢話。
不同應(yīng)用場(chǎng)景RowKey的設(shè)計(jì)也需要定制化考慮怀跛。
客戶端調(diào)優(yōu)
- Retry:hbase.client.retries.number=20
- AutoFlush:將HTable的setAutoFlush設(shè)為false距贷,可以支持客戶端批量更新。即當(dāng)Put填滿客戶端flush緩存時(shí)吻谋,才發(fā)送到服務(wù)端忠蝗。默認(rèn)是true。
- Scan Caching:scanner一次緩存多少數(shù)據(jù)來(lái)scan(從服務(wù)端一次抓多少數(shù)據(jù)回來(lái)scan)滨溉;默認(rèn)值是 1什湘,一次只取一條。
- Scan Attribute Selection:scan時(shí)建議指定需要的Column Family晦攒,減少通信量闽撤,否則scan操作默認(rèn)會(huì)返回整個(gè)row的所有數(shù)據(jù)(所有Coulmn Family)。
- Close ResultScanners:通過(guò)scan取完數(shù)據(jù)后脯颜,記得要關(guān)閉ResultScanner哟旗,否則RegionServer可能會(huì)出現(xiàn)問(wèn)題(對(duì)應(yīng)的Server資源無(wú)法釋放)。
- Optimal Loading of Row Keys:當(dāng)你scan一張表的時(shí)候,返回結(jié)果只需要row key(不需要CF, qualifier,values,timestaps)時(shí)闸餐,你可以在scan實(shí)例中添加一個(gè)filterList饱亮,并設(shè)置 MUST_PASS_ALL操作,filterList中add FirstKeyOnlyFilter或KeyOnlyFilter舍沙。這樣可以減少網(wǎng)絡(luò)通信量近上。
- 啟用Bloom Filter:Bloom Filter通過(guò)空間換時(shí)間,提高讀操作性能拂铡。
- 批量Get : HBase提供了get(List)批量獲取數(shù)據(jù)的接口壹无,減少RPC交互,提高性能感帅,但注意需要在客戶端關(guān)閉blockCache斗锭,否則容易OOM,也會(huì)沖掉熱點(diǎn)數(shù)據(jù)失球,這樣blockCache則失去了意義岖是。
Hive On HBase
NoSQL種類繁多,各有優(yōu)勢(shì)实苞,HBase其中一個(gè)優(yōu)勢(shì)就是和HDFS的集成豺撑,想象下如果實(shí)時(shí)事務(wù)數(shù)據(jù)需要結(jié)合歷史統(tǒng)計(jì)數(shù)據(jù),則需要將Hive離線跑批的數(shù)據(jù)T+1日導(dǎo)入HBase硬梁,這個(gè)數(shù)據(jù)搬遷的過(guò)程前硫,針對(duì)Redis\MongoDB\cassandra,則需要定制開發(fā)ETL工具荧止。對(duì)于HBase屹电,Hive提供了HBaseStorageHandle解決方案:
-- KV映射
CREATE TABLE helloWorld(key int, value string)
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES ("hbase.columns.mapping" = ":key,cf1:val")
TBLPROPERTIES ("hbase.table.name" = "helloWorld");
-- 列簇映射
CREATE TABLE helloWorld(key int, cf1 map<string,string>, cf2 map<string, string>)
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES ("hbase.columns.mapping" = ":key,cf1:")
TBLPROPERTIES ("hbase.table.name" = "helloWorld");
然后基于HQL插入數(shù)據(jù)即可。
Spark On HBase
某些業(yè)務(wù)場(chǎng)景下面跃巡,需要結(jié)合多個(gè)數(shù)據(jù)源的數(shù)據(jù)危号,比如redis和hbase的數(shù)據(jù)進(jìn)行join。第一個(gè)念頭則是Spark DataFrame或DataSet素邪,DF支持多種數(shù)據(jù)源外莲,同時(shí)還提供了SparkSQL語(yǔ)法用于統(tǒng)計(jì)分析。
- 針對(duì)Redis兔朦,參考https://github.com/RedisLabs/spark-redis 偷线。
// 配置文件
val conf = new SparkConf()
.setAppName(jobName)
.setMaster("local[*]")
.set("redis.host", redisHost)
.set("redis.port", redisPort)
.set("redis.db", redisDB)
.set("redis.timeout", redisTimeOut)
val sc = new SparkContext(conf)
val sqlContext = new SQLContext(sc)
// RDD讀取
var redisRDD = sc.fromRedisSet(groupID)
// 注冊(cè)DF
val redisSchemaString = "USER_ID"
val redisSchema = StructType(redisSchemaString.split(" ").map(fieldName => StructField(fieldName, StringType, true)))
val rowRDD = redisRDD.map(p => Row(p.toString.trim))
var redisDF = sqlContext.createDataFrame(rowRDD, redisSchema)
redisDF.registerTempTable(redisDFTableName)
- 針對(duì)HBase,常見的方案有newAPIHadoopRDD沽甥、nerdammer声邦、hortonworks-spark/shc、unicredit/hbase-rdd摆舟。
- newAPIHadoopRDD:Spark官方提供的HDFS文件讀寫接口亥曹;
// 配置文件
val hconf = HBaseConfiguration.create()
hconf.set("hbase.zookeeper.property.clientPort", zkPort)
hconf.set("hbase.zookeeper.quorum", zkQuorum)
// RDD讀取
hconf.set(TableInputFormat.INPUT_TABLE, tagTableName)
hconf.set(TableInputFormat.SCAN_COLUMNS, hbaseColumns)
val hbaseRDD = sc.newAPIHadoopRDD(hconf, classOf[TableInputFormat],
classOf[org.apache.hadoop.hbase.io.ImmutableBytesWritable],
classOf[org.apache.hadoop.hbase.client.Result])...
// 注冊(cè)DF:注冊(cè)之前需要將HBaseRDD轉(zhuǎn)換成RowRDD,此處省略
val hbaseDF = sqlContext.createDataFrame(hbaseRDD, hbaseStruct)
hbaseDF.registerTempTable(hbaseDFTableName)
- SHC :hortonworks提供的Spark讀寫HBase的方案邓了,具體Demo見Git,SHC只支持spark2.0+版本媳瞪;
// Schema定義
def catalog =
s"""{
|"table":{"namespace":"default", "name":"tag"},
|"rowkey":"key",
|"columns":{
|"col0":{"cf":"rowkey", "col":"key", "type":"string"},
|"col1":{"cf":"behavior", "col":"activate", "type":"string"}}
}""".stripMargin
// 讀取HBase數(shù)據(jù)
def withCatalog(cat: String): DataFrame = {
sqlContext
.read
.options(Map(HBaseTableCatalog.tableCatalog -> cat))
.format("org.apache.spark.sql.execution.datasources.hbase")
.load()
}
// 注冊(cè)DF
val df = withCatalog(catalog)
df.registerTempTable("userBehaviorTime")
- nerdammer : 寫入操作必須是tunpleRDD骗炉,但Scala的tunple最大長(zhǎng)度22,如果需要查詢的HBase列數(shù)超過(guò)22蛇受,則無(wú)法使用該方案句葵;
// nerdammer方案讀取HBase數(shù)據(jù):結(jié)果是tuple,長(zhǎng)度有限制22.
val hbaseRDD = sc.hbaseTable[(Option[String], Option[String], Option[String], Option[String], Option[String],
Option[String], Option[String], Option[String], Option[String], Option[String], Option[String], Option[String],
Option[String])]("tag").select(columnStringArray: _*).inColumnFamily("behavior")
// DF只接收RowRDD
val hbaseRowRDD = hbaseRDD.map(
i => {
var record = new ArrayBuffer[String]()
i.productIterator.foreach { col => {
record += col.asInstanceOf[Option[String]].get
}
}
Row(record.toArray: _*)
})
// RowRDD轉(zhuǎn)成DF
val hbaseDF = sqlContext.createDataFrame(hbaseRowRDD, hbaseSchema.struct)
hbaseDF.registerTempTable("userBehaviorTime")
unicredit太小眾兢仰,坑太多笼呆,不建議使用;
Phoenix : 兩大優(yōu)勢(shì)Sql On HBase旨别,HBase二級(jí)索引實(shí)現(xiàn)。HBase神器汗茄,但無(wú)法關(guān)聯(lián)Redis數(shù)據(jù)和HBase數(shù)據(jù)秸弛。
以上解決方案均是采用傳統(tǒng)的JOIN方式,這樣會(huì)帶來(lái)一個(gè)問(wèn)題就是HBase全表掃描洪碳,性能低下递览。解決思路是先將RedisRDD進(jìn)行repartition操作,針對(duì)每個(gè)partition形成HBase的List[Get]對(duì)象瞳腌,進(jìn)行批量讀取绞铃。但同樣批量讀取需要考慮內(nèi)存激增的情況,所以需要結(jié)合上面的優(yōu)化點(diǎn)使用嫂侍。
Phoenix On HBase
Phoenix是構(gòu)建在HBase上的一個(gè)SQL層儿捧,能讓我們用標(biāo)準(zhǔn)的JDBC API而不是HBase客戶端API來(lái)增刪查改HBase數(shù)據(jù)。相對(duì)原生HBase接口挑宠,Phoenix提供了以下特性:
- JDBC API
- 性能優(yōu)化
- 二級(jí)索引實(shí)現(xiàn)
- SQL引擎
- 事務(wù)
- UDF
- 統(tǒng)計(jì)信息收集
- 分頁(yè)查詢
- 散步表
- 跳躍掃描
- 視圖
- 多租戶
- 動(dòng)態(tài)列
- 大量CSV數(shù)據(jù)加載
- 查詢服務(wù)器
- 追蹤
- 指標(biāo)
有興趣者查閱Phoenix官網(wǎng)菲盾。