hbase热点问题解决(预分区)

一、出现热点问题原因

       1、hbase的中的数据是按照字典序排序的,当大量连续的rowkey集中写在个别的region,各个region之间数据分布不均衡;

       2、创建表时没有提前预分区,创建的表默认只有一个region,大量的数据写入当前region;

       3、创建表已经提前预分区,但是设计的rowkey没有规律可循,设计的rowkey应该由regionNo+messageId组成。

二、如何解决热点问题       

       设计可以让数据分布均匀的rowkey,与nosql数据库们一样,rowkey是用来检索记录的主键。访问hbase table中的行,rowkey 可以是任意字符串(最大长度 是 64KB,实际应用中长度一般为 10-100bytes),在hbase内部,rowkey保存为字节数组,存储时,数据按照rowkey的字典序排序存储。

         创建表命令:

create 'testTable',{NAME => 'cf', DATA_BLOCK_ENCODING => 'NONE', BLOOMFILTER => 'ROW', REPLICATION_SCOPE=> '0', VERSIONS => '1', COMPRESSION => 'snappy', MIN_VERSIONS =>'0', TTL => '15552000', KEEP_DELETED_CELLS => 'false', BLOCKSIZE =>'65536', IN_MEMORY => 'false', BLOCKCACHE => 'true', METADATA =>{'ENCODE_ON_DISK' => 'true'}},{SPLITS_FILE=>'/app/soft/hbaseregionsplist/region.txt'}

        region.txt内容:

        

        我这里预分10个region,创建表之后,在hbae的ui中可以看到以下信息,说明分预期ok了!!!

        

        1、第一种设计rowkey方式:随机数+messageId,如果想让最近的数据快速get到,可以将时间戳加上

             我这里的region是0001|到0009|开头的,因为hbase的数据是字典序排序的,所以如果我生成的                                                 rowkey=0002rer4343343422,则当前这条数据就会保存到0001|~0002|这个region里,使用了|,因为我的messageId都是字母和数字,|的ASCII值大于字母和数字。

                 regionNum=10,因为我预分10个region

            

                  


           这种设计的rowkey可以解决热点问题,但是要建立关联表,比如将rowkey保存到数据库或者nosql数据库中,因为前面的regionNo是随机的,不知道 对应数据在hbase的rowkey是多少;同一批数据,因为这个regionNo是随机的,所以要到多个region中get数据,不能使用startkey和endkey去get数据。

          2、第二种设计rowkey的方式:通过messageId映射regionNo,这样既可以让数据均匀分布到各个region中,同时可以根据startkey和endkey可以get到同一批数据

          messageId映射regionNo,使用一致性hash算法解决,一致性哈希算法在1997年由麻省理工学院的Karger等人在解决分布式Cache中提出的,设计目标是为了解决因特网中的热点(Hot spot)问题,参考(https://baike.baidu.com/item/%E4%B8%80%E8%87%B4%E6%80%A7%E5%93%88%E5%B8%8C/2460889?fr=aladdin)

(https://www.cnblogs.com/lpfuture/p/5796398.html)

public class ConsistentHash<T> implements Serializable{
    private static final long serialVersionUID = 1L;
    private  final HashFunction hashFunction;
    //每个regions的虚拟节点个数
    private final int numberOfReplicas;
    //存储虚拟节点的hash值到真实节点的映射
    private final SortedMap<Long, String> circle = new TreeMap<Long, String>();
    public ConsistentHash(HashFunction hashFunction, int numberOfReplicas, Collection<String> nodes) {
        this.hashFunction = hashFunction;
        this.numberOfReplicas = numberOfReplicas;
        for (String node : nodes){
            add(node);
        }
    }
    /**
     * 添加节点
     * @param node
     * @see java.util.TreeMap
     * */
    public void add(String node) {
        for (int i = 0; i < numberOfReplicas; i++)
             /*
              * 不同的虚拟节点(i不同)有不同的hash值,但都对应同一个实际机器node
              * 虚拟node一般是均衡分布在环上的,数据存储在顺时针方向的虚拟node上
              */
            circle.put(hashFunction.getHashValue(node.toString() + i), node);
    }
    /**
     * 移除节点
     * @param node
     * @see java.util.TreeMap
     * */
    public void remove(String node) {
        for (int i = 0; i < numberOfReplicas; i++)
            circle.remove(hashFunction.getHashValue(node.toString() + i));
    }
    /**
     * 获取对应key的hashcode值,然后根据hashcode获取当前数据储存的真实节点
     * */
    public String get(Object key) {
        if (circle.isEmpty())
            return null;
        //获取对应key的hashcode值
        long hash = hashFunction.getHashValue((String) key);
        //数据映射在两台虚拟机器所在环之间,就需要按顺时针方向寻找机器
        if (!circle.containsKey(hash)) {
            SortedMap<Long, String> tailMap = circle.tailMap(hash);
            hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
        }
        return circle.get(hash);
    }
    /**
     * 获取hash环节点大小
     * @return
     * */
    public long getSize() {
        return circle.size();
    }
    /**
     * 获取double类型数据的小数位后四位小数
     * @param num
     * @return
     * */
    public String getDecimalPoint(double num){
        DecimalFormat df = new DecimalFormat("0.0000");
        return df.format(num);
    }

}

public class HashFunction implements Serializable{
    private static final long serialVersionUID = 1L;
    /**
     * 获取对应字符串的hashCode值
     * @param key
     * @return
     * */
    public  long getHashValue(String key) {
        final int p = 167776199999;
        int hash = (int) 216613626111L;
        for (int i = 0; i < key.length(); i++)
            hash = (hash ^ key.charAt(i)) * p;
        hash += hash << 13;
        hash ^= hash >> 8;
        hash += hash << 3;
        hash ^= hash >> 18;
        hash += hash << 5;
        // 如果算出来的值为负数则取其绝对值
        if (hash < 0)
            hash = Math.abs(hash);
        return hash;
    }
}
 这样可以通过messageId映射出regionNo,最后得到rowkey。

     我目前满意第二种方式,然后在es中建立关联表,get数据时,现在es中get到rowkey,然后在hbase中获取数据,这个根据自己的业务设计。

写的内容有问题,欢迎来吐槽,我会及时修改,谢谢!

猜你喜欢

转载自blog.csdn.net/qq_31289187/article/details/80869906