預拆分表通常是一種比較好的實踐。如果預先拆分表,則必須了解rowkey將如何分布在region邊界上。是否所有的region都有rowkey能夠訪問。
舉一個為什么這一點很重要的示例,考慮使用可顯示十六進制字符作為鍵的前導位置的示例(例如,“0000000000000000”到“ffffffffffffffff”)。
創(chuàng)建預分區(qū)表的代碼:
String startKeyString="0000000000000000";
String endKeyString="ffffffffffffffff";
int numRegions=10;
admin.createTable(tableDescriptor,startKeyString.getBytes(),endKeyString.getBytes(),numRegions);
//創(chuàng)建表,與下面的方法等價,生成一樣region分布的表
int splitTimes=numRegions-3;//因為有兩個空數(shù)組作為rowkey的開始和結(jié)束rowkey
byte[][] splitKeys=Bytes.split(startKeyString.getBytes(),endKeyString.getBytes(),splitTimes);
admin.createTable(tableDescriptor,splitKeys);
執(zhí)行代碼后,table的region分布如下:

image.png
當我們使用十六進制的字符作為rowkey前綴的時候,就會發(fā)生熱點分區(qū)的問題。記錄都會集中在如下四個region,因為十六進制字符rowkey的值區(qū)間為[0-9]和[a-f]。

image.png
做一些驗證,向表中插入數(shù)據(jù)
byte[] rowKey=Bytes.toBytes("a");
System.out.println(new String(rowKey)+"_"+rowKey.length);
Put put = new Put(rowKey);
put.addColumn(Bytes.toBytes(familyName), Bytes.toBytes(columnName), rowKey);
table.put(put);
發(fā)現(xiàn)如下region接收到一次請求,即"a"與各個region的start key首字母與end key比較,"a"會落在"_"與"f"之間,所以此條記錄會落在次rowkey區(qū)間的region上。

image.png
另外需要注意的一點,rowkey字符串的每個字節(jié)的ascii碼會與startkey、endkey的每一個字節(jié)的ascii碼比較。
/** 因為最后一個字符"e"的ascii碼比"f"小,所以仍然落在split key "ffffffffffffffff"之前的region分區(qū) */
byte[] rowKey=Bytes.toBytes("fffffffffffffffe");
/** 因為沒有第三個字符與split key比較,所以"ff"小于"ffffffffffffffff" */
byte[] rowKey=Bytes.toBytes("ff");
上面兩個rowkey的記錄仍然會落在如下分區(qū)

image.png
錯誤分區(qū)代碼總結(jié):
- 預拆分表通常是一種最佳實踐。但您需要讓預拆分表滿足rokwey可以訪問任意的分區(qū)。雖然這個例子演示了十六進制鍵空間的問題,但任何鍵空間都可能發(fā)生同樣的問題。需要了解你的數(shù)據(jù),要知道rowkey的展示格式,知道各個region的start key與end key的展示格式。
- 錯誤的分區(qū)代碼盡管不適合,但只要加以改進,將所有創(chuàng)建的region都可以在rowkey鍵空間中訪問,那么使用十六進制鍵(更常見的目的是,可顯示rowkey數(shù)據(jù))仍然可以作為預拆分表的方案。
下面是如何為十六進制格式的rowkey進行預分區(qū)的正確示例:
public static byte[][] getHexSplits(String startKey, String endKey, int numRegions) {
byte[][] splits = new byte[numRegions-1][];
//將16進制表示的字符串startKey轉(zhuǎn)換為10進制的BigInteger類型
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;
}
public void createTable3() throws IOException {
String startKeyString="0000000000000000";
String endKeyString="ffffffffffffffff";
byte[][] splitKeys= getHexSplits(startKeyString,endKeyString,10);
admin.createTable(tableDescriptor,splitKeys);
}
運行代碼,創(chuàng)建好的region所有的startKey、endKey都為16進制的字符串表示。

image.png
ASCII表:

image.png