HBase学习笔记:HBase的预分区与BloomFilter特性

预分区特性

       Hbase中的表会被划分为n个Region,然后存放在多个RegionServer中,每个Region有StartKey和EndKey,表示这个Region维护的RowKey范围,而第一个Region没有StartKey,最后一个Region没有EndKey。需要读写数据时,RowKey会落在某个范围内,就会定位到目标的Region以及所在的RegionServer。
        默认情况下,创建表的时候只有一个Region,当表的数据增长,数据大小大于一定的阈值,HBase就会找到一个MidKey将Region一分为二,这个过程被称为Region-Split,而Split是有时间和资源开销的。

随机散列与预分区

1.通过随机取样随机生成一定数量的RowKey,将取样数据按升序排序放到一个集合里
2.根据预分区的Region个数,对集合进行平均切割
3.HBaseAdmin.createTable(HTableDescriptor tableDescriptor,byte[][] splitkeys)可以指定预分区的splitKey,即是指定region间的rowkey临界值.

Java实现

两个接口

package com.test.HBasePrepartition;

public interface RowKeyGenerator {
	byte[] nextId();
}
package com.test.HBasePrepartition;

//createTable(HTableDescriptor desc, byte[][] splitKeys),所以要建二维数组
public interface SplitKeysCalculator {
	public byte[][] calcSplitKeys();
}

RowKey的生成 

package com.test.HBasePrepartition;

import java.util.Random;

import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.MD5Hash;

public class HashRowKeyGenerator implements RowKeyGenerator {
    private long currentId = 1;
    private long currentTime = System.currentTimeMillis();
    private Random random = new Random();
    public byte[] nextId() {
        try {
            currentTime += random.nextInt(1000);
            //从偏移量第4位开始取长度为4作为byte数组返回
            byte[] lowT = Bytes.copy(Bytes.toBytes(currentTime), 4, 4);
            byte[] lowU = Bytes.copy(Bytes.toBytes(currentId), 4, 4);
            
            //拼装成一个byte数组
            return Bytes.add(MD5Hash.getMD5AsHex(Bytes.add(lowU, lowT)).substring(0, 8).getBytes(),Bytes.toBytes(currentId));
        } finally {
        	//ID递增
            currentId++;
        }
    }
}

 生成RowKeys,通过对RowKeys均分得出SplitKeys

package com.test.HBasePrepartition;

import java.util.Iterator;
import java.util.TreeSet;

import org.apache.hadoop.hbase.util.Bytes;

public class HashChoreWoker implements SplitKeysCalculator{

    //数据量
    private int baseRecord;

    //rowkey生成器
    private RowKeyGenerator rkGen;

    //取样时,由取样数目及region数相除所得的数量,即每个region所要存储的数据量
    private int splitKeysBase;

    //splitkeys个数
    private int splitKeysNumber;

    //由抽样计算出来的splitkeys结果
    private byte[][] splitKeys;

    //构造方法
    public HashChoreWoker(int baseRecord, int prepareRegions) {
        this.baseRecord = baseRecord;

        //实例化rowkey生成器
        rkGen = new HashRowKeyGenerator();

        //所需要的SplitKey为分区数-1
        splitKeysNumber = prepareRegions - 1;
        splitKeysBase = baseRecord / prepareRegions;
    }

    //计算splitkey
    public byte[][] calcSplitKeys() {

        splitKeys = new byte[splitKeysNumber][];

        //使用treeset保存抽样数据,通过使用比较器进行排序
        TreeSet rows = new TreeSet(Bytes.BYTES_COMPARATOR);

        //生成抽样数据,因为使用了try..finally,所以生成的id在递增
        for (int i = 0; i < baseRecord; i++) {
            rows.add(rkGen.nextId());
        }
        
        int pointer = 0;
        Iterator rowKeyIter = rows.iterator();
        int index = 0;
        //遍历生成的rowkey,当pointer的值与每个region所要存储的数据量相等时,插入一个rowkey作为split
        while (rowKeyIter.hasNext()) {
            byte[] tempRow = rowKeyIter.next();
            rowKeyIter.remove();
            if ((pointer != 0) && (pointer % splitKeysBase == 0)) {
                if (index < splitKeysNumber) {
                    splitKeys[index] = tempRow;
                    index ++;
                }
            }
            pointer ++;
        }
        rows.clear();
        rows = null;
        return splitKeys;
    }
}

根据生成的SplitKeys建表,获得预分区的效果 

package com.test.HBasePrepartition;

import java.io.IOException;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;

import org.apache.hadoop.hbase.util.Bytes;

public class TestHashAndCreateTable {
	public static void main(String[] args) throws IOException {
		HashChoreWoker worker = new HashChoreWoker(1000000,10);
        byte [][] splitKeys = worker.calcSplitKeys();
        
        Configuration conf=HBaseConfiguration.create();
        Connection conn=ConnectionFactory.createConnection(conf);
        Admin admin = conn.getAdmin();
        TableName tableName = TableName.valueOf("hash_split_table");
        
        if (admin.tableExists(tableName)) {
            try {
                admin.disableTable(tableName);
            } catch (Exception e) {
            }
            admin.deleteTable(tableName);
        }

        HTableDescriptor tableDesc = new HTableDescriptor(tableName);
        HColumnDescriptor columnDesc = new HColumnDescriptor(Bytes.toBytes("info"));
        columnDesc.setMaxVersions(1);
        tableDesc.addFamily(columnDesc);

        admin.createTable(tableDesc ,splitKeys);

        admin.close();
	}
}

在HBase的Web界面可以看见已经分好了10个Region

HBase学习笔记:HBase的预分区与BloomFilter特性_第1张图片

put数据的时候也是按指定的方法来生成rowkey,因为函数固定,生成的rowkey也是固定的。

BloomFilter

       Bloom Filter是由Bloom在1970年提出的一种多哈希函数映射的快速查找算法。通常应用在一些需要快速判断某个元素是否属于集合,但是并不严格要求100%正确的场合。

算法分析

       经过研读多篇文章,对BloomFilter算法有了一定的了解,如果理解有误,请指正,但这里不作数学类分析。
      BloomFilter的原理是,集合的大小为n,当一个元素被加入集合S时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。下面是一个例子。
      初始状态时候,Bloom Filter是一个包含m位的位数组,每一位都置为0:

      当元素A出现,使用两个Hash函数,计算出元素A对应的Hash值为1和5,然后到位数组中判断第1位和第5位的值,发现均为0,因此元素A不在位数组里,将元素A的位信息添加进位数组,元素A添加进集合。

       之后的元素,要添加进集合也是使用一样的方法。如果要判断是否在集合内,则对元素进行Hash得出Hash值作为,在位数组中,计算出的Hash值作为编号对应的值均为1,则认为该元素在集合内(但是有可能是误判);如果对应的值有任何一个是0,则被检元素一定不在集合内。

        随着元素的插入,位数组中修改的值变多,出现误判的几率也随之变大,所谓的误判就是发生了哈希碰撞,即对不同的值进行Hash得出的值是相同的,但这个元素不在集合内。
      字符串加入了就被不能删除了,因为删除会影响到其他字符串。实在需要删除字符串的可以使用Counting bloomfilter(CBF),这是一种基本Bloom Filter的变体,CBF将基本Bloom Filter每一个Bit改为一个计数器,这样就可以实现删除字符串的功能了。 
       根据这篇参考文献,对于给定的m、n,当 k = ln(2)* m/n 时出错的概率是最小的。同时该文献还给出特定的k,m,n的出错概率,即一个m/n与k值的对应表。

BloomFilter在HBase的作用

HBase中,使用BloomFilter有两个控制粒度:
     1.ROW依据KeyValue中的RowKey来过滤StoreFile(对Get有优化)
           举例:假设有2个storefile文件sf1和sf2, 
                      sf1包含kv1(r1 cf:q1 v)、kv2(r2 cf:q1 v) 
                      sf2包含kv3(r3 cf:q1 v)、kv4(r4 cf:q1 v)

        如果设置了bloomfilter为ROW,那么get(r1)时就会过滤sf1,get(r3)就会过滤sf2

     2.ROWCOL依据KeyValue中的RowKey+Qualifier来过滤HFile(对Scan有优化)
             举例:假设有2个storefile文件sf1和sf2, 
                        sf1包含kv1(r1 cf:q1 v)、kv2(r2 cf:q1 v) 
                        sf2包含kv3(r1 cf:q2 v)、kv4(r2 cf:q2 v)

             如果设置了bloomfilter为ROW,无论get(r1,q1)还是get(r1,q2),都会读取sf1+sf2; 
             而如果设置了CF属性中的bloomfilter为ROWCOL,那么get(r1,q1)就会过滤sf2,get(r1,q2)就会过滤sf1。

        如果在表中设置了BloomFilter,那么HBase会在生成StoreFile时,包含一份BloomFilter 结构的数据,称其为MetaBlock;MetaBlock与DataBlock(真实的KeyValue数据)一起由LRUBlockCache维护,所以开启BloomFilter会有一定的存储及内存cache开销。
         Region下的StoreFile数目越多,BloomFilter效果越好;StoreFile数目越少,HBase读的效率越高。

BloomFilter在HBase中如何开启

在建表的时候加上BloomFilter的特性即可

create 'test',{NAME=>'INFO,BLOOMFILTER=>'ROWCOL'}
create 'test1',{NAME=>'INFO,BLOOMFILTER=>'ROW'}


参考:  
http://pages.cs.wisc.edu/~cao/papers/summary-cache/node8.html 
http://zjushch.iteye.com/blog/1530143
http://lxw1234.com/archives/2015/12/580.htm

你可能感兴趣的:(Hbase)