当前位置:   article > 正文

hbase 的Rowkey设计方案_hbase rowkey生成

hbase rowkey生成

 

1.1 hbase的概述

HBase由于其存储和读写的高性能,在OLAP即时分析中越来越发挥重要的作用。作为Nosql数据库的一员,HBase查询只能通过其Rowkey来查询(Rowkey用来表示唯一一行记录),Rowkey设计的优劣直接影响读写性能。

由于HBase是通过Rowkey查询的,一般Rowkey上都会存一些比较关键的检索信息,我们需要提前想好数据具体需要如何查询,根据查询方式进行数据存储格式的设计要避免做全表扫描,因为效率特别低

此外易观方舟也使用HBase做用户画像的标签存储方案,存储每个app的用户的人口学属性和商业属性等标签信息

HBase中设计有MemStore和BlockCache,分别对应列族/Store级别的写入缓存,和RegionServer级别的读取缓存。如果RowKey过长,缓存中存储数据的密度就会降低,影响数据落地或查询效率。

 1.2 hbase的设计原则以及解决方法

 

1.3 预分区

1.3.1 什么是预分区

HBase表在刚刚被创建时,只有1个分区(region),当一个region过大(达到hbase.hregion.max.filesize属性中定义的阈值,默认10GB)时,

表将会进行split,分裂为2个分区。表在进行split的时候,会耗费大量的资源,频繁的分区对HBase的性能有巨大的影响。

HBase提供了预分区功能,即用户可以在创建表的时候对表按照一定的规则创建分区

1.3.2 预分区的目的

减少由于region split带来的资源消耗。从而提高HBase的性能。

避免数据倾斜,热点等问题

  • hbase的热点问题

1.4 hbase的热点

  默认情况下,当我们通过hbaseAdmin指定TableDescriptor来创建一张表时,只有一个region正处于混沌时期,start-end key无边界,可谓海纳百川。所有的rowkey都写入到这个region里,然后数据越来越多,region的size越来越大时,大到一定的阀值,hbase就会将region一分为二,成为2个region,这个过程称为分裂(region-split)。

 如果我们就这样默认建表,表里不断的put数据,更严重的是我们的rowkey还是顺序增大的,是比较可怕的。存在的缺点比较明显

首先是热点写,我们总是会往最大的start-key所在的region写东西,因为我们的rowkey总是会比之前的大,并且hbase的是按升序方式排序的。所以写操作总是被定位到无上界的那个region中。
其次,由于写热点,我们总是往最大start-key的region写记录,之前分裂出来的region不会再被写数据,有点被打进冷宫的赶脚,它们都处于半满状态,这样的分布也是不利的。
 如果在写频率高的场景下,数据增长快,split的次数也会增多,由于split是比较耗时耗资源的,所以我们并不希望这种事情经常发生。

1.5 hbase的热点的解决方法

预分区与随机散列二者结合起来,是比较完美的。

预分区一开始就预建好了一部分region,这些region都维护着自己的start-end keys,在配合上随机散列,写数据能均衡的命中这些预建的region,就能解决上面的那些缺点,大大提供性能。

https://www.cnblogs.com/cxzdy/p/5521308.html     rowkey设计

https://blog.csdn.net/w1014074794/article/details/73140489   rowkey的设计

1.5.1 方法一:hash散列预分区

 hash就是rowkey前面由一串随机字符串组成,随机字符串生成方式可以由SHA或者MD5方式生成只要region所管理的start-end keys范围比较随机,那么就可以解决写热点问题

1.rowkey的生成策略:

rowkey原本是自增长的long型,可以将rowkey转为hash再转为bytes,加上本身id转为bytes。rowkey=hash(id)+id;这样就生成随便的rowkey。

2.预分区:对于这种方式的rowkey设计,如何去进行预分区呢?

2.1取样,先随机生成一定数量的rowkey,将取样数据按升序排序放到一个集合里。

2.2根据预分区的region个数,对整个集合平均分割,即是相关的splitkeys。

2.3 HBaseAdmin.createTable(HTableDescriptor tableDescriptor,byte[][] splitkeys)可以指定预分区的splitkey,即指定region间的rowkey临界值。

3.  以上就是按照hash方式,预建好分区,后再插入数据的时候,也是按照此rowkeyGenerator的方式生成rowkey

  1. public interface SplitKeysCalculator {
  2. }
  3. public interface RowKeyGenerator {
  4. byte [] nextId();
  5. }
  6. public class HashRowKeyGenerator implements RowKeyGenerator {
  7. private long currentId = 1;
  8. private long currentTime = System.currentTimeMillis();
  9. private Random random = new Random();
  10. public byte[] nextId() {
  11. try {
  12. currentTime += random.nextInt(1000);
  13. byte[] lowT = Bytes.copy(Bytes.toBytes(currentTime), 4, 4);
  14. byte[] lowU = Bytes.copy(Bytes.toBytes(currentId), 4, 4);
  15. return Bytes.add(MD5Hash.getMD5AsHex(Bytes.add(lowU, lowT)).substring(0, 8).getBytes(),
  16. Bytes.toBytes(currentId));
  17. } finally {
  18. currentId++;
  19. }
  20. }
  21. }
  22. //
  23. public class HashMethodCreatePartition implements SplitKeysCalculator {
  24. //随机取机数目
  25. private int baseRecord;
  26. //rowkey生成器
  27. private RowKeyGenerator rkGen;
  28. //取样时,由取样数目及region数相除所得的数量.
  29. private int splitKeysBase;
  30. //splitkeys个数
  31. private int splitKeysNumber;
  32. //由抽样计算出来的splitkeys结果
  33. private byte[][] splitKeys;
  34. public HashMethodCreatePartition(int baseRecord, int prepareRegions) {
  35. this.baseRecord = baseRecord;
  36. //实例化rowkey生成器
  37. rkGen = new HashRowKeyGenerator();
  38. splitKeysNumber = prepareRegions - 1;
  39. splitKeysBase = baseRecord / prepareRegions;
  40. }
  41. public byte[][] calcSplitKeys() {
  42. splitKeys = new byte[splitKeysNumber][];
  43. //使用treeset保存抽样数据,已排序过
  44. TreeSet<byte[]> rows = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR);
  45. for (int i = 0; i < baseRecord; i++) {
  46. rows.add(rkGen.nextId());//关键步骤
  47. }
  48. int pointer = 0;
  49. Iterator<byte[]> rowKeyIter = rows.iterator();
  50. int index = 0;
  51. while (rowKeyIter.hasNext()) {
  52. byte[] tempRow = rowKeyIter.next();
  53. rowKeyIter.remove();
  54. if ((pointer != 0) && (pointer % splitKeysBase == 0)) {
  55. if (index < splitKeysNumber) {
  56. splitKeys[index] = tempRow;
  57. index ++;
  58. }
  59. }
  60. pointer ++;
  61. }
  62. rows.clear();
  63. rows = null;
  64. return splitKeys;
  65. }
  66. //测试类:
  67. public class TestHashPartition {
  68. private final static Logger log= Logger.getLogger(String.valueOf(TestHashPartition.class));
  69. public static void main(String args[])throws Exception{
  70. HashMethodCreatePartition worker = new HashMethodCreatePartition(1000000,10);
  71. byte [][] splitKeys = worker.calcSplitKeys();
  72. String tableName="hash_split_table";//用户画像hbase中的表
  73. TableName tn=TableName.valueOf(Constants.HBASE_TABLE_NAME.value);
  74. String familyArray[]={"mboth"};//定义列族
  75. initUserTable(tableName,familyArray,true,splitKeys);
  76. }
  77. public static void initUserTable(String tableName,String familyArray[],boolean partionFlag,byte[][] splitKeys){
  78. List<String> list=new ArrayList<String>();
  79. try {
  80. Admin hadmin = HbaseConnectionUtils.getInstance().getConnection().getAdmin();
  81. TableName tm = TableName.valueOf(tableName);
  82. if (!hadmin.tableExists(TableName.valueOf(tableName))) {
  83. HTableDescriptor hTableDescriptor = new HTableDescriptor(tm);
  84. for(String colFa:familyArray){
  85. HColumnDescriptor family = new HColumnDescriptor(colFa);
  86. family.setMaxVersions(1);
  87. hTableDescriptor.addFamily(family);
  88. }
  89. if(partionFlag){
  90. hadmin.createTable(hTableDescriptor, splitKeys);
  91. }
  92. else {
  93. hadmin.createTable(hTableDescriptor);//不分区
  94. }
  95. hadmin.close();
  96. }
  97. else {
  98. log.info("................新建表:"+tableName+"已存在..........................");
  99. }
  100. } catch (IOException e) {
  101. // TODO Auto-generated catch block
  102. e.printStackTrace();
  103. }
  104. log.info("................................................create hbase table "+tableName+" successful..........");
  105. }
  106. }

#结果:

1.5.2  方法二:整数取模预分区

rowkey:将ID取模后,然后拼上ID整体作为rowkey,rowkey=(id)%regionNum +id;

分区:用长整数作为分区号,每个region管理着相应的区域数据,splitkeys也非常简单,直接是分区号即可。

  1. public interface RowKeyGenerator {
  2. byte [] nextId();
  3. }
  4. public interface SplitKeysCalculator {
  5. }
  6. public class ModPartitionRowKeyManager implements RowKeyGenerator, SplitKeysCalculator {
  7. public static final int DEFAULT_PARTITION_AMOUNT = 20;
  8. private long currentId = 1;
  9. private int partition = DEFAULT_PARTITION_AMOUNT;
  10. public void setPartition(int partition) {
  11. this.partition = partition;
  12. }
  13. public byte[] nextId() {
  14. try {
  15. long partitionId = currentId % partition;
  16. return Bytes.add(Bytes.toBytes(partitionId+""),
  17. Bytes.toBytes(currentId+""));
  18. } finally {
  19. currentId++;
  20. }
  21. }
  22. public byte[][] calcSplitKeys() {
  23. byte[][] splitKeys = new byte[partition - 1][];
  24. for(int i = 1; i < partition ; i ++) {
  25. splitKeys[i-1] = Bytes.toBytes((long)i);
  26. }
  27. return splitKeys;
  28. }
  29. }
  30. //测试
  31. public class TestMod {
  32. private final static Logger log= Logger.getLogger(String.valueOf(TestMod.class));
  33. public static void main(String args[])throws Exception{
  34. ModPartitionRowKeyManager rkManager = new ModPartitionRowKeyManager();
  35. //只预建10个分区
  36. rkManager.setPartition(10);
  37. byte [][] splitKeys = rkManager.calcSplitKeys();
  38. String tableName="hash_split_table2";//用户画像hbase中的表
  39. TableName tn=TableName.valueOf(Constants.HBASE_TABLE_NAME.value);
  40. String familyArray[]={"mboth2"};//定义列族
  41. initUserTable(tableName,familyArray,true,splitKeys);
  42. //插入1亿条记录,看数据分布
  43. List<Put> listp = new ArrayList<Put>();
  44. for(int i = 0; i < 10000; i ++) {
  45. Put put = new Put(rkManager.nextId());
  46. put.addColumn("mboth2".getBytes(), "email".getBytes(), Bytes.toBytes("test@123.com"));
  47. listp.add(put);
  48. }
  49. HbaseConnectionUtils.getInstance().getTable("hash_split_table2").put(listp);
  50. }
  51. public static void initUserTable(String tableName,String familyArray[],boolean partionFlag,byte[][] splitKeys){
  52. List<String> list=new ArrayList<String>();
  53. try {
  54. Admin hadmin = HbaseConnectionUtils.getInstance().getConnection().getAdmin();
  55. TableName tm = TableName.valueOf(tableName);
  56. if (!hadmin.tableExists(TableName.valueOf(tableName))) {
  57. HTableDescriptor hTableDescriptor = new HTableDescriptor(tm);
  58. for(String colFa:familyArray){
  59. HColumnDescriptor family = new HColumnDescriptor(colFa);
  60. family.setMaxVersions(1);
  61. hTableDescriptor.addFamily(family);
  62. }
  63. if(partionFlag){
  64. hadmin.createTable(hTableDescriptor, splitKeys);
  65. }
  66. else {
  67. hadmin.createTable(hTableDescriptor);//不分区
  68. }
  69. hadmin.close();
  70. }
  71. else {
  72. log.info("................新建表:"+tableName+"已存在..........................");
  73. }
  74. } catch (IOException e) {
  75. // TODO Auto-generated catch block
  76. e.printStackTrace();
  77. }
  78. log.info("................................................create hbase table "+tableName+" successful..........");
  79. }
  80. }

#结果:

1.5.3  方法三:url进行md5后预分区

Rowkey的生成策略为=url的md5值;

Rowkey=md5(url)

/**
     * 当list的size小于baseReocrd,分区n,最终的结果可能要小于n
     * 如list的size为6,baseReord=100,分区20,理论每个分区5个,所以最终结果为2个分区

所以要尽量设置baseReocrd,regionNum,要合理

#代码:

public class CreateHbaseTable {
   private  final static Logger log= Logger.getLogger(String.valueOf(HbaseUtil.class));
    public static void main(String args[]){
        String tableName="hash_split_table3";//用户画像hbase中的表hash_split_table3 hb_user_profile_t
        TableName tn=TableName.valueOf(Constants.HBASE_TABLE_NAME.value);
        String familyArray[]={"user"};//定义列族
        initUserTable(tableName,familyArray,true);
       Table table= HbaseConnectionUtils.getInstance().getTable("hash_split_table3");
        putTable(table, familyArray);
    }

    /**
     * 当list的size小于baseReocrd,分区n,最终的结果可能要小于n
     * 如list的size为6,baseReord=100,分区20,理论每个分区5个,所以最终结果为2个分区
     * @param tableName
     * @param familyArray
     * @param partionFlag
     */
    public static void initUserTable(String tableName,String familyArray[],boolean partionFlag){
        List<String> list=new ArrayList<String>();
        list.add("http://guangzhou.anjuke.com/community/view/756975");
        list.add("http://chengdu.anjuke.com/community/view/141695");
        list.add("http://beijing.anjuke.com/community/view/80542");
        list.add("http://guangzhou.anjuke.com/community/view/111235");
        list.add("http://lijingfu.fang.com");
        list.add("http://tianjin.anjuke.com/community/view/202811");
        try {
            Admin hadmin = HbaseConnectionUtils.getInstance().getConnection().getAdmin();
            TableName tm = TableName.valueOf(tableName);
            if (!hadmin.tableExists(TableName.valueOf(tableName))) {
                HTableDescriptor hTableDescriptor = new HTableDescriptor(tm);
                for(String colFa:familyArray){
                    HColumnDescriptor family = new HColumnDescriptor(colFa);
                    family.setMaxVersions(1);
                    hTableDescriptor.addFamily(family);
                }
                if(partionFlag){
                    hadmin.createTable(hTableDescriptor,HbaseUtil.calcSplitKeys(list,100,20));
                }
                else {
                    hadmin.createTable(hTableDescriptor);//不分区
                }
                hadmin.close();
            }
            else {
                log.info("................新建表:"+tableName+"已存在..........................");
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        log.info("................................................create hbase table "+tableName+" successful..........");
    }
    public static void putTable(Table hTable, String familyArray[]) {
        List<Put> listp = new ArrayList<Put>();
       List<String>  dataList=new ArrayList<String>();
       dataList.add("http://guangzhou.anjuke.com/community/view/112825");
        dataList.add("http://lijingfu.fang.com/sdfsd/234");
        for (String key:dataList) {
           String rowKey=  HbaseUtil.createMd5(key);
            Put put = new Put(Bytes.toBytes(rowKey));
            put.addColumn(familyArray[0].getBytes(), "email".getBytes(),
                    Bytes.toBytes("ddd"));
            listp.add(put);
        }
        try {
            hTable.put(listp);
        } catch (IOException e) {
            e.printStackTrace();
        }
        listp.clear();
        log.info("添加数据成功..........................");
    }

}

#splitkeys

/**
 * 预分区,根据预分区的region个数,对整个集合平均分割,即是相关的splitkeys。
 * @param rkGen
 * @param baseRecord
 * @param prepareRegions
 * @return
 */
public static byte[][] calcSplitKeys(List<String> rkGen, int baseRecord, int prepareRegions) {
    int splitKeysNumber = prepareRegions - 1;
    int splitKeysBase = baseRecord / prepareRegions;
    byte[][] splitKeys = new byte[splitKeysNumber][];
    TreeSet<byte[]> rows = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR);
    for (String rk : rkGen) {
        rows.add(createMd5(rk).getBytes());
    }
    int pointer = 0;
    Iterator<byte[]> rowKeyIter = rows.iterator();
    int index = 0;
    while (rowKeyIter.hasNext()) {
        byte[] tempRow = rowKeyIter.next();
        if ((pointer != 0) && (pointer % splitKeysBase == 0)) {
            if (index < splitKeysNumber) {
                splitKeys[index] = tempRow;
                index ++;
            }
        }
        rowKeyIter.remove();
        pointer ++;

    }
    rows.clear();
    rows = null;
    //防止分区多,数据少,有些分区没有数据
    byte[][] splitKeysRegion = new byte[index][];
    for(int m=0;m<splitKeys.length;m++){
        byte[] tempRow=splitKeys[m];
        if(tempRow==null){
            continue;
        }
        splitKeysRegion[m]=tempRow;
    }
    return splitKeysRegion;
}

/**
 * md5随机散列,通过SHA或者md5生成随机散列的字符串。
 * @param plainText
 * @return
 */
public static String createMd5(String plainText) {
    byte[] secretBytes = null;
    try {
        secretBytes = MessageDigest.getInstance("md5").digest(
                plainText.getBytes());
    } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("没有md5这个算法!");
    }
    String md5code = new BigInteger(1, secretBytes).toString(16);// 16进制数字
    // 如果生成数字未满32位,需要前面补0
    for (int i = 0; i < 32 - md5code.length(); i++) {
        md5code = "0" + md5code;
    }
    return md5code;
}

#结果:

1.6 通过rowkey查看所属的region

  1. public static void getRegionInfo(){
  2. RegionLocator r= null;
  3. try {
  4. r = connection.getRegionLocator(TableName.valueOf("hash_split_table3"));
  5. HRegionLocation location = r.getRegionLocation(Bytes.toBytes("00582ad1ea63c52490c1c8fd135b67"));
  6. HRegionInfo rg = location.getRegionInfo();
  7. String regionname = Bytes.toString(rg.getRegionName());
  8. String strkey = Bytes.toString(rg.getStartKey());
  9. String endkey = Bytes.toString(rg.getEndKey());
  10. System.out.println(regionname);
  11. System.out.println("START:"+strkey);
  12. System.out.println("END:"+endkey);
  13. } catch (IOException e) {
  14. e.printStackTrace();
  15. }
  16. }

 

#结果:

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/510666
推荐阅读
相关标签
  

闽ICP备14008679号