1.hbase热点问题是什么?
数据倾斜到一个节点,其余节点空转
2.出现haase热点的原因是什么?
出现haase热点情况一定是这两个原因:
- rowkey设计太low
- 没有做预分区
3.设计hbase经验
rowkey设计原则:唯一,如果不唯一数据就会被覆盖,2散列的,防止出现hbase热点问题,3,字典
rowkey长度设计2的整数倍16,32,64,最长不超过64位.只有这样才可以使用高速缓存,如果不是这些,不能被64整除,就只能使用主机内存,那是比较慢的(最快是寄存器,其次是高速缓存,再往后是内存,磁盘)
列族只设计1个,如果数据量特别大,也就做2个列族,不要在多了.列族最好使用一个字母表示"M",最多使用两个字母"MM"定义其名称
4.为什么row可以设计要使用字典序列去排序的?
为了便利scan操作
5.HBase创建表实现预分区
1.命令行创建HBase的demo01表:
create 'demo01',{NAME=>'M'},SPLITS=>['0','1','2','3'] //列族为一个字母,最大不要超过两个,要使用splits指定预分区
2.通过java代码实现建表并且预分区
hbase给我们提供了两个方法可以做预分区:
- RegionSplitter.HexStringSplit:适合于当前rowkey是16进制字符串(7E8A9S777A)
- RegionSplitter.UnformSplit:适合于rowkey经过hash,MD5处理过后的
0000| 0001| 0002| 0003|…0007|
竖杠是ASCII中最大的值,因为我们rowkey是字典排序,增加竖杠就就一点会在这个范围之内
2.1工具类:实现预分区前缀二进制数组的生成
- 创建一个指定分区数量的接口
package com.cartravel.hbase;
/**
* 创建一个指定分区数量的接口
*/
public interface SplitKeyCalulaor {
public byte[][] getSplitKeys(int regionNum);
}
byte[][]二进制数组
实现过程中要运用到lang3这个包下的leftPad()左填充构建前缀
StringUtils.leftPad(null, *, *) = null
StringUtils.leftPad("", 3, 'z') = "zzz"
StringUtils.leftPad("bat", 3, 'z') = "bat"
StringUtils.leftPad("bat", 5, 'z') = "zzbat"
StringUtils.leftPad("bat", 1, 'z') = "bat"
StringUtils.leftPad("bat", -1, 'z') = "bat"
@param str:要给那个的字符串左边进行拼接,可以为null
@param size:拼接后总字符个数
@param padChar:拼接的字符
- 实现预分区前缀的生成
package com.cartravel.hbase;
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.hbase.util.Bytes;
import java.util.Iterator;
import java.util.TreeSet;
/**
*工具类实现的是:生成预分区前缀
*/
public class SparkKeyBuilder implements SplitKeyCalulaor {
//############## 实现上述接口生成预分区的前缀(返回的是二进制数组) ###################
public byte[][] getSplitKeys(int regionNum) {
//1.构建前缀
String[] keys = new String[regionNum];
for(int i=0;i<regionNum;i++){
String pre = StringUtils.leftPad(String.valueOf(i), 4, "0") + "|";
keys[i] = pre;
}
//2.构建一个返回值数组
byte[][] splitKeys = new byte[keys.length][];
//使用一个有序集合封装前缀
TreeSet<byte[]> row = new TreeSet<byte[]>();
for (int i = 0; i < keys.length; i++) {
row.add(Bytes.toBytes(keys[i]));
}
//迭代TreeSet,把值赋值给splitKeys
Iterator<byte[]> iterator = row.iterator();
int i =0;
while(iterator.hasNext()){
byte[] tempRow = iterator.next();
iterator.remove();
splitKeys[i] = tempRow;
i++;
}
row.clear();
row=null;
return splitKeys;
}
}
2.2 通过预分区前缀的二进制数组和表描述器实现创建生成hbase表
package com.cartravel.hbase
import com.cartravel.loggings.Logging
import org.apache.hadoop.hbase.{HColumnDescriptor, HTableDescriptor, TableName}
import org.apache.hadoop.hbase.client.{Connection, TableDescriptorBuilder}
import org.apache.hadoop.hbase.io.compress.Compression.Algorithm
import org.apache.hadoop.hbase.regionserver.BloomType
object hbaseOperation extends Logging {
private def createTableBySelfBuilt(conn:Connection,tableName:TableName,regionNum:Int,column:Array[String]): Unit ={
this.synchronized{
val admin = conn.getAdmin
try{
if (admin.tableExists(tableName)){
//要想创建表,需要一个表的描述器
val tbDesc: HTableDescriptor = new HTableDescriptor(tableName)
if (column != null){
column.foreach(c =>{
val hcd = new HColumnDescriptor(c.getBytes())
hcd.setBlockCacheEnabled(false)
hcd.setMaxVersions(1)
hcd.setBloomFilterType(BloomType.ROW) //布隆过滤器
hcd.setCompressionType(Algorithm.SNAPPY) //指定压缩方式
//将列表示器添加到表描述器里面
tbDesc.addFamily(hcd)
})
}
val sparkKeyBuilder = new SparkKeyBuilder()
val splitKeys = sparkKeyBuilder.getSplitKeys(regionNum)
//建表
admin.createTable(tbDesc,splitKeys)//拥有预分区的hbase表就创建完毕了
}
}catch {
case e:Exception=>
//邮件报警
}
}
}
}