HBase - 性能调优策略汇总(核心参数优化、预分区、RowKey设计原则、批量处理)
作者:hangge | 2024-11-26 08:33
一、HBase 核心参数优化
1,核心参数说明
(1)hbase.hregion.majorcompaction
- 配置大合并的间隔时间,默认为 604800000 毫秒(7 天),可设置为 0,禁止自动的大合并,大合并的执行可能会持续数小时,为减少对业务的影响,建议在业务低峰期进行手动或者通过脚本或者 API 定期进行大合并。
(2)hbase.hregion.max.filesize
- 默认为 10737418240 Byte(10G),当 Region 达到这个阈值时,会自动分裂。Region 分裂会有短暂的 Region 下线时间(通常在 5s 以内),为减少对业务端的影响,建议调大该值,并在业务低峰期定时手动进行分裂。
(3)hbase.regionserver.handler.count
- 默认 30,对于大负载的 Put(达到了 M 范围)或是大范围的 Scan 操作,handler 数目不易过大,易造成 OOM(内存溢出)。 对于小负载的 put、get,delete 等操作,handler 数要适当调大。handler 属于一个处理器,实现底层数据的发送。
(4)hbase.hregion.memstore.flush.size
- 默认值 134217728 Byte (128M),单位字节,这个参数是 Memstore 中数据持久化到 Storefile 的时机,超过该阈值,则会把 Memstore 中的数据持久化到 Storefile 中,如果 Regionserver 的 JVM 内存比较充足(例如:16G 以上),可以适当调大该值,例如:调整为 256M。这样可以减少 Memstore 中数据溢写文件的次数。
(5)hbase.hregion.memstore.block.multiplier
- 默认值 4,如果一个 Memstore 的内存大小已经超过 hbase.hregion.memstore.flush.size * hbase.hregion.memstore.block.multiplier,则会阻塞该 Memstore 的写操作,为避免阻塞,可以适当调大,例如 6~8,但如果太大,则会有 OOM 的风险。 如果在 Regionserver 日志中出现“Blocking updates for ‘’ on region : memstore size <多少 M> is >= than blocking <多少 M> size”的信息时,说明这个值该调整了。
(6)hbase.hstore.compaction.min
- 默认值为 3,如果任何一个 Store 里的 Storefile 总数超过该值,会触发默认的合并操作,可以设置 5~8,在手动的定期大合并中进行 Storefile 文件的合并,减少合并的次数,不过这会延长合并的时间。
2,如何修改参数
如果想要修改这些参数的话需要在 hbase-site.xml 中进行修改。
二、预分区
1,什么是预分区?
(1)预分区是指在创建 HBase 表时,预先将表的 Region(区域)进行划分,而不是等待 HBase 自动进行拆分。
(2)通过预分区,可以避免数据集中写入单个 Region 导致的负载不均和性能瓶颈。
2,为什么要预分区?
(1)HBase 默认新建的表中只有一个 Region,这个 Region 的 Rowkey 是没有边界的,即没有 startRowkey 和 endRowkey,在数据写入时,所有数据都会写入这个默认的 Region。
- 比如我们使用如下命令创建 batch1 表后可以看到表中只有一个分区:
create 'batch1', 'c1'
(2)随着数据量的不断增加,此 Region 已经不能承受不断增长的数据量,会进行 Split,分裂成 2 个 Region。在这个过程中,会产生两个问题:
- 数据往一个 Region 上写,会有写热点问题。
- Region split 会消耗宝贵的集群 IO 资源。
(3)基于此我们可以控制在建表的时候,创建多个空 Region,并确定每个 Region 的起始和终止 Rowkey,这样只要我们设计的 Rowkey 能均匀的命中各个 Region,就不会存在写热点问题。Region 分裂的几率也会大大降低。当然随着数据量的不断增长,该分裂还是要进行分裂的。
3,如何进行预分区?
(1)比如我们的 RowKey 是用户 ID,可以根据用户 ID 的范围进行分区。假设用户 ID 从 1 到 6000 的范围,那么我们可以使用如下命令建表时进行预分区:
create 'batch2', 'c1', SPLITS => ['1000', '2000', '3000', '4000', '5000']
(2)可以看到该表一共有 6 个分区,且每个分区都有相应的 Start Key 和 End Key:
三、Region 的 Request 计数
1,Requests 参数值介绍
HBase UI 界面(http://服务器 IP:16010)table Regions 中的 Requests 参数值这个参数的意义在于,可以分析哪个 Region 被频繁请求,是否存在读写热点的问题。
注意:HBase 集群重启之后,Requests 参数值会被清空。
2,查看方法
(1)我们在 HBase UI 界面点击需要查看的表:
(2)可以看到该表各个 Region 的读、写请求次数:
四、RowKey 的设计原则
1,Rowkey 长度原则
(1)Rowkey 底层存储是一个二进制流,可以是任意字符串,最大长度 64kb ,实际应用中一般是 10-100 字节,以 byte[] 形式保存,一般设计为定长。
(2)Rowkey 长度建议越短越好,不要超过 16 个字节,原因如下:
- 数据的持久化文件 HFile 中是按照 KeyValue 存储的,如果 Rowkey 过长,比如超过 100 字节,1000w 行数据,Rowkey 就要占用 100*1000w=10 亿字节,将近 1G 数据,这样会极大影响 HFile 的存储效率;
- MemStore 会缓存部分数据到内存,如果 Rowkey 字段过长,内存的有效利用率就会降低,系统不能缓存更多的数据,这样会降低检索效率。
- 目前操作系统都是 64 位系统,内存 8 字节对齐,控制在 16 个字节,8 字节的整数倍利用了操作系统的最佳特性。
2,Rowkey 散列原则
(1)Rowkey 散列原则,主要是为了避免数据热点问题。
- 虽然我们可以在建表的时候提前设计预分区,但是假设数据的 Rowkey 都是手机号,那么都是 1 开头,按照前面的设计,那么所有的数据都会写到 10-20 之间的 Region 中,仍然没有做到负载均衡。
(2)为了保证数据能够均匀的分布到预先设计好的分区中,我们的解决思路(以手机号为例)有如下两种:
- 方法 1:手机号反转,将手机号的最后一位前置,这样第一位就是 0-9 之间的任意一个数字了。
- 方法 2:按照一定规则使用 hashCode 获取余数,拼在手机号前面。例如:根据手机号后四位使用 hashCode 获取余数。这里的规则一定要是可以反推出来的,这样后期还可以根据这个规则找到对应的手机号,尽量不要使用随机数。
3,Rowkey 唯一性原则
(1)必须在设计上保证其唯一性,因为 Rowkey 相同则会覆盖
(2)Rowkey 是 HBase 里面唯一的索引,对于某些查询频繁的限定条件可以把它的内容存放在 Rowkey 里面,提高查询效率。
- 例如:需要经常使用姓名和年龄这两个字段进行查询,那么可以考虑把姓名和年龄拼接到一块作为 Rowkey。
五、列族的设计原则
(1)在设计列族的时候,建议把经常读取的字段存储到一个列族中,不经常读取的字段放到另一个列族中。
(2)这样在读取部分数据的时候,就只需要读取一个列族文件即可,可以提高读取效率。
六、批量处理
(1)Table.get(List)
- Table.get(Get)方法可以根据一个指定的 Rowkey 获取一行记录。
- 同样 HBase 提供了另一个方法:通过调用 Table.get(List)方法可以根据一个指定的 Rowkey 列表,批量获取多行记录,这样做的好处是批量执行,只需要一次网络 IO 开销,这样可以带来明显的性能提升。
(2)同理 Table.delete(List) 和 Table.put(List)
- 如果一次操作的数据量不是特别多,例如:100~1000 条左右的数据量,可以考虑这种方式。
- 如果是一次需要批量操作上千万的数据,建议使用前面讲的批量导入导出方法(使用MapReduce、使用BulkLoad),效率更高。
全部评论(0)