W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
本節(jié)介紹了 HBase 中的行鍵(Rowkey)設(shè)計(jì)。
HBase 中的行按行鍵按順序排序。這種設(shè)計(jì)優(yōu)化了掃描(scan),允許您將相關(guān)的行或彼此靠近的行一起讀取。但是,設(shè)計(jì)不佳的行鍵是 hotspotting 的常見來源。當(dāng)大量客戶端通信針對(duì)群集中的一個(gè)節(jié)點(diǎn)或僅少數(shù)幾個(gè)節(jié)點(diǎn)時(shí),會(huì)發(fā)生 Hotspotting。此通信量可能表示讀取、寫入或其他操作。通信量壓倒負(fù)責(zé)托管該區(qū)域的單個(gè)機(jī)器,從而導(dǎo)致性能下降并可能導(dǎo)致區(qū)域不可用性。這也會(huì)對(duì)由同一臺(tái)區(qū)域服務(wù)器托管的其他區(qū)域產(chǎn)生不利影響,因?yàn)樵撝鳈C(jī)無法為請(qǐng)求的負(fù)載提供服務(wù)。設(shè)計(jì)數(shù)據(jù)訪問模式以使群集得到充分和均勻利用非常重要。
為了防止 hotspotting 寫入,請(qǐng)?jiān)O(shè)計(jì)行鍵,使真正需要在同一個(gè)區(qū)域中的行成為行,但是從更大的角度來看,數(shù)據(jù)將被寫入整個(gè)群集中的多個(gè)區(qū)域,而不是一次。以下描述了避免 hotspotting 的一些常用技術(shù),以及它們的一些優(yōu)點(diǎn)和缺點(diǎn)。
從這個(gè)意義上說,Salting 與密碼學(xué)無關(guān),而是指將隨機(jī)數(shù)據(jù)添加到行鍵的開頭。在這種情況下,salting 是指為行鍵添加一個(gè)隨機(jī)分配的前綴,以使它的排序方式與其他方式不同。可能的前綴數(shù)量對(duì)應(yīng)于要傳播數(shù)據(jù)的區(qū)域數(shù)量。如果你有一些“hotspotting”行鍵模式,反復(fù)出現(xiàn)在其他更均勻分布的行中,那么 Salting 可能會(huì)有幫助。請(qǐng)考慮以下示例,該示例顯示 salting 可以跨多個(gè) RegionServer 傳播寫入負(fù)載,并說明讀取的一些負(fù)面影響。
使用實(shí)例
假設(shè)您有以下的行鍵列表,并且您的表格被拆分,以便字母表中的每個(gè)字母都有一個(gè)區(qū)域。前綴'a'是一個(gè)區(qū)域,前綴'b'是另一個(gè)區(qū)域。在此表中,所有以'f'開頭的行都在同一個(gè)區(qū)域中。本示例重點(diǎn)關(guān)注具有以下鍵的行:
foo0001
foo0002
foo0003
foo0004
現(xiàn)在,想象你想要在四個(gè)不同的地區(qū)傳播這些信息。您決定使用四個(gè)不同的 Salting:a,b,c 和 d。在這種情況下,每個(gè)這些字母前綴將位于不同的區(qū)域。應(yīng)用 Salting 后,您可以使用以下 rowkeys。由于您現(xiàn)在可以寫入四個(gè)不同的區(qū)域,因此理論上寫入時(shí)的吞吐量是吞吐量的四倍,如果所有寫入操作都在同一個(gè)區(qū)域,則會(huì)有這樣的吞吐量。
A-foo0003
B-foo0001
C-foo0004
d-foo0002
然后,如果添加另一行,它將隨機(jī)分配四種可能的 Salting 值中的一種,并最終靠近現(xiàn)有的一行。
A-foo0003
B-foo0001
C-foo0003
C-foo0004
d-foo0002
由于這個(gè)任務(wù)是隨機(jī)的,如果你想按字典順序檢索行,你需要做更多的工作。以這種方式,Salting 試圖增加寫入吞吐量,但在讀取期間會(huì)產(chǎn)生成本。
除了隨機(jī)分配之外,您可以使用單向 Hashing,這會(huì)導(dǎo)致給定的行總是被相同的前綴“salted”,其方式會(huì)跨 RegionServer 傳播負(fù)載,但允許在讀取期間進(jìn)行預(yù)測(cè)。使用確定性 Hashing 允許客戶端重建完整的 rowkey 并使用 Get 操作正常檢索該行。
Hashing 示例
考慮到上述 salting 示例中的相同情況,您可以改為應(yīng)用單向 Hashing,這會(huì)導(dǎo)致帶有鍵的行 foo0003 始終處于可預(yù)見的狀態(tài)并接收 a 前綴。
然后,為了檢索該行,您已經(jīng)知道了密鑰。
例如,您也可以優(yōu)化事物,以便某些鍵對(duì)總是在相同的區(qū)域中。
反轉(zhuǎn)關(guān)鍵
防止熱點(diǎn)的第三種常用技巧是反轉(zhuǎn)固定寬度或數(shù)字行鍵,以便最經(jīng)常(最低有效位數(shù))改變的部分在第一位。這有效地使行鍵隨機(jī)化,但犧牲了行排序?qū)傩浴?/p>
在 Tom White 的書“Hadoop: The Definitive Guide”(O'Reilly)的一章中,有一個(gè)優(yōu)化筆記,關(guān)注一個(gè)現(xiàn)象,即導(dǎo)入過程與所有客戶一起敲擊表中的一個(gè)區(qū)域(并且因此是單個(gè)節(jié)點(diǎn)),然后移動(dòng)到下一個(gè)區(qū)域等等。隨著單調(diào)遞增的行鍵(即,使用時(shí)間戳),這將發(fā)生。通過將輸入記錄隨機(jī)化為不按排序順序排列,可以緩解由單調(diào)遞增密鑰帶來的單個(gè)區(qū)域上的堆積,但通常最好避免使用時(shí)間戳或序列(例如1,2,3)作為行鍵。
如果您確實(shí)需要將時(shí)間序列數(shù)據(jù)上傳到 HBase 中,則應(yīng)將 OpenTSDB 作為一個(gè)成功的示例進(jìn)行研究。它有一個(gè)描述它在 HBase 中使用的模式的頁面。OpenTSDB 中的關(guān)鍵格式實(shí)際上是 [metric_type] [event_timestamp],它會(huì)在第一眼看起來與之前關(guān)于不使用時(shí)間戳作為關(guān)鍵的建議相矛盾。但是,區(qū)別在于時(shí)間戳不在密鑰的主導(dǎo)位置,并且設(shè)計(jì)假設(shè)是有幾十個(gè)或幾百個(gè)(或更多)不同的度量標(biāo)準(zhǔn)類型。因此,即使連續(xù)輸入數(shù)據(jù)和多種度量類型,Puts也會(huì)分布在表中不同的地區(qū)。
在 HBase 中,值總是隨著坐標(biāo)而運(yùn)行;當(dāng)單元格值通過系統(tǒng)時(shí),它將始終伴隨其行,列名稱和時(shí)間戳。如果你的行和列的名字很大,特別是與單元格的大小相比,那么你可能會(huì)遇到一些有趣的場(chǎng)景。其中之一就是 Marc Limotte 在 HBASE-3551 尾部描述的情況。其中,保存在 HBase商店文件(
StoreFile(HFile))以方便隨機(jī)訪問可能最終占用 HBase 分配的 RAM 的大塊,因?yàn)閱卧底鴺?biāo)很大。上面引用的注釋中的標(biāo)記建議增加塊大小,以便存儲(chǔ)文件索引中的條目以更大的間隔發(fā)生,或者修改表模式,以便使用較小的行和列名稱。壓縮也會(huì)使更大的指數(shù)。在用戶郵件列表中查看線程問題 storefileIndexSize。
大多數(shù)時(shí)候,小的低效率并不重要。不幸的是,這是他們的情況。無論為 ColumnFamilies,屬性和 rowkeys 選擇哪種模式,都可以在數(shù)據(jù)中重復(fù)數(shù)十億次。
盡量保持 ColumnFamily 名稱盡可能小,最好是一個(gè)字符(例如,"d" 用于 data 或者 default)。
雖然詳細(xì)的屬性名稱(例如,“myVeryImportantAttribute”)更易于閱讀,但更喜歡使用較短的屬性名稱(例如,“via”)來存儲(chǔ)在 HBase 中。
保持它們盡可能短,這樣它們?nèi)匀豢梢杂糜谒璧臄?shù)據(jù)訪問(例如,Get 和 Scan)。對(duì)數(shù)據(jù)訪問無用的短密鑰并不比具有更好的 get/scan 屬性的更長(zhǎng)密鑰更好。在設(shè)計(jì)行鍵時(shí)需要權(quán)衡。
長(zhǎng)為8個(gè)字節(jié)。您可以在這八個(gè)字節(jié)中存儲(chǔ)最多18,446,744,073,709,551,615的未簽名數(shù)字。如果您將此數(shù)字作為字符串存儲(chǔ) - 假定每個(gè)字符有一個(gè)字節(jié) - 則需要接近3倍的字節(jié)。
以下是您可以自行運(yùn)行的一些示例代碼:
// long
//
long l = 1234567890L;
byte[] lb = Bytes.toBytes(l);
System.out.println("long bytes length: " + lb.length); // returns 8
String s = String.valueOf(l);
byte[] sb = Bytes.toBytes(s);
System.out.println("long as string length: " + sb.length); // returns 10
// hash
//
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(Bytes.toBytes(s));
System.out.println("md5 digest bytes length: " + digest.length); // returns 16
String sDigest = new String(digest);
byte[] sbDigest = Bytes.toBytes(sDigest);
System.out.println("md5 digest as string length: " + sbDigest.length); // returns 26
不幸的是,使用類型的二進(jìn)制表示會(huì)使您的數(shù)據(jù)難以在代碼之外讀取。例如,這是您在增加值時(shí)在 shell 中將看到的內(nèi)容:
hbase(main):001:0> incr 't', 'r', 'f:q', 1
COUNTER VALUE = 1
hbase(main):002:0> get 't', 'r'
COLUMN CELL
f:q timestamp=1369163040570, value=\x00\x00\x00\x00\x00\x00\x00\x01
1 row(s) in 0.0310 seconds
shell 會(huì)盡最大努力打印一個(gè)字符串,并且它決定只打印十六進(jìn)制。區(qū)域名稱內(nèi)的行鍵也會(huì)發(fā)生同樣的情況。如果您知道存儲(chǔ)的內(nèi)容可能沒問題,但如果可以將任意數(shù)據(jù)放入同一個(gè)單元格中,它可能也是不可讀的。這是主要的權(quán)衡。
HBASE-4811 實(shí)現(xiàn)一個(gè) API,以反向掃描表中的表或區(qū)域,從而減少了為正向或反向掃描優(yōu)化模式的需要。此功能在 HBase 0.98 和更高版本中可用。
數(shù)據(jù)庫處理中的一個(gè)常見問題是快速找到最新版本的值。使用反向時(shí)間戳作為密鑰的一部分的技術(shù)可以幫助解決這個(gè)問題的一個(gè)特例。在 Tom White 的書籍“Hadoop:The Definitive Guide(O'Reilly)”的 HBase 章節(jié)中也有介紹,該技術(shù)包括附加 Long.MAX_VALUE - timestamp 到任何密鑰的末尾(例如,[key][reverse_timestamp])。
通過執(zhí)行 Scan [key] 并獲取第一條記錄,可以找到表格中 [key] 的最新值。由于 HBase 密鑰的排序順序不同,因此該密鑰在 [key] 的任何較舊的行鍵之前排序,因此是第一個(gè)。
這種技術(shù)將被用來代替使用版本號(hào),其意圖是永久保存所有版本(或者很長(zhǎng)時(shí)間),同時(shí)通過使用相同的掃描技術(shù)來快速獲得對(duì)任何其他版本的訪問。
行鍵的范圍為 ColumnFamilies。因此,相同的 rowkey 可以存在于沒有碰撞的表中存在的每個(gè) ColumnFamily 中。
行鍵無法更改。他們可以在表格中“更改”的唯一方法是該行被刪除然后重新插入。這是 HBase dist-list 上的一個(gè)相當(dāng)常見的問題,所以在第一次(或在插入大量數(shù)據(jù)之前)獲得 rowkeys 是值得的。
如果您預(yù)先拆分表格,了解您的 rowkey 如何在區(qū)域邊界上分布是非常重要的。作為重要的一個(gè)例子,考慮使用可顯示的十六進(jìn)制字符作為鍵的前導(dǎo)位置(例如,“0000000000000000” 到 “ffffffffffffffff”)的示例。通過這些關(guān)鍵范圍 Bytes.split(這是在 Admin.createTable(byte[] startKey, byte[] endKey, numRegions) 為10個(gè)區(qū)域創(chuàng)建區(qū)域時(shí)使用的分割策略)將生成以下分割:
48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 // 0
54 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 // 6
61 = 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 // D
75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 72 // K
82 18 18 18 18 18 18 18 18 18 18 18 18 18 18 14 // R
88 -40 -40 -40 -40 -40 -40 -40 -40 -40 -40 -40 -40 -40 -40 -44 // X
95 -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
(注意:前導(dǎo)字節(jié)作為注釋列在右側(cè)。)鑒于第一個(gè)分割是'0'而最后一個(gè)分割是'f',一切都很好,但是還沒有結(jié)束。
其中的問題是,所有的數(shù)據(jù)都會(huì)堆積在前兩個(gè)區(qū)域和最后一個(gè)區(qū)域,從而產(chǎn)生一個(gè)“塊狀(lumpy)”(也可能是“hot”)區(qū)域問題。'0'是字節(jié)48,'f'是字節(jié)102,但字節(jié)值(字節(jié)58到96)之間存在巨大的差距,永遠(yuǎn)不會(huì)出現(xiàn)在這個(gè)密鑰空間中,因?yàn)槲ㄒ坏闹凳?[0-9] 和 [af]。因此,中間地區(qū)將永遠(yuǎn)不會(huì)被使用。要使用此示例鍵空間進(jìn)行預(yù)分割工作,需要分割的自定義定義(即,不依賴于內(nèi)置拆分方法)。
第1課:預(yù)分割表通常是最佳做法,但您需要預(yù)先拆分它們,以便可以在密鑰空間中訪問所有區(qū)域。雖然此示例演示了十六進(jìn)制密鑰空間的問題,但任何密鑰空間都會(huì)出現(xiàn)同樣的問題。了解你的數(shù)據(jù)。
第2課:盡管通常不可取,但只要所有創(chuàng)建的區(qū)域都可在密鑰空間中訪問,則使用十六進(jìn)制鍵(更一般而言,可顯示的數(shù)據(jù))仍可用于預(yù)分割表。
為了總結(jié)這個(gè)例子,以下是如何為十六進(jìn)制密鑰預(yù)先創(chuàng)建恰當(dāng)?shù)姆指畹睦樱?/p>
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");
// the table 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;
}
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: