Rowkey设计_HBase表设计
在HBase中我們都知道rowkey的設(shè)計(jì)尤為重要,其設(shè)計(jì)原則分為三種:
- 長(zhǎng)度原則
- 散列原則
- 唯一原則
那么下面對(duì)rowkey的設(shè)計(jì)做一個(gè)詳細(xì)的闡述:?
HBase的rowkey設(shè)計(jì)可以說(shuō)是使用HBase最為重要的事情,直接影響到HBase的性能,常見(jiàn)的RowKey的設(shè)計(jì)問(wèn)題及對(duì)應(yīng)訪問(wèn)為:
?
Hotspotting
的行由行鍵按字典順序排序,這樣的設(shè)計(jì)優(yōu)化了掃描,允許存儲(chǔ)相關(guān)的行或者那些將被一起讀的鄰近的行。然而,設(shè)計(jì)不好的行鍵是導(dǎo)致 hotspotting 的常見(jiàn)原因。當(dāng)大量的客戶端流量( traffic )被定向在集群上的一個(gè)或幾個(gè)節(jié)點(diǎn)時(shí),就會(huì)發(fā)生 hotspotting。這些流量可能代表著讀、寫或其他操作。流量超過(guò)了承載該region的單個(gè)機(jī)器所能負(fù)荷的量,這就會(huì)導(dǎo)致性能下降并有可能造成region的不可用。在同一 RegionServer 上的其他region也可能會(huì)受到其不良影響,因?yàn)橹鳈C(jī)無(wú)法提供服務(wù)所請(qǐng)求的負(fù)載。設(shè)計(jì)使集群能被充分均勻地使用的數(shù)據(jù)訪問(wèn)模式是至關(guān)重要的。
為了防止在寫操作時(shí)出現(xiàn) hotspotting ,設(shè)計(jì)行鍵時(shí)應(yīng)該使得數(shù)據(jù)盡量同時(shí)往多個(gè)region上寫,而避免只向一個(gè)region寫,除非那些行真的有必要寫在一個(gè)region里。
下面介紹了集中常用的避免 hotspotting 的技巧,它們各有優(yōu)劣:
Salting
Salting 從某種程度上看與加密無(wú)關(guān),它指的是將隨機(jī)數(shù)放在行鍵的起始處。進(jìn)一步說(shuō),salting給每一行鍵隨機(jī)指定了一個(gè)前綴來(lái)讓它與其他行鍵有著不同的排序。所有可能前綴的數(shù)量對(duì)應(yīng)于要分散數(shù)據(jù)的region的數(shù)量。如果有幾個(gè)“hot”的行鍵模式,而這些模式在其他更均勻分布的行里反復(fù)出現(xiàn),salting就能到幫助。下面的例子說(shuō)明了salting能在多個(gè)RegionServer間分散負(fù)載,同時(shí)也說(shuō)明了它在讀操作時(shí)候的負(fù)面影響。
假設(shè)行鍵的列表如下,表按照每個(gè)字母對(duì)應(yīng)一個(gè)region來(lái)分割。前綴‘a(chǎn)’是一個(gè)region,‘b’就是另一個(gè)region。在這張表中,所有以‘f’開頭的行都屬于同一個(gè)region。這個(gè)例子關(guān)注的行和鍵如下:
foo0001foo0002foo0003foo0004現(xiàn)在,假設(shè)想將它們分散到不同的region上,就需要用到四種不同的 salts :a,b,c,d。在這種情況下,每種字母前綴都對(duì)應(yīng)著不同的一個(gè)region。用上這些salts后,便有了下面這樣的行鍵。由于現(xiàn)在想把它們分到四個(gè)獨(dú)立的區(qū)域,理論上吞吐量會(huì)是之前寫到同一region的情況的吞吐量的四倍。?
a-foo0003b-foo0001c-foo0004d-foo0002如果想新增一行,新增的一行會(huì)被隨機(jī)指定四個(gè)可能的salt值中的一個(gè),并放在某條已存在的行的旁邊。
a-foo0003b-foo0001c-foo0003c-foo0004d-foo0002由于前綴的指派是隨機(jī)的,因而如果想要按照字典順序找到這些行,則需要做更多的工作。從這個(gè)角度上看,salting增加了寫操作的吞吐量,卻也增大了讀操作的開銷。
Hashing
可用一個(gè)單向的 hash 散列來(lái)取代隨機(jī)指派前綴。這樣能使一個(gè)給定的行在“salted”時(shí)有相同的前綴,從某種程度上說(shuō),這在分散了RegionServer間的負(fù)載的同時(shí),也允許在讀操作時(shí)能夠預(yù)測(cè)。確定性hash( deterministic hash )能讓客戶端重建完整的行鍵,以及像正常的一樣用Get操作重新獲得想要的行。
考慮和上述salting一樣的情景,現(xiàn)在可以用單向hash來(lái)得到行鍵foo0003,并可預(yù)測(cè)得‘a(chǎn)’這個(gè)前綴。然后為了重新獲得這一行,需要先知道它的鍵??梢赃M(jìn)一步優(yōu)化這一方法,如使得將特定的鍵對(duì)總是在相同的region。
Reversing the Key(反轉(zhuǎn)鍵)
第三種預(yù)防hotspotting的方法是反轉(zhuǎn)一段固定長(zhǎng)度或者可數(shù)的鍵,來(lái)讓最常改變的部分(最低顯著位, the least significant digit )在第一位,這樣有效地打亂了行鍵,但是卻犧牲了行排序的屬性
單調(diào)遞增行鍵/時(shí)序數(shù)據(jù)
在一個(gè)集群中,一個(gè)導(dǎo)入數(shù)據(jù)的進(jìn)程鎖住不動(dòng),所有的client都在等待一個(gè)region(因而也就是一個(gè)單個(gè)節(jié)點(diǎn)),過(guò)了一會(huì)后,變成了下一個(gè)region… 如果使用了單調(diào)遞增或者時(shí)序的key便會(huì)造成這樣的問(wèn)題。使用了順序的key會(huì)將本沒(méi)有順序的數(shù)據(jù)變得有順序,把負(fù)載壓在一臺(tái)機(jī)器上。所以要盡量避免時(shí)間戳或者序列(e.g. 1, 2, 3)這樣的行鍵。
如果需要導(dǎo)入時(shí)間順序的文件(如log)到HBase中,可以學(xué)習(xí)OpenTSDB的做法。它有一個(gè)頁(yè)面來(lái)描述它的HBase模式。OpenTSDB的Key的格式是[metric_type][event_timestamp],乍一看,這似乎違背了不能將timestamp做key的建議,但是它并沒(méi)有將timestamp作為key的一個(gè)關(guān)鍵位置,有成百上千的metric_type就足夠?qū)毫Ψ稚⒌礁鱾€(gè)region了。因此,盡管有著連續(xù)的數(shù)據(jù)輸入流,Put操作依舊能被分散在表中的各個(gè)region中
簡(jiǎn)化行和列
在HBase中,值是作為一個(gè)單元(Cell)保存在系統(tǒng)的中的,要定位一個(gè)單元,需要行,列名和時(shí)間戳。通常情況下,如果行和列的名字要是太大(甚至比value的大小還要大)的話,可能會(huì)遇到一些有趣的情況。在HBase的存儲(chǔ)文件( storefiles )中,有一個(gè)索引用來(lái)方便值的隨機(jī)訪問(wèn),但是訪問(wèn)一個(gè)單元的坐標(biāo)要是太大的話,會(huì)占用很大的內(nèi)存,這個(gè)索引會(huì)被用盡。要想解決這個(gè)問(wèn)題,可以設(shè)置一個(gè)更大的塊大小,也可以使用更小的行和列名 。壓縮也能得到更大指數(shù)。
大部分時(shí)候,細(xì)微的低效不會(huì)影響很大。但不幸的是,在這里卻不能忽略。無(wú)論是列族、屬性和行鍵都會(huì)在數(shù)據(jù)中重復(fù)上億次。
列族
盡量使列族名小,最好一個(gè)字符。(如:f 表示)
屬性
詳細(xì)屬性名 (如:”myVeryImportantAttribute”) 易讀,最好還是用短屬性名 (e.g., “via”) 保存到HBase.
行鍵長(zhǎng)度
?讓行鍵短到可讀即可,這樣對(duì)獲取數(shù)據(jù)有幫助(e.g., Get vs. Scan)。短鍵對(duì)訪問(wèn)數(shù)據(jù)無(wú)用,并不比長(zhǎng)鍵對(duì)get/scan更好。設(shè)計(jì)行鍵需要權(quán)衡
字節(jié)模式
long類型有8字節(jié)。8字節(jié)內(nèi)可以保存無(wú)符號(hào)數(shù)字到18,446,744,073,709,551,615。如果用字符串保存——假設(shè)一個(gè)字節(jié)一個(gè)字符——需要將近3倍的字節(jié)數(shù)。
下面是示例代碼,可以自己運(yùn)行一下:???????
long?l?=?1234567890L;byte[] lb = Bytes.toBytes(l);System.out.println("long?bytes?length:?"?+?lb.length); String s = String.valueOf(l);byte[] sb = Bytes.toBytes(s);System.out.println("long?as?string?length:?"?+?sb.length); MessageDigest md = MessageDigest.getInstance("MD5");byte[] digest = md.digest(Bytes.toBytes(s));System.out.println("md5 digest bytes length: " + digest.length);String sDigest = new String(digest);byte[] sbDigest = Bytes.toBytes(sDigest);System.out.println("md5?digest?as?string?length:?"?+?sbDigest.length);不幸的是,用二進(jìn)制表示會(huì)使數(shù)據(jù)在代碼之外難以閱讀。下例便是當(dāng)需要增加一個(gè)值時(shí)會(huì)看到的shell:???????
hbase(main):001:0>?incr?'t',?'r',?'f:q',?1COUNTER?VALUE?=?1 hbase(main):002:0> get 't', 'r'COLUMN CELL f:q timestamp=1369163040570, value=\x00\x00\x00\x00\x00\x00\x00\x011?row(s)?in?0.0310?seconds?
這個(gè)shell盡力在打印一個(gè)字符串,但在這種情況下,它決定只將進(jìn)制打印出來(lái)。當(dāng)在region名內(nèi)行鍵會(huì)發(fā)生相同的情況。如果知道儲(chǔ)存的是什么,那自是沒(méi)問(wèn)題,但當(dāng)任意數(shù)據(jù)都可能被放到相同單元的時(shí)候,這將會(huì)變得難以閱讀。這是最需要權(quán)衡之處。
?
倒序時(shí)間戳
一個(gè)數(shù)據(jù)庫(kù)處理的通常問(wèn)題是找到最近版本的值。采用倒序時(shí)間戳作為鍵的一部分可以對(duì)此特定情況有很大幫助。該技術(shù)包含追加( Long.MAX_VALUE - timestamp ) 到key的后面,如 [key][reverse_timestamp] 。
表內(nèi)[key]的最近的值可以用[key]進(jìn)行Scan,找到并獲取第一個(gè)記錄。由于HBase行鍵是排序的,該鍵排在任何比它老的行鍵的前面,所以是第一個(gè)。
該技術(shù)可以用于代替版本數(shù),其目的是保存所有版本到“永遠(yuǎn)”(或一段很長(zhǎng)時(shí)間) 。同時(shí),采用同樣的Scan技術(shù),可以很快獲取其他版本。
行鍵和列族
行鍵在列族范圍內(nèi)。所以同樣的行鍵可以在同一個(gè)表的每個(gè)列族中存在而不會(huì)沖突。
行鍵不可改
行鍵不能改變。唯一可以“改變”的方式是刪除然后再插入。這是一個(gè)常問(wèn)問(wèn)題,所以要注意開始就要讓行鍵正確(且/或在插入很多數(shù)據(jù)之前)。
?
行鍵和region split的關(guān)系
如果已經(jīng) pre-split (預(yù)裂)了表,接下來(lái)關(guān)鍵要了解行鍵是如何在region邊界分布的。為了說(shuō)明為什么這很重要,可考慮用可顯示的16位字符作為鍵的關(guān)鍵位置(e.g., “0000000000000000” to “ffffffffffffffff”)這個(gè)例子。通過(guò) Bytes.split來(lái)分割鍵的范圍(這是當(dāng)用 Admin.createTable(byte[] startKey, byte[] endKey, numRegions) 創(chuàng)建region時(shí)的一種拆分手段),這樣會(huì)分得10個(gè)region。???????
48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 // 054 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 // 661 -67 -67 -67 -67 -67 -67 -67 -67 -67 -67 -67 -67 -67 -67 -68 // =68 -124 -124 -124 -124 -124 -124 -124 -124 -124 -124 -124 -124 -124 -124 -126 // D75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 72 // K82 18 18 18 18 18 18 18 18 18 18 18 18 18 18 14 // R88 -40 -40 -40 -40 -40 -40 -40 -40 -40 -40 -40 -40 -40 -40 -44 // X95 -97 -97 -97 -97 -97 -97 -97 -97 -97 -97 -97 -97 -97 -97 -102 // _102 102 102 102 102 102 102 102 102 102 102 102 102 102 102 102 // f但問(wèn)題在于,數(shù)據(jù)將會(huì)堆放在前兩個(gè)region以及最后一個(gè)region,這樣就會(huì)導(dǎo)致某幾個(gè)region由于數(shù)據(jù)分布不均勻而特別忙。為了理解其中緣由,需要考慮ASCII Table的結(jié)構(gòu)。根據(jù)ASCII表,“0”是第48號(hào),“f”是102號(hào);但58到96號(hào)是個(gè)巨大的間隙,考慮到在這里僅[0-9]和[a-f]這些值是有意義的,因而這個(gè)區(qū)間里的值不會(huì)出現(xiàn)在鍵空間( keyspace ),進(jìn)而中間區(qū)域的region將永遠(yuǎn)不會(huì)用到。為了pre-split這個(gè)例子中的鍵空間,需要自定義拆分。
教程1:預(yù)裂表( pre-splitting tables ) 是個(gè)很好的實(shí)踐,但pre-split時(shí)要注意使得所有的region都能在鍵空間中找到對(duì)應(yīng)。盡管例子中解決的問(wèn)題是關(guān)于16位鍵的鍵空間,但其他任何空間也是同樣的道理。
教程2:16位鍵(通常用到可顯示的數(shù)據(jù)中)盡管通常不可取,但只要所有的region都能在鍵空間找到對(duì)應(yīng),它依舊能和預(yù)裂表配合使用。
一下case說(shuō)明了如何16位鍵預(yù)分區(qū)
public?static?boolean?createTable(Admin?admin,?HTableDescriptor?table,?byte[][]?splits)throws IOException { try { admin.createTable( table, splits ); return true; } catch (TableExistsException e) { logger.info("table " + table.getNameAsString() + " already exists"); return false; }} public static byte[][] getHexSplits(String startKey, String endKey, int numRegions) { byte[][] splits = new byte[numRegions-1][]; BigInteger lowestKey = new BigInteger(startKey, 16); BigInteger highestKey = new BigInteger(endKey, 16); BigInteger range = highestKey.subtract(lowestKey); BigInteger regionIncrement = range.divide(BigInteger.valueOf(numRegions)); lowestKey = lowestKey.add(regionIncrement); for(int i=0; i < numRegions-1;i++) { BigInteger key = lowestKey.add(regionIncrement.multiply(BigInteger.valueOf(i))); byte[] b = String.format("%016x", key).getBytes(); splits[i] = b; } return splits;}摘自:https://help.aliyun.com/document_detail/59035.html?spm=a2c4g.11186623.6.773.3c3e7bb29cWzA0?
總結(jié)
以上是生活随笔為你收集整理的Rowkey设计_HBase表设计的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 大剑无锋之什么是值传递和引用传递?【面试
- 下一篇: 大剑无锋之你了解HTTPS吗?那么它为什