hbase熱點問題(數(shù)據(jù)傾斜)解決方案---rowkey散列和預(yù)分區(qū)設(shè)計

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ù)。

? ? 默認情況下虐块,當(dāng)我們通過hbaseAdmin指定TableDescriptor來創(chuàng)建一張表時俩滥,只有一個region正處于混沌時期,start-end key無邊界贺奠,可謂海納百川霜旧。所有的rowkey都寫入到這個region里,然后數(shù)據(jù)越來越多儡率,region的size越來越大時挂据,大到一定的閥值,hbase就會將region一分為二儿普,成為2個region崎逃,這個過程稱為分裂(region-split)。

? ? 如果我們就這樣默認建表眉孩,表里不斷的put數(shù)據(jù)个绍,更嚴重的是我們的rowkey還是順序增大的,是比較可怕的浪汪。存在的缺點比較明顯:首先是熱點寫巴柿,我們總是向最大的start key所在的region寫數(shù)據(jù),因為我們的rowkey總是會比之前的大死遭,并且hbase的是按升序方式排序的广恢。所以寫操作總是被定位到無上界的那個region中;其次呀潭,由于熱點钉迷,我們總是往最大的start key的region寫記錄,之前分裂出來的region不會被寫數(shù)據(jù)蜗侈,有點打入冷宮的感覺篷牌,他們都處于半滿狀態(tài),這樣的分布也是不利的踏幻。

? ? 如果在寫比較頻繁的場景下,數(shù)據(jù)增長太快戳杀,split的次數(shù)也會增多该面,由于split是比較耗費資源的夭苗,所以我們并不希望這種事情經(jīng)常發(fā)生。

? ? 在集群中為了得到更好的并行性隔缀,我們希望有好的load blance题造,讓每個節(jié)點提供的請求都是均衡的,我們也不希望猾瘸,region不要經(jīng)常split界赔,因為split會使server有一段時間的停頓,如何能做到呢牵触?

隨機散列與預(yù)分區(qū)二者結(jié)合起來淮悼,是比較完美的。預(yù)分區(qū)一開始就預(yù)建好了一部分region揽思,這些region都維護著自己的start-end keys袜腥,在配合上隨機散列,寫數(shù)據(jù)能均衡的命中這些預(yù)建的region钉汗,就能解決上面的那些缺點羹令,大大提供性能。

一损痰、解決思路

? ? 提供兩種思路:hash與partition福侈。

1、hash方案

? ? hash就是rowkey前面由一串隨機字符串組成卢未,隨機字符串生成方式可以由SHA或者MD5方式生成肪凛,只要region所管理的start-end keys范圍比較隨機,那么就可以解決寫熱點問題尝丐。

散列函數(shù)是固定的显拜,讀取的時候,拿著未散列的數(shù)據(jù)爹袁,求出散列的數(shù)據(jù)远荠,即可實現(xiàn)查詢。

例如:


Java代碼

long?currentId?=?1L;??

byte?[]?rowkey?=?Bytes.add(MD5Hash.getMD5AsHex(Bytes.toBytes(currentId))??

????????????????????.substring(0,?8).getBytes(),Bytes.toBytes(currentId));??

? ? ?假如rowkey原本是自增長的long型失息,可以將rowkey轉(zhuǎn)為hash再轉(zhuǎn)為bytes譬淳,加上本身id轉(zhuǎn)為bytes,這樣就生成隨便的rowkey盹兢。那么對于這種方式的rowkey設(shè)計邻梆,如何去進行預(yù)分區(qū)呢?

取樣绎秒,先隨機生成一定數(shù)量的rowkey浦妄,將取樣數(shù)據(jù)按升序排序放到一個集合里。

根據(jù)預(yù)分區(qū)的region個數(shù),對整個集合平均分割剂娄,即是相關(guān)的splitkeys蠢涝。

HBaseAdmin.createTable(HTableDescriptor tableDescriptor,byte[][] splitkeys)可以指定預(yù)分區(qū)的splitkey,即指定region間的rowkey臨界值阅懦。

? ? 創(chuàng)建split計算器和二,用于從抽樣數(shù)據(jù)生成一個比較合適的splitkeys

Java代碼

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)

Java代碼

//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++;??

????????}??

????}??

}??

? ? ?unit test case測試

Java代碼

@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'

以上就是按照hash方式,預(yù)建好分區(qū)怕午,以后再插入數(shù)據(jù)的時候废登,也是按照此rowkeyGenerator的方式生成rowkey。

2诗轻、partition的方式

? ? partition顧名思義就是分區(qū)式钳宪,這種分區(qū)有點類似于mapreduce中的partitioner,將區(qū)域用長整數(shù)作為分區(qū)號扳炬,每個region管理著相應(yīng)的區(qū)域數(shù)據(jù)吏颖,在rowkey生成時,將ID取模后恨樟,然后拼上ID整體作為rowkey半醉,這個比較簡單,不需要取樣劝术,splitkeys也非常簡單缩多,直接是分區(qū)號即可。直接上代碼:

Java代碼

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的編號衬吆,測試類如下:

Java代碼

@Test

????public?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();??

????}??

? ?通過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ù)分區(qū)然后進行數(shù)據(jù)遷移色徘。如果數(shù)據(jù)裝不住了,對于partition方式預(yù)分區(qū)的話操禀,如果讓它自然分裂的話褂策,情況分嚴重一點。因為分裂出來的分區(qū)號會是一樣的颓屑,所以計算到partitionId的話斤寂,其實還是回到了順序?qū)懩甏瑫胁糠譄狳c寫問題出現(xiàn)揪惦,如果使用partition方式生成主鍵的話遍搞,數(shù)據(jù)增長后就要不斷地調(diào)整分區(qū)了,比如增多預(yù)分區(qū)器腋,或者加入子分區(qū)號的處理.(我們的分區(qū)號為long型溪猿,可以將它作為多級partition)

? ? 以上基本已經(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è)計,還是按需求來吧鱼炒。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末衔沼,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌指蚁,老刑警劉巖菩佑,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異凝化,居然都是意外死亡稍坯,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門搓劫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瞧哟,“玉大人,你說我怎么就攤上這事枪向∏诳” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵秘蛔,是天一觀的道長陨亡。 經(jīng)常有香客問我,道長深员,這世上最難降的妖魔是什么负蠕? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮辨液,結(jié)果婚禮上虐急,老公的妹妹穿的比我還像新娘。我一直安慰自己滔迈,他們只是感情好止吁,可當(dāng)我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著燎悍,像睡著了一般敬惦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上谈山,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天俄删,我揣著相機與錄音,去河邊找鬼奏路。 笑死畴椰,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鸽粉。 我是一名探鬼主播斜脂,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼触机!你這毒婦竟也來了帚戳?” 一聲冷哼從身側(cè)響起玷或,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎片任,沒想到半個月后偏友,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡对供,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年位他,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片犁钟。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡棱诱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出涝动,到底是詐尸還是另有隱情,我是刑警寧澤炬灭,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布醋粟,位于F島的核電站,受9級特大地震影響重归,放射性物質(zhì)發(fā)生泄漏米愿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一鼻吮、第九天 我趴在偏房一處隱蔽的房頂上張望育苟。 院中可真熱鬧,春花似錦椎木、人聲如沸违柏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽漱竖。三九已至,卻和暖如春畜伐,著一層夾襖步出監(jiān)牢的瞬間馍惹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工玛界, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留万矾,地道東北人。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓慎框,卻偏偏與公主長得像良狈,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鲤脏,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內(nèi)容