日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

HBase Rowkey的散列与预分区设计

發(fā)布時間:2024/8/1 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 HBase Rowkey的散列与预分区设计 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

問題導(dǎo)讀:
1.如何防止熱點?
2.如何預(yù)分區(qū)?
擴展:
為什么會產(chǎn)生熱點存儲?

HBase中,表會被劃分為1…n個Region,被托管在RegionServer中。Region二個重要的屬性:StartKey與EndKey表示這個Region維護的rowKey范圍,當(dāng)我們要讀/寫數(shù)據(jù)時,如果rowKey落在某個start-end key范圍內(nèi),那么就會定位到目標(biāo)region并且讀/寫到相關(guān)的數(shù)據(jù)。簡單地說,有那么一點點類似人群劃分,1-15歲為小朋友,16-39歲為年輕人,40-64為中年人,65歲以上為老年人。(這些數(shù)值都是拍腦袋出來的,只是舉例,非真實),然后某人找隊伍,然后根據(jù)年齡,處于哪個范圍,就找到它所屬的隊伍。 : ( 有點廢話了。。。。
然后,默認地,當(dāng)我們只是通過HBaseAdmin指定TableDescriptor來創(chuàng)建一張表時,只有一個region,正處于混沌時期,start-end key無邊界,可謂海納百川。啥樣的rowKey都可以接受,都往這個region里裝,然而,當(dāng)數(shù)據(jù)越來越多,region的size越來越大時,大到一定的閥值,hbase認為再往這個region里塞數(shù)據(jù)已經(jīng)不合適了,就會找到一個midKey將region一分為二,成為2個region,這個過程稱為分裂(region-split).而midKey則為這二個region的臨界,左為N無下界,右為M無上界。< midKey則為陰被塞到N區(qū),> midKey則會被塞到M區(qū)。

如何找到midKey?涉及的內(nèi)容比較多,暫且不去討論,最簡單的可以認為是region的總行數(shù) / 2的那一行數(shù)據(jù)的rowKey.雖然實際上比它會稍復(fù)雜點。
如果我們就這樣默認地,建表,表里不斷地Put數(shù)據(jù),更嚴(yán)重的是我們的rowkey還是順序增大的,是比較可怕的。存在的缺點比較明顯。
首先是熱點寫,我們總是會往最大的start-key所在的region寫東西,因為我們的rowkey總是會比之前的大,并且hbase的是按升序方式排序的。所以寫操作總是被定位到無上界的那個region中。
其次,由于寫熱點,我們總是往最大start-key的region寫記錄,之前分裂出來的region不會再被寫數(shù)據(jù),有點被打進冷宮的趕腳,它們都處于半滿狀態(tài),這樣的分布也是不利的。
如果在寫比較頻率的場景下,數(shù)據(jù)增長快,split的次數(shù)也會增多,由于split是比較耗時耗資源的,所以我們并不希望這種事情經(jīng)常發(fā)生。
看到這些缺點,我們知道,在集群的環(huán)境中,為了得到更好的并行性,我們希望有好的load blance,讓每個節(jié)點提供的請求處理都是均等的。我們也希望,region不要經(jīng)常split,因為split會使server有一段時間的停頓,如何能做到呢?
隨機散列與預(yù)分區(qū)。二者結(jié)合起來,是比較完美的,預(yù)分區(qū)一開始就預(yù)建好了一部分region,這些region都維護著自已的start-end keys,再配合上隨機散列,寫數(shù)據(jù)能均等地命中這些預(yù)建的region,就能解決上面的那些缺點,大大地提高了性能。

提供2種思路: hash 與 partition.
hash就是rowkey前面由一串隨機字符串組成,隨機字符串生成方式可以由SHA或者MD5等方式生成,只要region所管理的start-end keys范圍比較隨機,那么就可以解決寫熱點問題。

long currentId = 1L; byte [] rowkey = Bytes.add(MD5Hash.getMD5AsHex(Bytes.toBytes(currentId)).substring(0, 8).getBytes(),Bytes.toBytes(currentId));

假設(shè)rowKey原本是自增長的long型,可以將rowkey轉(zhuǎn)為hash再轉(zhuǎn)為bytes,加上本身id 轉(zhuǎn)為bytes,組成rowkey,這樣就生成隨便的rowkey。那么對于這種方式的rowkey設(shè)計,如何去進行預(yù)分區(qū)呢?
1.取樣,先隨機生成一定數(shù)量的rowkey,將取樣數(shù)據(jù)按升序排序放到一個集合里
2.根據(jù)預(yù)分區(qū)的region個數(shù),對整個集合平均分割,即是相關(guān)的splitKeys.
3.HBaseAdmin.createTable(HTableDescriptor tableDescriptor,byte[][] splitkeys)可以指定預(yù)分區(qū)的splitKey,即是指定region間的rowkey臨界值.

1.創(chuàng)建split計算器,用于從抽樣數(shù)據(jù)中生成一個比較合適的splitKeys

public class HashChoreWoker implements SplitKeysCalculator{//隨機取機數(shù)目private int baseRecord;//rowkey生成器private RowKeyGenerator rkGen;//取樣時,由取樣數(shù)目及region數(shù)相除所得的數(shù)量.private int splitKeysBase;//splitkeys個數(shù)private int splitKeysNumber;//由抽樣計算出來的splitkeys結(jié)果private byte[][] splitKeys;public HashChoreWoker(int baseRecord, int prepareRegions) {this.baseRecord = baseRecord;//實例化rowkey生成器rkGen = new HashRowKeyGenerator();splitKeysNumber = prepareRegions - 1;splitKeysBase = baseRecord / prepareRegions;}public byte[][] calcSplitKeys() {splitKeys = new byte[splitKeysNumber][];//使用treeset保存抽樣數(shù)據(jù),已排序過TreeSet<byte[]> rows = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR);for (int i = 0; i < baseRecord; i++) {rows.add(rkGen.nextId());}int pointer = 0;Iterator<byte[]> rowKeyIter = rows.iterator();int index = 0;while (rowKeyIter.hasNext()) {byte[] tempRow = rowKeyIter.next();rowKeyIter.remove();if ((pointer != 0) && (pointer % splitKeysBase == 0)) {if (index < splitKeysNumber) {splitKeys[index] = tempRow;index ++;}}pointer ++;}rows.clear();rows = null;return splitKeys;} } KeyGenerator及實現(xiàn) //interface public interface RowKeyGenerator {byte [] nextId(); } //implements public class HashRowKeyGenerator implements RowKeyGenerator {private long currentId = 1;private long currentTime = System.currentTimeMillis();private Random random = new Random();public byte[] nextId() {try {currentTime += random.nextInt(1000);byte[] lowT = Bytes.copy(Bytes.toBytes(currentTime), 4, 4);byte[] lowU = Bytes.copy(Bytes.toBytes(currentId), 4, 4);return Bytes.add(MD5Hash.getMD5AsHex(Bytes.add(lowU, lowT)).substring(0, 8).getBytes(),Bytes.toBytes(currentId));} finally {currentId++;}} } @Test public void testHashAndCreateTable() throws Exception{HashChoreWoker worker = new HashChoreWoker(1000000,10);byte [][] splitKeys = worker.calcSplitKeys();HBaseAdmin admin = new HBaseAdmin(HBaseConfiguration.create());TableName tableName = TableName.valueOf("hash_split_table");if (admin.tableExists(tableName)) {try {admin.disableTable(tableName);} catch (Exception e) {}admin.deleteTable(tableName);}HTableDescriptor tableDesc = new HTableDescriptor(tableName);HColumnDescriptor columnDesc = new HColumnDescriptor(Bytes.toBytes("info"));columnDesc.setMaxVersions(1);tableDesc.addFamily(columnDesc);admin.createTable(tableDesc ,splitKeys);admin.close();}

查看建表結(jié)果:執(zhí)行 scan ‘hbase:meta’

以上我們只是顯示了部分region的信息,可以看到region的start-end key 還是比較隨機散列的。同樣可以查看hdfs的目錄結(jié)構(gòu),的確和預(yù)期的38個預(yù)分區(qū)一致:


以上,就已經(jīng)按hash方式,預(yù)建好了分區(qū),以后在插入數(shù)據(jù)的時候,也要按照此rowkeyGenerator的方式生成rowkey,有興趣的話,也可以做些試驗,插入些數(shù)據(jù),看看數(shù)據(jù)的分布。

partition故名思義,就是分區(qū)式,這種分區(qū)有點類似于mapreduce中的partitioner,將區(qū)域用長整數(shù)(Long)作為分區(qū)號,每個region管理著相應(yīng)的區(qū)域數(shù)據(jù),在rowKey生成時,將id取模后,然后拼上id整體作為rowKey.這個比較簡單,不需要取樣,splitKeys也非常簡單,直接是分區(qū)號即可。直接上代碼吧:

public class PartitionRowKeyManager implements RowKeyGenerator,SplitKeysCalculator {public static final int DEFAULT_PARTITION_AMOUNT = 20;private long currentId = 1;private int partition = DEFAULT_PARTITION_AMOUNT;public void setPartition(int partition) {this.partition = partition;}public byte[] nextId() {try {long partitionId = currentId % partition;return Bytes.add(Bytes.toBytes(partitionId),Bytes.toBytes(currentId));} finally {currentId++;}}public byte[][] calcSplitKeys() {byte[][] splitKeys = new byte[partition - 1][];for(int i = 1; i < partition ; i ++) {splitKeys[i-1] = Bytes.toBytes((long)i);}return splitKeys;} }

calcSplitKeys方法比較單純,splitKey就是partition的編號,我們看看測試類:

@Testpublic void testPartitionAndCreateTable() throws Exception{PartitionRowKeyManager rkManager = new PartitionRowKeyManager();//只預(yù)建10個分區(qū)rkManager.setPartition(10);byte [][] splitKeys = rkManager.calcSplitKeys();HBaseAdmin admin = new HBaseAdmin(HBaseConfiguration.create());TableName tableName = TableName.valueOf("partition_split_table");if (admin.tableExists(tableName)) {try {admin.disableTable(tableName);} catch (Exception e) {}admin.deleteTable(tableName);}HTableDescriptor tableDesc = new HTableDescriptor(tableName);HColumnDescriptor columnDesc = new HColumnDescriptor(Bytes.toBytes("info"));columnDesc.setMaxVersions(1);tableDesc.addFamily(columnDesc);admin.createTable(tableDesc ,splitKeys);admin.close();}

同樣我們可以看看meta表和hdfs的目錄結(jié)果,其實和hash類似,region都會分好區(qū),在這里就不上圖了。

通過partition實現(xiàn)的loadblance寫的話,當(dāng)然生成rowkey方式也要結(jié)合當(dāng)前的region數(shù)目取模而求得,大家同樣也可以做些實驗,看看數(shù)據(jù)插入后的分布。
在這里也順提一下,如果是順序的增長型原id,可以將id保存到一個數(shù)據(jù)庫,傳統(tǒng)的也好,redis的也好,每次取的時候,將數(shù)值設(shè)大1000左右,以后id可以在內(nèi)存內(nèi)增長,當(dāng)內(nèi)存數(shù)量已經(jīng)超過1000的話,再去load下一個,有點類似于oracle中的sqeuence.

隨機分布加預(yù)分區(qū)也不是一勞永逸的。因為數(shù)據(jù)是不斷地增長的,隨著時間不斷地推移,已經(jīng)分好的區(qū)域,或許已經(jīng)裝不住更多的數(shù)據(jù),當(dāng)然就要進一步進行split了,同樣也會出現(xiàn)性能損耗問題,所以我們還是要規(guī)劃好數(shù)據(jù)增長速率,觀察好數(shù)據(jù)定期維護,按需分析是否要進一步分行手工將分區(qū)再分好,也或者是更嚴(yán)重的是新建表,做好更大的預(yù)分區(qū)然后進行數(shù)據(jù)遷移。小吳只是菜鳥,運維方面也只是自已這樣認為而已,供大家作簡單的參考吧。如果數(shù)據(jù)裝不住了,對于partition方式預(yù)分區(qū)的話,如果讓它自然分裂的話,情況分嚴(yán)重一點。因為分裂出來的分區(qū)號會是一樣的,所以計算到partitionId的話,其實還是回到了順序?qū)懩甏?#xff0c;會有部分熱點寫問題出現(xiàn),如果使用partition方式生成主鍵的話,數(shù)據(jù)增長后就要不斷地調(diào)整分區(qū)了,比如增多預(yù)分區(qū),或者加入子分區(qū)號的處理.(我們的分區(qū)號為long型,可以將它作為多級partition)

OK,寫到這里,基本已經(jīng)講完了防止熱點寫使用的方法和防止頻繁split而采取的預(yù)分區(qū)。但rowkey設(shè)計,遠遠也不止這些,比如rowkey長度,然后它的長度最大可以為char的MAXVALUE,但是看過之前我寫KeyValue的分析知道,我們的數(shù)據(jù)都是以KeyValue方式存儲在MemStore或者HFile中的,每個KeyValue都會存儲rowKey的信息,如果rowkey太大的話,比如是128個字節(jié),一行10個字段的表,100萬行記錄,光rowkey就占了1.2G+所以長度還是不要過長,另外設(shè)計,還是按需求來吧。

最后題外話是我想分享我在github中建了一個project,希望做一些hbase一些工具:https://github.com/bdifn/hbase-tools,如果本地裝了git的話,可以執(zhí)行命令: git clone https://github.com/bdifn/hbase-tools.git目前加了一個region-helper子項目,也是目前唯一的一個子項目,項目使用maven管理,主要目的是幫助我們設(shè)計rowkey做一些參考,比如我們設(shè)計的隨機寫和預(yù)分區(qū)測試,提供了抽樣的功能,提供了檢測隨機寫的功能,然后統(tǒng)計按目前rowkey設(shè)計,隨機寫n條記錄后,統(tǒng)計每個region的記錄數(shù),然后顯示比例等。
測試仿真模塊我程為simualtor,主要是模擬hbase的region行為,simple的實現(xiàn),僅僅是上面提到的預(yù)測我們rowkey設(shè)計后,建好預(yù)分區(qū)后,寫數(shù)據(jù)的的分布比例,而emulation是比較逼真的仿真,設(shè)想是我們寫數(shù)據(jù)時,會統(tǒng)計數(shù)目的大小,根據(jù)我們的hbase-site.xml設(shè)定,模擬memStore行為,模擬hfile的行為,最終會生成一份表的報表,比如分區(qū)的數(shù)據(jù)大小,是否split了,等等,以供我們?nèi)ピO(shè)計hbase表時有一個參考,但是遺憾的是,由于時間關(guān)系,我只花了一點業(yè)余時間簡單搭了一下框架,目前沒有更一步的實現(xiàn),以后有時間再加以完善,當(dāng)然也歡迎大家一起加入,一起學(xué)習(xí)吧。

項目使用maven管理,為了方便測試,一些組件的實例化,我使用了java的SPI,download源碼后,如果想測試自已的rowKeyGeneator的話,打開com.bdifn.hbasetools.regionhelper.rowkey.RowKeyGenerator文件后,替換到你們的ID生成器就可以了。如果是hash的話,抽樣和測試等,都是可以復(fù)用的。

如測試代碼:

public class HBaseSimulatorTest {//通過SPI方式獲取HBaseSimulator實例,SPI的實現(xiàn)為simgpleprivate HBaseSimulator hbase = BeanFactory.getInstance().getBeanInstance(HBaseSimulator.class);//獲取RowKeyGenerator實例,SPI的實現(xiàn)為hashRowkeyprivate RowKeyGenerator rkGen = BeanFactory.getInstance().getBeanInstance(RowKeyGenerator.class);//初如化苦工,去檢測100w個抽樣rowkey,然后生成一組splitKeysHashChoreWoker worker = new HashChoreWoker(1000000,10);@Testpublic void testHash(){byte [][] splitKeys = worker.calcSplitKeys();hbase.createTable("user", splitKeys);//插入1億條記錄,看數(shù)據(jù)分布TableName tableName = TableName.valueOf("user");for(int i = 0; i < 100000000; i ++) {Put put = new Put(rkGen.nextId());hbase.put(tableName, put);}hbase.report(tableName);}@Testpublic void testPartition(){//default 20 partitions.PartitionRowKeyManager rkManager = new PartitionRowKeyManager();byte [][] splitKeys = rkManager.calcSplitKeys();hbase.createTable("person", splitKeys);TableName tableName = TableName.valueOf("person");//插入1億條記錄,看數(shù)據(jù)分布for(int i = 0; i < 100000000; i ++) {Put put = new Put(rkManager.nextId());hbase.put(tableName, put);}hbase.report(tableName);} }

執(zhí)行結(jié)果:

Execution Reprort:[StartRowkey:puts requsts:(put ratio)] :9973569:(1.0015434) 1986344a\x00\x00\x00\x00\x00\x01\x0E\xAE:9999295:(1.0041268) 331ee65f\x00\x00\x00\x00\x00\x0F)g:10012532:(1.005456) 4cbfd4f6\x00\x00\x00\x00\x00\x00o0:9975842:(1.0017716) 664c6388\x00\x00\x00\x00\x00\x02\x1Du:10053337:(1.0095537) 800945e0\x00\x00\x00\x00\x00\x01\xADV:9998719:(1.0040689) 99a158d9\x00\x00\x00\x00\x00\x0BZ\xF3:10000563:(1.0042541) b33a2223\x00\x00\x00\x00\x00\x07\xC6\xE6:9964921:(1.000675) ccbcf370\x00\x00\x00\x00\x00\x00*\xE2:9958200:(1.0) e63b8334\x00\x00\x00\x00\x00\x03g\xC1:10063022:(1.0105262) total requests:100000000 Execution Reprort:[StartRowkey:puts requsts:(put ratio)] :5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x01:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x02:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x03:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x04:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x05:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x06:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x07:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x08:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x09:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x0A:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x0B:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x0C:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x0D:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x0E:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x0F:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x10:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x11:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x12:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x13:5000000:(1.0) total requests:100000000

原貼地址:http://www.cnblogs.com/bdifn/p/3801737.html ,轉(zhuǎn)載請注明

總結(jié)

以上是生活随笔為你收集整理的HBase Rowkey的散列与预分区设计的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。