HBase优化
1. RowKey设计
一条数据的唯一标识就是rowkey,那么这条数据存储于哪个分区,取决于rowkey处于哪个一个预分区的区间内,设计rowkey的主要目的,就是让数据均匀的分布于所有的region中,在一定程度上防止数据倾斜。接下来我们就谈一谈 rowkey常用的设计思路:
- 生成随机数、hash、散列值
- 时间戳反转(指的是用一个很大的数减去时间戳)
- 字符串拼接
Hbase的rowKey使用的特点为:针对强、泛用性差、能够完美实现一个需求,但是不能同时完美实现多个需要。
1.1 案例实操
要求设计合理的Rowkey,方便查询出下面2个问题的数据。
1.2 实现需求1
为了能够统计张三在2021年12月份消费的总金额,我们需要用scan命令能够得到张三在这个月消费的所有记录,之后在进行累加即可。
设计的RowKey为:^A^A名字date(yyyy-MM-dd HH:mm:ss) Scan需要填写startRow和stopRow:
scan: startRow -> ^A^Azhangsan2021-12
endRow -> ^A^Azhangsan2021-12.
使用^A
的好处是会填充名字使得名字长度固定,避免扫描数据混乱,解决字段长度不一致的问题(比如11,2,1由于不定长,扫描1会把11也扫描出来)。使用.
的意思是因为scan按照startRow\endRow去找的时候,底层会将startRow\endRow转换成acsii码,我们要查找2021-12有字符-
(acsii码为45)的都满足,只要比acsii码45大就行,acsii码46就是.
1.3 实现需求2
问题提出:按照问题1的rowKey设计,会发现对于需求2,完全没有办法写rowKey的扫描范围。
如果想要同时完成两个需求,需要对rowKey出现字段的顺序进行调整。调整的原则为:可枚举的放在前面。其中时间是可以枚举的,用户名称无法枚举,所以必须把时间放在前面。
最终满足2个需求的rowKey设计: date(yyyy-MM)^A^Auserdate(-dd hh:mm:ss ms)
## 统计张三在 2021 年 12 月份消费的总金额
scan: startRow => 2021-12^A^Azhangsan
stopRow => 2021-12^A^Azhangsan.
## 统计所有人在 2021 年 12 月份消费的总金额
scan: startRow => 2021-12
stopRow => 2021-12.
1.4 添加预分区优化
预分区的分区号同样需要遵守rowKey的scan原则。所有必须添加在rowKey的最前面,前缀为最简单的数字。同时使用hash算法将用户名和月份拼接决定分区号。(单独使用用户名会造成单一用户所有数据存储在一个分区)
添加预分区优化
startKey stopKey
001
001 002
002 003
...
119 120
分区号=> hash(user+date(MM)) % 120
分区号填充 如果得到 1 => 001
rowKey 设计格式 => 分区号 date(yyyy-MM)^A^Auserdate(-dd hh:mm:ss ms)
缺点:实现需求2的时候,由于每个分区都有12月份的数据,需要扫描120个分区。
解决方法:提前将分区号和月份进行对应。
000 到 009 分区 存储的都是 1 月份数据
010 到 019 分区 存储的都是 2 月份数据
...
110 到 119 分区 存储的都是 12 月份数据
是 9 月份的数据
分区号=> hash(user+date(MM)) % 10 + 80
分区号填充 如果得到 85 => 085
得到 12 月份所有人的数据
扫描 10 次
scan: startRow => 1102021-12
stopRow => 1102021-12.
...
startRow => 1122021-12
stopRow => 1122021-12.
..
startRow => 1192021-12
stopRow => 1192021-12.
2. 参数优化
2.1 Zookeeper会话超时时间
修改hbase-site.xml的zookeeper.session.timeout属性,用来当某个RegionServer挂掉,90s之后Master 才 能察觉到。可适当减小此值,尽可能快地检测regionserver故障,可调整至20-30s。看你能有都能忍耐超时,同时可以调整重试时间和重试次数
<!-- 默认值为 90000 毫秒 -->
<property>
<name>zookeeper.session.timeout</name>
<value>30000</value>
</property>
解释:默认值为 90000 毫秒(90s)。
<!-- 调整重试间隔时间 默认 100ms-->
<property>
<name>hbase.client.pause</name>
<value>500</value>
</property>
(默认值 100ms)
<!-- 重试次数 (默认 15 次)-->
<property>
<name>hbase.client.retries.number</name>
<value>10</value>
</property>
2.2 设置RPC监听数量
<!-- 默认值为30,用于指定RPC监听的数量,可以根据客户端的请求数进行调整,读写请求较多时,增加此值。-->
<property>
<name>hbase.regionserver.handler.count</name>
<value>500</value>
</property>
2.3 手动控制Major Compaction
<!-- 默认值:604800000 秒(7 天), Major Compaction的周期,若关闭自动Major Compaction,可将其设为 0。如果关闭一定记得自己手动合并,因为大合并非常有意义 -->
<property>
<name>hbase.hregion.majorcompaction</name>
<value>0</value>
</property>
2.4 优化HStore文件大小
<!-- 默认值10737418240(10GB),如果需要运行HBase的MR任务,可以减小此值,
因为一个region对应一个map任务,如果单个region过大,会导致map任务执行时间过长。该值的意思就是,如果 HFile的大小达到这个数值,则这个region会被切分为两个Hfile。-->
<property>
<name>hbase.hregion.max.filesize</name>
<value>10</value>
</property>
2.5 优化HBase客户端缓存
<!-- 默认值 2097152bytes(2M)用于指定HBase客户端网络通信缓存,增大该值可以减少RPC调用次数,但是会消耗更多内存,反之则反之。一般我们需要设定一定的缓存大小,以达到减少RPC次数的目的。-->
<property>
<name>hbase.client.write.buffer</name>
<value>2097152</value>
</property>
2.6 指定scan.next获取的行数
<!-- 用于指定scan.next方法获取的默认行数,值越大,消耗内存越大。默认Integer.MaxValue -->
<property>
<name>hbase.client.scanner.caching</name>
<value>100000</value>
</property>
2.7 BlockCache占用RegionServer堆内存的比例
<!-- 默认 0.4,读请求比较多的情况下,可适当调大 -->
<property>
<name>hfile.block.cache.size</name>
<value>0.4</value>
</property>
2.8 MemStore占用RegionServer堆内存的比例
<!-- 默认 0.4,写请求较多的情况下,可适当调大 -->
<property>
<name>hbase.regionserver.global.memstore.size</name>
<value>0.4</value>
</property>
Lars Hofhansl(拉斯·霍夫汉斯)大神推荐 Region设置20G(设置大的话避免Hbase自动进行系统预分区,使得我们自己设置的分区不受到干扰),刷写大小设置128M,其它默认。
3. JVM调优
JVM调优的思路有两部分:一是内存设置(这个看服务器配置),二是垃圾回收器设置。
垃圾回收的修改是使用并发垃圾回收,默认PO+PS是并行垃圾回收,会有大量的暂停。 理由是HBsae大量使用内存用于存储数据,容易遭遇数据洪峰造成OOM,同时写缓存的数据是不能垃圾回收的,主要回收的就是读缓存,而读缓存垃圾回收不影响性能,所以最终设置的效果可以总结为:防患于未然,早洗早轻松。
3.1 设置使用CMS收集器
-XX:+UseConcMarkSweepGC
3.2 保持新生代尽量小,同时尽早开启GC
//在内存占用到 70%的时候开启 GC
-XX:CMSInitiatingOccupancyFraction=70
//指定使用 70%,不让 JVM 动态调整
-XX:+UseCMSInitiatingOccupancyOnly
//新生代内存设置为 512m
-Xmn512m
//并行执行新生代垃圾回收
-XX:+UseParNewGC
// 设置scanner扫描结果占用内存大小 , 在hbase-site.xml 中,设置
hbase.client.scanner.max.result.size(默认值为 2M)为eden空间的1/8大概在64M)
// 设置多个与max.result.size * handler.count相乘的结果 < Survivor Space(新生代经过垃圾回收之后存活的对象)
4. HBase使用经验法则
官方给出了权威的使用法则:
(1)Region大小控制10-50G。
(2)cell大小(就是插入的一次数据量)不超过10M(性能对应小于100K的值有优化),如果使用mob(Medium-sized Objects一种特殊用法)则不超过50M。
(3)1张表有1到3个列族,不要设计太多。最好就1个,如果使用多个尽量保证不会同时读取多个列族。
(4)1到2个列族的表格,设计50-100个Region。
(5)列族名称要尽量短,不要去模仿 RDBMS(关系型数据库)具有准确的名称和描述。
(6)如果RowKey设计时间在最前面,会导致有大量的旧数据存储在不活跃的Region中,使用的时候,仅仅会操作少数的活动Region,此时建议增加更多的Region个数。
(7)如果只有一个列族用于写入数据,分配内存资源的时候可以做出调整,即写缓存不会占用太多的内存。