目录
HBase 优化
RowKey 设计
一条数据的唯一标识就是 rowkey
,那么这条数据存储于哪个分区,取决于 rowkey
处于哪个预分区的区间内。设计 rowkey
的主要目的,就是在一定程度上防止数据倾斜,使数据均匀地分布于所有的 region
中。接下来我们将讨论 rowkey
常用的设计方案。
- 生成随机数、hash、散列值
- 时间戳反转
- 字符串拼接
实现需求 1
为了能够统计张三在 2021 年 12 月份消费的总金额,我们需要用 scan
命令能够得到张三在这个月消费的所有记录,之后再进行累加即可。Scan
需要填写 startRow
和 stopRow
:
startRow -> ^A^Azhangsan2021-12
endRow -> ^A^Azhangsan2021-12.
注意点:
- 避免扫描数据混乱,解决字段长度不一致的问题,可以使用相同 ASCII 值的符号进行填充,框架底层填充使用的是 ASCII 值为 1 的
^A
。 - 最后的日期结尾处需要使用 ASCII 略大于
-
的值。
最终得到 rowKey
的设计为:
rowKey: userdate(yyyy-MM-dd HH:mm:SS)
实现需求 2
按照需求 1 的 rowKey
设计,会发现对于需求 2,完全没有办法写出 rowKey
的扫描范围。此处可以看出 HBase 设计 rowKey
使用的特点为:适用性强但泛用性差,能够完美实现一个需求但不能同时完美实现多个需求。
如果想要同时完成两个需求,需要对 rowKey
出现字段的顺序进行调整。调整的原则为:可枚举的放在前面。其中时间是可以枚举的,用户名称无法枚举,所以必须把时间放在前面。
最终满足两个需求的设计如下:
rowKey 设计格式 => date(yyyy-MM)^A^Auserdate(-dd hh:mm:ss ms)
实现需求 1:
scan: startRow => 2021-12^A^Azhangsan
stopRow => 2021-12^A^Azhangsan.
实现需求 2:
scan: startRow => 2021-12
stopRow => 2021-12.
添加预分区优化
预分区的分区号同样需要遵守 rowKey
的 scan
原则。所有必须添加在 rowKey
的最前面,前缀为最简单的数字。同时使用 hash 算法将用户名和月份拼接决定分区号。(单独使用用户名会造成单一用户所有数据存储在一个分区)
添加预分区优化:
分区号 => hash(user+date(MM)) % 120
分区号填充 如果得到 1 => 001
rowKey 设计格式:
分区号 date(yyyy-MM)^A^Auserdate(-dd hh:mm:ss ms)
解决方法:
提前将月份和分区号对应如下:
000 到 009 分区 存储的都是 1 月份数据
010 到 019 分区 存储的都是 2 月份数据
...
110 到 119 分区 存储的都是 12 月份数据
分区号 => hash(user+date(MM)) % 10 + 80
分区号填充 如果得到 85 => 085
得到 12 月份所有人的数据:
扫描 10 次
startRow => 1102021-12
stopRow => 1102021-12.
...
startRow => 1192021-12
stopRow => 1192021-12.
参数优化
- Zookeeper 会话超时时间
- 属性:
zookeeper.session.timeout
- 解释:默认值为 90000 毫秒(90s)。当某个 RegionServer 挂掉,90s 之后 Master 才能察觉到。可适当减小此值,尽可能快地检测 regionserver 故障,可调整至 20-30s。
- 可以调整重试时间和重试次数:
hbase.client.pause
(默认值 100ms);hbase.client.retries.number
(默认 15 次)
- 属性:
- 设置 RPC 监听数量
- 属性:
hbase.regionserver.handler.count
- 解释:默认值为 30,用于指定 RPC 监听的数量,可以根据客户端的请求数进行调整,读写请求较多时,增加此值。
- 属性:
- 手动控制 Major Compaction
- 属性:
hbase.hregion.majorcompaction
- 解释:默认值:604800000 秒(7 天),Major Compaction 的周期,若关闭自动 Major Compaction,可将其设为 0。如果关闭一定记得自己手动合并,因为大合并非常有意义。
- 属性:
- 优化 HStore 文件大小
- 属性:
hbase.hregion.max.filesize
- 解释:默认值 10737418240(10GB),如果需要运行 HBase 的 MR 任务,可以减小此值,因为一个 region 对应一个 map 任务,如果单个 region 过大,会导致 map 任务执行时间过长。该值的意思就是,如果 HFile 的大小达到这个数值,则这个 region 会被切分为两个 Hfile。
- 属性:
- 优化 HBase 客户端缓存
- 属性:
hbase.client.write.buffer
- 解释:默认值 2097152bytes(2M)用于指定 HBase 客户端缓存,增大该值可以减少 RPC 调用次数,但是会消耗更多内存,反之则反之。一般我们需要设定一定的缓存大小,以达到减少 RPC 次数的目的。
- 属性:
- 指定 scan.next 扫描 HBase 所获取的行数
- 属性:
hbase.client.scanner.caching
- 解释:用于指定
scan.next
方法获取的默认行数,值越大,消耗内存越大。
- 属性:
- BlockCache 占用 RegionServer 堆内存的比例
- 属性:
hfile.block.cache.size
- 解释:默认 0.4,读请求比较多的情况下,可适当调大。
- 属性:
- MemStore 占用 RegionServer 堆内存的比例
- 属性:
hbase.regionserver.global.memstore.size
- 解释:默认 0.4,写请求较多的情况下,可适当调大。
- 属性:
Lars Hofhansl(拉斯·霍夫汉斯)大神推荐 Region 设置 20G,刷写大小设置 128M,其它默认。
JVM 调优
JVM 调优的思路有两部分:一是内存设置,二是垃圾回收器设置。
- 设置使用 CMS 收集器:
-XX:+UseConcMarkSweepGC
- 保持新生代尽量小,同时尽早开启 GC,例如:
-XX:CMSInitiatingOccupancyFraction=70
-XX:+UseCMSInitiatingOccupancyOnly
-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 (新生代经过垃圾回收之后存活的对象)
HBase 使用经验法则
官方给出了权威的使用法则:
- Region 大小控制 10-50G
- Cell 大小不超过 10M(性能对应小于 100K 的值有优化),如果使用 MOB(Medium sized Objects 一种特殊用法)则不超过 50M。
- 一张表有 1 到 3 个列族,不要设计太多。最好就 1 个,如果使用多个尽量保证不会同时读取多个列族。
- 1 到 2 个列族的表格,设计 50-100 个 Region。
- 列族名称要尽量短,不要去模仿 RDBMS(关系型数据库)具有准确的名称和描述。
- 如果 RowKey 设计时间在最前面,会导致有大量的旧数据存储在不活跃的 Region 中,使用的时候,仅仅会操作少数的活动 Region,此时建议增加更多的 Region 个数。
- 如果只有一个列族用于写入数据,分配内存资源的时候可以做出调整,即写缓存不会占用太多的内存。