结合业务场景的HBase预分区及热点处理

本文深入探讨了HBase中热点数据产生的原因,主要源于rowkey设计不合理导致的数据集中写入同一region。为解决这一问题,介绍了预分区策略以及加盐和散列算法的应用,通过在rowkey前添加散列值确保数据均匀分布,从而有效防止热点的出现。此外,文章强调了rowkey设计的原则,包括长度短、数据分散、考虑热点以及唯一性。
摘要由CSDN通过智能技术生成

一、 业务背景

业务优化需要将海量的回调数据处理后存入hbase表,供后续查询,分析,导出等处理,hbase在存储大量数据时,首先要考虑的是热点与预分区的问题,本文主要探讨热点产生的原因、如何结合实际业务进行合理的预分区。

二、 热点产生原因

想知道hbase数据热点产生的原因,需要先了解hbase的存储结构,入下图所示

HBase中的每张表都通过行键按照一定的范围被分割成多个子表(HRegion),默认一个HRegion超过256M就要被分割成两个,由HRegionServer管理,管理哪些HRegion由HMaster分配,通常较少的region数量可使群集运行的更加平稳,官方指出每个RegionServer大约100个regions。

HRegionServer存取一个子表时,会创建一个HRegion对象,然后对表的每个列族(Column Family)创建一个Store实例,每个Store都会有0个或多个StoreFile与之对应,每个StoreFile都会对应一个HFile, HFile就是实际的存储文件。因此,一个HRegion有多少个列族就有多少个Store。

另外,每个HRegion还拥有一个MemStore实例。

也就是说我们的HBase的表会被划分为1个或多个Region,被托管在RegionServer中。

 可以看到上面的表有两个region,同时我们可以看到region有两个重要的属性:StartKey和EndKey。表示这个Region维护的rowkey的范围,当我们要读写数据时,如果rowkey落在某个start-end key范围内,那么就会定位到目标region并且读写到相关的数据。

默认情况下,当我们通过hbaseAdmin来创建一张表时,刚开始的时候只有一个Region,start-endkey没有边界,所有进入的数据都会被接收并存储。

所有的rowkey都会写入当前的region,随着数据量的增长,region的size变大,当到达阀值时,hbase会将region一分为二成两个region,这个过程称为region-split,源码在以下位置

org/apache/hadoop/hbase/regionserver/SplitRequest.java

private void requestRegionSplit() {
    final TableName table = parent.getTable();
    final RegionInfo hri_a = RegionInfoBuilder.newBuilder(table)
        .setStartKey(parent.getStartKey())
        .setEndKey(midKey)
        .build();
    final RegionInfo hri_b = RegionInfoBuilder.newBuilder(table)
        .setStartKey(midKey)
        .setEndKey(parent.getEndKey())
        .build();
    // Send the split request to the master. the master will do the validation on the split-key.
    // The parent region will be unassigned and the two new regions will be assigned.
    // hri_a and hri_b objects may not reflect the regions that will be created, those objects
    // are created just to pass the information to the reportRegionStateTransition().
    if (!server.reportRegionStateTransition(new RegionStateTransitionContext(
      TransitionCode.READY_TO_SPLIT, HConstants.NO_SEQNUM, -1, parent, hri_a, hri_b))) {
      LOG.error("Unable to ask master to split " + parent.getRegionNameAsString());
    }
  }

如果我们在创建hbase表的时候,不进行预分区设置,默认只有一个region,一般情况我们的rowkey是顺序增长的,这样会存在问题:我们总是向最大的startkey所在的region写数据,因为我们的rowkey总是会比之前的大,并且hbase的是按升序方式排序的。所以写操作总是被定位到无上界的那个region中,之前分裂出来的region不会被写数据,所以这样产生的结果是不利的。
同时如果写请求很频繁,数据量增长很快,split的次数就会变多,每次分裂都伴随着资源消耗,所以我们不希望这种情况经常发生,所以我们可以采用rowkey做散列、预分区的方式来解决问题。

采用预分区方式创建hbase表,就是提前创建好多个region,所有region都维护自己的start-key和end-key,我们知道在hbase中,表的所有行都是按照rowkey 的字典序排列的,表在行的方向上分割为多个region,那么当我们的rowkey包含业务逻辑,比如号码,那么创建预分区后,如果直接拿号码做rowkey,那么按字典排列,就会造成大量数据进入同一个region,达到阈值后,region分裂,这样既形成了热点数据,也导致预分区失去了作用,所以当我们采用预分区时,要考虑如何设计rowkey来避免热点数据的出现。

我们总结一下:热点问题主要原因在于rowkey的设计不合理.在某个时间段,对HBase的读写请求集中到少数几个region上面,导致这些region所属的regionserver请求量比较大,负载压力增加,而其他regionserver属于空闲状态,一般这种问题就是hbase的rowkey热点问题了

三、 如何避免热点数据出现

针对业务中,rowkey是由号码组成的,所以下述我们针对号码来讨论如何解决热点问题

加盐

我们知道由于hbase行都是按照rowkey 的字典序排列的,预分区后region的startKey和endKey的范围,就是rowKey的前缀,也就是说rowKey的前缀决定了数据会进入哪个region.那么我们在rowKey前面加入随机数,就可以保证数据可以分散的进入不同的region了。首先我们通过javaApi创建预分区表

    public static void createTable(Connection connection, String tName, String... columnFamily){

        try(Admin admin =connection.getAdmin()){
            TableName tableName = TableName.valueOf(tName);
            //判断表是否存在
            if(admin.tableExists(tableName)){
                logger.error("表" + tName + "已存在");
                //System.exit(0);
            }else{
                //创建表属性对象,表名需要转字节
                HTableDescriptor descriptor = new HTableDescriptor(tableName);
                //创建多个列族
                for(String cf : columnFamily){
                    descriptor.addFamily(new HColumnDescriptor(cf));
                }
                //创建256个region,001-255,三位数字分区,key的头也为三位数字
                byte[][] regions = new byte[255][];
                for (int i = 1; i < 256; i++) {
                    String mk = String.valueOf(i);
                    if(mk.length() == 1){
                        mk = "00" + mk;
                    } else if(mk.length() == 2){
                        mk = "0" + mk;
                    }
                    regions[i - 1] = mk.getBytes();
                }
                //根据对表的配置,创建表
                admin.createTable(descriptor,regions);
                logger.info("表" + tableName + "创建成功!");
            }
        } catch (Exception e){
            logger.error("error", e);
        }
    }

上述代码中我们按001-255区间范围 分了256个预分区,可以结合实际业务进行region数的调整

 

rowKey在号码前面拼接随机数,通过测试发现数据可以均匀进入region,但是实际业务中出现问题,每个号码可能发送多条数据,hbase中要做更新操作,如果rowKey包含随机部分,就无法实现更新操作,所以该方案不适合我们,那么我们就需要一种能固定hash并且不会形成热点的前缀算法。经过寻找,我们直接使用redis的一种实现算法,如下

    /**
     * 通过号码获取均匀散列映射到256范围内
     * @param phone
     * @return
     */
    public static String getTableIdByPhone(String phone) {
        int hash = CRC16.getCRC16(phone) % SIZE;
        String mk = String.valueOf(hash);
        if(mk.length() == 1){
            mk = "00" + mk;
        } else if(mk.length() == 2){
            mk = "0" + mk;
        }
        return mk;
    }

SIZE是我们的预分区数量,通过该算法可以将号码均匀散列到指定范围内。将随机数替换为散列生成算法,就可以解决热点问题了。

四、rowkey设计原则

我们总结一下hbase 的rowkey设计原则

rowkey的长度尽量短

散列原则,将数据分散到不同的region中

表设计要考虑好热点问题

保证rowkey是唯一的

下期我们会总结一下hbase的使用与优化。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
HBase 中,分区是一个很重要的概念,它可以提高 HBase 的性能和可伸缩性。分区是指在创建 HBase 表时,手动指定表的分区键,以便将数据分布到多个 Region 中。分区的目的是让数据分布均匀,避免某个 Region 过大而导致负载不均衡的情况。 分区的设计需要考虑以下几个因素: 1. 数据的访问模式:首先需要了解数据的访问模式,比如是否是范围查询、随机查询等,以便根据不同的访问模式来设计分区。 2. 数据的分布情况:需要了解数据的分布情况,比如数据的热点区域、数据的更新频率等,以便根据不同的分布情况来设计分区。 3. 期的数据量:需要估未来的数据量,以便根据数据量来设计分区。 4. 集群的硬件配置:需要了解集群的硬件配置,比如服务器的数量、内存大小、磁盘容量等,以便根据硬件配置来设计分区。 在设计分区时,可以采用以下几种策略: 1. 均匀分区:将表的分区键分成相等的若干部分,每个分区大小相等。 2. 范围分区:根据数据的范围来划分分区,比如按照时间范围来划分分区。 3. 哈希分区:根据分区键的哈希值来划分分区,可以确保数据分布均匀。 4. 混合分区:可以将多种分区策略组合起来使用,以便充分利用各种策略的优点。 需要注意的是,分区的设计需要根据实际情况进行调整和优化,以便达到最佳的性能和可伸缩性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值