Hbase热点产生及解决办法

HBase热点
什么是热点
HBase中的行是按照rowkey的字典顺序排序的,这种设计优化了scan操作,可以将相关的行以及会被一起读取的行存取在临近位置,便于scan。然而糟糕的rowkey设计是热点的源头。 热点发生在大量的client直接访问集群的一个或极少数个节点(访问可能是读,写或者其他操作)。 大量访问会使热点region所在的单个机器超出自身承受能力,引起性能下降甚至region不可用,这 也会影响同一个RegionServer上的其他region,由于主机无法服务其他region的请求。

产生的原因
Hbase 创建表默认只有一个分区
Rowkey 设计不合理
解决方案
Hbase 创建表时指定分区
合理设计Rowkey
Hbase 预分区
分区
HBase中,表会被划分为1…n个Region,被托管在RegionServer中。Region二个重要的属性:StartKey与 EndKey表示这个Region维护的rowKey范围,当我们要读/写数据时,如果rowKey落在某个start-end key范围内,那么就会定位到目标region并且读/写到相关的数据。

默认地,当我们只是通过HBaseAdmin指定TableDescriptor来创建一张表时,start-end key无边界,region的size越来越大时,大到一定的阀值,就会找到一个midKey将region一分为二,成为2个region,这个过程称为分裂,而midKey则为这二个region的临界。

缺点
总是往最大start-key的region写记录,之前分裂出来的region不会再被写数据,它们都处于半满状态
split是比较耗时耗资源
优点
合理设计rowkey 能让各个region 的并发请求 平均分配(趋于均匀) 使IO 效率达到最高
预分区
shell创建
create ‘table1’,‘cf1’ ,SPLITS=>[‘10’,‘20’,‘30’,‘40’]
创建5个regin,每个region都有个startKey和endKey,第一个region没有startKey,最后一个没有endKey:

第一个region:" to 10"
第二个region:“10 t0 20”
第三个region:“20 t0 30”
第四个region:“30 t0 40”
第五个region:"40 t0 "

java api createTable并预分区
在hbase包的Admin类中提供了4个create表的方法(前三个为同步创建,第四个为异步):

一.直接根据描述创建表
这里是直接根据表描述创建表,不指定分区。

/**

  • Creates a new table. Synchronous operation.
  • @param desc table descriptor for table
  • @throws IllegalArgumentException if the table name is reserved
  • @throws MasterNotRunningException if master is not running
  • @throws org.apache.hadoop.hbase.TableExistsException if table already exists (If concurrent
  • threads, the table may have been created between test-for-existence and attempt-at-creation).
  • @throws IOException if a remote or network exception occurs
    */
    void createTable(HTableDescriptor desc) throws IOException;
    二.根据描述和region个数以及startKey以及endKey自动分配

根据表描述以及指定startKey和endKey和region个数创建表,这里hbase会自动创建region个数,并且会为你的每一个region指定key的范围,但是所有的范围都是连续的且均匀的,如果业务key的某些范围内数据量很多有的很少,这样就会造成数据的数据的倾斜,这样的场景就必须自己指定分区的范围,可以用第三种或者第四种方式预分区。

/**

  • Creates a new table with the specified number of regions. The start key specified will become
  • the end key of the first region of the table, and the end key specified will become the start
  • key of the last region of the table (the first region has a null start key and the last region
  • has a null end key). BigInteger math will be used to divide the key range specified into enough
  • segments to make the required number of total regions. Synchronous operation.
  • @param desc table descriptor for table
  • @param startKey beginning of key range
  • @param endKey end of key range
  • @param numRegions the total number of regions to create
  • @throws IllegalArgumentException if the table name is reserved
  • @throws MasterNotRunningException if master is not running
  • @throws org.apache.hadoop.hbase.TableExistsException if table already exists (If concurrent
  • threads, the table may have been created between test-for-existence and attempt-at-creation).
  • @throws IOException
    */
    void createTable(HTableDescriptor desc, byte[] startKey, byte[] endKey, int numRegions)
    throws IOException;
    三.根据表的描述和自定义的分区设置创建表(同步)
    根据表的描述和自定义的分区设置创建表,这个就可以自己自定义指定region执行的key的范围,比如:

byte[][] splitKeys = new byte[][] { Bytes.toBytes(“100000”),
Bytes.toBytes(“200000”), Bytes.toBytes(“400000”),
Bytes.toBytes(“500000”) };
调用接口的时候splitKeys传入上面的值,那么他会自动创建5个region并且为之分配key的分区范围。

/**

  • Creates a new table with an initial set of empty regions defined by the specified split keys.
  • The total number of regions created will be the number of split keys plus one. Synchronous
  • operation. Note : Avoid passing empty split key.
  • @param desc table descriptor for table
  • @param splitKeys array of split keys for the initial regions of the table
  • @throws IllegalArgumentException if the table name is reserved, if the split keys are repeated
  • and if the split key has empty byte array.
  • @throws MasterNotRunningException if master is not running
  • @throws org.apache.hadoop.hbase.TableExistsException if table already exists (If concurrent
  • threads, the table may have been created between test-for-existence and attempt-at-creation).
  • @throws IOException
    */
    void createTable(final HTableDescriptor desc, byte[][] splitKeys) throws IOException;
    四.根据表的描述和自定义的分区设置创建表(异步)

/**

  • Creates a new table but does not block and wait for it to come online. Asynchronous operation.
  • To check if the table exists, use {@link #isTableAvailable} – it is not safe to create an
  • HTable instance to this table before it is available. Note : Avoid passing empty split key.
  • @param desc table descriptor for table
  • @throws IllegalArgumentException Bad table name, if the split keys are repeated and if the
  • split key has empty byte array.
  • @throws MasterNotRunningException if master is not running
  • @throws org.apache.hadoop.hbase.TableExistsException if table already exists (If concurrent
  • threads, the table may have been created between test-for-existence and attempt-at-creation).
  • @throws IOException
    */
    void createTableAsync(final HTableDescriptor desc, final byte[][] splitKeys) throws IOException;
    Hbase Rowkey设计原则
    rowkey长度原则
    rowkey是一个二进制码流,rowkey的长度被很多开发者建议说设计在10~100个字节,不过建议是越短越好,不要超过16个字节。

原因如下

数据的持久化文件HFile中是按照KeyValue存储的,如果Rowkey过长比如100个字节,1000万列数据光Rowkey就要占用100*1000万=10亿个字节,将近1G数据,这会极大影响HFile的存储效率.
MemStore将缓存部分数据到内存,如果Rowkey字段过长内存的有效利用率会降低,系统将无法缓存更多的数据,这会降低检索效率。因此Rowkey的字节长度越短越好。
目前操作系统是都是64位系统,内存8字节对齐。控制在16个字节,8字节的整数倍利用操作系统的最佳特性。
rowkey唯一原则
必须在设计上保证其唯一性,rowkey是按照字典顺序排序存储的,因此,设计rowkey的时候,要 充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问的数据放到一块。

rowkey散列原则
如果rowkey按照时间戳的方式递增,不要将时间放在二进制码的前面,建议将rowkey的高位作为 散列字段,由程序随机生成,低位放时间字段,这样将提高数据均衡分布在每个RegionServer, 以实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息,所有的数据都会集中在一个 RegionServer上,这样在数据检索的时候负载会集中在个别的RegionServer上,造成热点问题, 会降低查询效率。

Hbase 常见避免热点问题方法
加盐
在rowkey的前面增加随机数。具体就是给rowkey分配一个随机前缀以使得它和之前排序不同。分配的前缀种类数量应该和你想使数据分散到不同的region的数量一致。 如果你有一些热点rowkey反复出现在其他分布均匀的rowkey 中,加盐是很有用的。

假如你有下列 rowkey,你表中每一个 region 对应字母表中每一个字母。 以 ‘a’ 开头是同一个 region, ‘b’开头的是同一个region。在表中,所有以 ‘f’开头的都在同一个 region, 它们的 rowkey 像下面这样:

foo0001
foo0002
foo0003
foo0004
现在,假如你需要将上面这个 region 分散到 4个 region。你可以用4个不同的盐:’a’, ‘b’, ‘c’, ‘d’. 在这个方案下,每一个字母前缀都会在不同的 region 中。加盐之后,你有了下面的 rowkey:

a-foo0003
b-foo0001
c-foo0004
d-foo0002
所以,你可以向4个不同的 region 写,理论上说,如果所有人都向同一个region 写的话,你将拥 有之前4倍的吞吐量。

现在,如果再增加一行,它将随机分配a,b,c,d中的一个作为前缀,并以一个现有行作为尾部结束:

a-foo0003
b-foo0001
c-foo0003
c-foo0004
d-foo0002
因为分配是随机的,所以如果你想要以字典序取回数据,你需要做更多工作。加盐这种方式增加了写时的吞吐量,但是当读时有了额外代价。

哈希
哈希会使同一行永远用同一个前缀加盐。哈希也可以使负载分散到整个集群,但是读却是可以预测的。使用确定的哈希可以让客户端重构完成的rowkey,使用Get操作获取正常的获取某一行数据。

翻转
第三种防止热点的方法时反转固定长度或者数字格式的rowkey。这样可以使得rowkey中经常改变 的部分(最没有意义的部分)放在前面。这样可以有效的随机rowkey,但是牺牲了rowkey的有序性。

反转rowkey的例子
以手机号为rowkey,可以将手机号反转后的字符串作为rowkey,这样就避免了以手机号那样比较固定开头导致热点问题

<think>好的,用户想了解HBase查询中的热点问题及解决方案。首先,我需要回忆一下HBase热点问题是怎么回事。热点通常发生在某个Region Server处理大量请求,而其他节点负载较低,导致性能瓶颈。用户提到的引用里也提到了数据倾斜和Rowkey设计的问题。 用户的问题主要集中在查询时的热点,所以得先分析查询热点和写入热点的区别。查询热点可能是因为Rowkey设计不合理,导致某些Region被频繁访问。例如,如果Rowkey是顺序递增的,比如时间戳,那么最新的数据可能集中在某个Region,导致查询压力大。 解决方案方面,引用里提到了预分区、Rowkey散列化、加盐等方法。需要详细说明每种方法如何实施。比如,预分区可以在建表时划分多个Region,避免初始时只有一个Region。Rowkey设计部分可能需要举例,比如反转时间戳或者组合字段,确保分布均匀。 还要考虑列族和列名的优化,因为过大的列名会增加存储和传输开销,进而影响索引大小,引用2提到了这点。可能需要建议用户缩短列名或使用编码。 另外,用户可能没有意识到热点不仅出现在写入时,查询时同样会有。比如,频繁查询某个特定范围的Rowkey,导致某个Region负载过高。这时候可能需要结合业务逻辑调整查询模式,或者使用缓存机制。 需要检查是否有遗漏的解决方案,比如负载均衡机制是否开启,或者手动拆分Region。引用3中提到了默认情况下的Region行为,所以预分区和避免单调递增Rowkey是关键。 最后,确保回答结构清晰,分点说明问题和解决方案,并附上相关问题供用户进一步学习。可能还要提醒用户根据实际业务场景选择合适的方法,比如加盐可能会影响范围查询,需要权衡。</think>### HBase查询热点问题及解决方案 #### 一、查询热点问题表现 当HBase表出现查询热点时,**特定Region的请求量显著高于其他Region**,通常伴随以下现象: 1. 单个Region Server的读写请求数比其他节点高2-10倍[^1] 2. UI监控显示某Region的请求量比其他Region高1-2个数量级[^1] 3. 客户端出现频繁的读超时或响应延迟 #### 二、核心原因分析 1. **Rowkey设计缺陷** - 单调递增Rowkey(如时间戳)导致新数据集中写入/查询同一Region[^3] - 固定前缀造成数据局部聚集(例如`user_1000_`开头的Rowkey集中存储) 2. **数据分布不均** - 未预分区的表初始只有一个Region,所有查询集中在单点[^3] - 未合理使用散列算法导致数据倾斜 3. **查询模式问题** - 高频访问特定业务数据(如热门商品ID) - 范围查询未优化导致扫描大量连续Rowkey #### 三、针对性解决方案 ##### 1. Rowkey优化(关键措施) | 方法 | 实现 | 适用场景 | 注意事项 | |------|------|----------|----------| | **散列化** | `MD5(原Rowkey).substr(0,4)+原Rowkey` | 均匀分布需求 | 牺牲范围查询能力 | | **反转键** | `reverse(timestamp)+业务ID` | 时间序列数据 | 需配合前缀过滤 | | **组合键** | `业务字段1_业务字段2_散列值` | 多维度查询 | 需明确查询模式 | | **加盐值** | `(hash(业务字段)%region数)_原Rowkey` | 强制均匀分布 | 增加计算开销 | > 示例:对时间戳`20230801120000`优化为`00_200001108322`(反转+加盐) ##### 2. 预分区策略 ```java // 创建表时指定分区边界 byte[][] splits = new byte[][]{ Bytes.toBytes("A|"), Bytes.toBytes("M|"), Bytes.toBytes("S|") }; admin.createTable(tableDesc, splits); ``` - 建议根据**业务查询模式**设计分区 - 分区数可按`RegionServer数量 * 2`作为基准[^3] ##### 3. 查询模式优化 - 避免全表扫描:设置合理的`startRow`和`stopRow` - 使用协处理器将热点查询逻辑下沉到RegionServer - 对高频查询结果启用BlockCache(调整`hbase.blockcache.size`) ##### 4. 系统级调优 - 开启自动负载均衡:`balance_switch true` - 调整Region大小:`hbase.hregion.max.filesize=30GB`(默认10GB) - 启用压缩:`COMPRESSION => 'SNAPPY'`减少IO压力[^2] #### 四、诊断工具链 1. **HBase Shell** ```shell hbase> status 'detailed' # 查看Region分布 hbase> scan 'hbase:meta' # 检查Region边界 ``` 2. **HBase UI** - Regionserver的请求量监控 - Region级别的读写统计 3. **日志分析** - 关注`RegionTooBusyException` - 跟踪慢查询日志中的Rowkey模式 #### 五、典型场景解决方案 **场景:电商订单查询热点** - 问题:按订单创建时间查询产生时间范围热点 - 优化方案: 1. Rowkey设计:`reverse(timestamp)_userid_md5(orderid)` 2. 预分区按小时划分 3. 建立反向索引表用于用户维查询
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值