预拆分表通常是一种比较好的实践。如果预先拆分表,则必须了解rowkey将如何分布在region边界上。是否所有的region都有rowkey能够访问。
举一个为什么这一点很重要的示例,考虑使用可显示十六进制字符作为键的前导位置的示例(例如,“0000000000000000”到“ffffffffffffffff”)。
创建预分区表的代码:
String startKeyString="0000000000000000";
String endKeyString="ffffffffffffffff";
int numRegions=10;
admin.createTable(tableDescriptor,startKeyString.getBytes(),endKeyString.getBytes(),numRegions);
//创建表,与下面的方法等价,生成一样region分布的表
int splitTimes=numRegions-3;//因为有两个空数组作为rowkey的开始和结束rowkey
byte[][] splitKeys=Bytes.split(startKeyString.getBytes(),endKeyString.getBytes(),splitTimes);
admin.createTable(tableDescriptor,splitKeys);
执行代码后,table的region分布如下:
当我们使用十六进制的字符作为rowkey前缀的时候,就会发生热点分区的问题。记录都会集中在如下四个region,因为十六进制字符rowkey的值区间为[0-9]和[a-f]。
做一些验证,向表中插入数据
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);
发现如下region接收到一次请求,即"a"与各个region的start key首字母与end key比较,"a"会落在"_"与"f"之间,所以此条记录会落在次rowkey区间的region上。
另外需要注意的一点,rowkey字符串的每个字节的ascii码会与startkey、endkey的每一个字节的ascii码比较。
/** 因为最后一个字符"e"的ascii码比"f"小,所以仍然落在split key "ffffffffffffffff"之前的region分区 */
byte[] rowKey=Bytes.toBytes("fffffffffffffffe");
/** 因为没有第三个字符与split key比较,所以"ff"小于"ffffffffffffffff" */
byte[] rowKey=Bytes.toBytes("ff");
上面两个rowkey的记录仍然会落在如下分区
错误分区代码总结:
- 预拆分表通常是一种最佳实践。但您需要让预拆分表满足rokwey可以访问任意的分区。虽然这个例子演示了十六进制键空间的问题,但任何键空间都可能发生同样的问题。需要了解你的数据,要知道rowkey的展示格式,知道各个region的start key与end key的展示格式。
- 错误的分区代码尽管不适合,但只要加以改进,将所有创建的region都可以在rowkey键空间中访问,那么使用十六进制键(更常见的目的是,可显示rowkey数据)仍然可以作为预拆分表的方案。
下面是如何为十六进制格式的rowkey进行预分区的正确示例:
public static byte[][] getHexSplits(String startKey, String endKey, int numRegions) {
byte[][] splits = new byte[numRegions-1][];
//将16进制表示的字符串startKey转换为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);
}
运行代码,创建好的region所有的startKey、endKey都为16进制的字符串表示。
ASCII表: