一、Rowkey設(shè)計(jì)
1、Rowkey長度原則
Rowkey是一個二進(jìn)制碼流,Rowkey的長度建議設(shè)計(jì)在10-100個字節(jié),最好不要超過16個字節(jié)。
原因有:
- 數(shù)據(jù)的持久化文件HFile中是按照KeyValue存儲的,如果Rowkey過長,比如100個字節(jié),1000萬列數(shù)據(jù)光Rowkey就要占用100*1000萬=10億個字節(jié),將近1G數(shù)據(jù),這會極大影響HFile的存儲效率。
- MemStore將緩存部分?jǐn)?shù)據(jù)到內(nèi)存,如果Rowkey字段過長內(nèi)存的有效利用率會降低,系統(tǒng)將無法緩存更多的數(shù)據(jù),這會降低檢索效率。因此Rowkey的字節(jié)長度越短越好。
- 目前操作系統(tǒng)都是64位系統(tǒng),內(nèi)存8字節(jié)對齊。控制在16個字節(jié),8字節(jié)的整數(shù)倍利用操作系統(tǒng)的最佳特性。
2、Rowkey散列原則
如果Rowkey是按照時間戳的方式遞增,不要將時間放在二進(jìn)制碼的前面,建議將RowKey的高位作為散列字段,由程序循環(huán)生成,低位放時間字段,這樣將提高數(shù)據(jù)均衡分布在每個RegionServer實(shí)現(xiàn)負(fù)載均衡的幾率。如果沒有散列字段,首字段直接是時間信息將產(chǎn)生所有新數(shù)據(jù)都在一個RegionServer上堆積的熱點(diǎn)現(xiàn)象,這樣在做數(shù)據(jù)檢索的時候負(fù)載將會集中在個別RegionServer,降低查詢效率。
3、Rowkey唯一原則
必須在設(shè)計(jì)上保證Rowkey的唯一性。
二、Region優(yōu)化
1、預(yù)先分區(qū)
默認(rèn)情況下,在創(chuàng)建HBase表的時候會自動創(chuàng)建一個Region分區(qū),當(dāng)導(dǎo)入數(shù)據(jù)的時候,所有的HBase客戶端都向這一個Region寫數(shù)據(jù),直到這個Region足夠大了才進(jìn)行切分。一種可以加快批量寫入速度的方法是通過預(yù)先創(chuàng)建一些空的Regions,這樣當(dāng)數(shù)據(jù)寫入HBase時,會按照Region分區(qū)情況,在集群內(nèi)做數(shù)據(jù)的負(fù)載均衡。
2、HFile大小設(shè)計(jì)
- Region過大會發(fā)生多次compaction,將數(shù)據(jù)讀一遍并重寫一遍到HDFS上,占用磁盤IO;
- Region過小會造成多次split,Region 會下線,影響訪問服務(wù)。
最佳的解決方法是調(diào)整hbase.hregion. max.filesize 為256m。
3、RegionServer資源配置
1)磁盤資源
每臺 RegionServer 管理 10~1000 個 Regions,每個 Region 在 1~2G,則每臺 Server 最少要 10G,最大要1000*2G=2TB,考慮 3 備份,則要 6TB。方案一是用 3 塊 2TB 硬盤,二是用 12 塊 500G 硬盤,帶寬足夠時,后者能提供更大的吞吐率,更細(xì)粒度的冗余備份,更快速的單盤故障恢復(fù)。
2)內(nèi)存資源
在不影響其他服務(wù)的情況下,越大越好。例如在 HBase 的 conf 目錄下的 hbase-env.sh 的最后添加 export HBASE_REGIONSERVER_OPTS="-Xmx16000m$HBASE_REGIONSERVER_OPTS”
其中 16000m 為分配給 RegionServer 的內(nèi)存大小。
3)RegionServer的請求處理I/O線程數(shù)
較少的 IO 線程適用于處理單次請求內(nèi)存消耗較高的 Big Put 場景 (大容量單次 Put 或設(shè)置了較大 cache 的Scan,均屬于 Big Put) 或 ReigonServer 的內(nèi)存比較緊張的場景。
較多的 IO 線程,適用于單次請求內(nèi)存消耗低,TPS 要求 (每秒事務(wù)處理量 (TransactionPerSecond)) 非常高的場景。設(shè)置該值的時候,以監(jiān)控內(nèi)存為主要參考。
在 hbase-site.xml 配置文件中配置項(xiàng)為 hbase.regionserver.handler.count。
三、數(shù)據(jù)熱點(diǎn)問題
1、出現(xiàn)數(shù)據(jù)熱點(diǎn)問題原因
出現(xiàn)數(shù)據(jù)熱點(diǎn)問題的原因有:
- HBase中的數(shù)據(jù)是按照字典順序排序的,當(dāng)大量連續(xù)的Rowkey集中寫在個別的region,各個region之間數(shù)據(jù)分布不均衡;
- 創(chuàng)建表時沒有提前預(yù)分區(qū)。創(chuàng)建的表默認(rèn)只有一個region,大量的數(shù)據(jù)寫入當(dāng)前region;
- 創(chuàng)建表已經(jīng)以前預(yù)分區(qū),但是設(shè)計(jì)的Rowkey沒有規(guī)律可循,設(shè)計(jì)的Rowkey應(yīng)該由regionNo + messageId組成。
2、如何解決熱點(diǎn)問題
解決數(shù)據(jù)熱點(diǎn)問題目前有兩種方案:Hash和Partition。
1)hash方案
hash就是rowkey前面由一串隨機(jī)字符串組成,隨機(jī)字符串生成方式可以由SHA或者M(jìn)D5方式生成,只要region所管理的start-end keys范圍比較隨機(jī),那么就可以解決數(shù)據(jù)熱點(diǎn)問題。
2)partition方案
partition顧名思義就是分區(qū)式,這種分區(qū)有點(diǎn)類似于MapReduce中的Partitioner,將區(qū)域用長整數(shù)作為分區(qū)號,每個region管理著相應(yīng)的區(qū)域數(shù)據(jù),在rowkey生成時,將ID取模后,然后拼上ID整體作為rowkey,這個比較簡單,必須要取樣,splitkeys也非常簡單,直接是分區(qū)號即可。
四、數(shù)據(jù)讀取
1、客戶端一次從服務(wù)器拉取的數(shù)量
通過配置一次拉去的較大的數(shù)據(jù)量可以減少客戶端獲取數(shù)據(jù)的時間,但是它會占用客戶端內(nèi)存。有三個地方可進(jìn)行配置:
- 在 HBase 的 conf 配置文件中進(jìn)行配置 hbase.client.scanner.caching;
- 通過調(diào)用HTable.setScannerCaching(intscannerCaching) 進(jìn)行配置;
- 通過調(diào)用Scan.setCaching(intcaching) 進(jìn)行配置。三者的優(yōu)先級越來越高。
五、數(shù)據(jù)寫入
1、批量寫
HBase 的 Put 支持單條插入,也支持批量插入,一般來說批量寫更快,節(jié)省來回的網(wǎng)絡(luò)開銷。在客戶端調(diào)用JavaAPI 時,先將批量的 Put 放入一個 Put 列表,然后調(diào)用 HTable 的 Put(Put 列表) 函數(shù)來批量寫。
2、緩沖區(qū)大小
hbase.client.write.buffer
這個參數(shù)可以設(shè)置寫入數(shù)據(jù)緩沖區(qū)的大小,當(dāng)客戶端和服務(wù)器端傳輸數(shù)據(jù),服務(wù)器為了提高系統(tǒng)運(yùn)行性能開辟一個寫的緩沖區(qū)來處理它,這個參數(shù)設(shè)置如果設(shè)置的大了,將會對系統(tǒng)的內(nèi)存有一定的要求,直接影響系統(tǒng)的性能。
3、寫數(shù)據(jù)備份
備份數(shù)與讀性能成正比,與寫性能成反比,且備份數(shù)影響高可用性。有兩種配置方式,一種是將 hdfs-site.xml拷貝到 hbase 的 conf 目錄下,然后在其中添加或修改配置項(xiàng) dfs.replication 的值為要設(shè)置的備份數(shù),這種修改對所有的 HBase 用戶表都生效,另外一種方式,是改寫 HBase 代碼,讓 HBase 支持針對列族設(shè)置備份數(shù),在創(chuàng)建表時,設(shè)置列族備份數(shù),默認(rèn)為 3,此種備份數(shù)只對設(shè)置的列族生效。
4、WAL(預(yù)寫日志)
可設(shè)置開關(guān),表示 HBase 在寫數(shù)據(jù)前用不用先寫日志,默認(rèn)是打開,關(guān)掉會提高性能,但是如果系統(tǒng)出現(xiàn)故障(負(fù)責(zé)插入的 RegionServer 掛掉),數(shù)據(jù)可能會丟失。配置 WAL 在調(diào)用 JavaAPI 寫入時,設(shè)置 Put 實(shí)例的WAL,調(diào)用 Put.setWriteToWAL(boolean)。