文章目录
问题
刚开始用 HBase 的时候如果数据量比较大,经常会遇到下面这个异常:org.apache.hadoop.hbase.RegionTooBusyException: over memstore limit = 512.0m
org.apache.hadoop.hbase.RegionTooBusyException: org.apache.hadoop.hbase.RegionTooBusyException: over memstore limit = 512.0m
原因定位
HBase 进行 put 操作的时候会进行 resource 检查,当 Region 中所有 MemStore 的大小总和达到了上限,
hbase.hregion.memstore.block.multiplier * hbase.hregion.memstore.flush.size
默认 4* 128M = 512M,会阻塞该 Region 所有请求并触发 memstore 刷写。
<property>
<name>hbase.hregion.memstore.flush.size</name>
<value>134217728</value>
</property>
<property>
<name>hbase.hregion.memstore.block.multiplier</name>
<value>4</value>
</property>
当一个 Region 里面的 MemStore 超过上述限制时,也会阻塞该 Region 的所有请求,进行 flush,释放内存。
产生异常的原因
直白一些说就是数据写入太快了,Region 中的 MemStore 还没有把数据 flush 出来,内存还未释放,此时一大堆请求过来了,region 将拒绝所有写请求,所以客户端抛出 RegionTooBusyException,并在一定时间后重试。
解决方案(供参考)
降低写入批次大小
如果是写入数据量不是太多使用 jdbc 方式来操作 phonenix 的写入,那需要评估一下 HBase 的处理速度(和集群性能有关)和 put 速度,适当降低写入的批次大小。
提高 MemStore 内存(谨慎使用)
在 HBase 内存允许的前提下,提高 hbase.hregion.memstore.block.multiplier
参数,在 flush 阻塞的这段时间,允许更多的数据写入到 MemStore 中。
风险:
增加了 RegionServer OOM 概率,修改此参数,需要进行大数据量写入压测。
减少阻塞时间
减少阻塞时间 hbase.hstore.blockingwaittime
(比如到30s),加快 MemStore flush 到 HDFS 上的速度,从而减少 MemStore 数据大小触碰到上限的时间,也就能够减少拒绝写入请求的时间。
风险:
1、增加了 compaction 的压力
2、占用磁盘 IO,可能会影响到其他服务
bulkload 导入
一般出现 RegionTooBusyException,表示写入数据量比较大,这种场景更好的选择可能是采用 bulkload 导入方式,前提是这种方式刚好符合你的应用场景。
数据热点(数据倾斜)
数据热点(数据倾斜)也会导致上述异常。
HBase 的 rowkey 设计原理
Rowkey设计时需要遵循三大原则:
1)唯一性原则
rowkey在设计上保证其唯一性。rowkey是按照字典顺序排序存储的,因此,设计rowkey的时候,要充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问的数据放到一块。
2)长度原则
rowkey是一个二进制码流,可以是任意字符串,最大长度 64kb ,实际应用中一般为10-100bytes,以byte[] 形式保存,一般设计成定长。建议越短越好,不要超过16个字节,原因如下:数据的持久化文件HFile中是按照KeyValue存储的,如果rowkey过长,比如超过100字节,1000w行数据,光rowkey就要占用100*1000w=10亿个字节,将近1G数据,这样会极大影响HFile的存储效率;MemStore将缓存部分数据到内存,如果rowkey字段过长,内存的有效利用率就会降低,系统不能缓存更多的数据,这样会降低检索效率。目前操作系统都是64位系统,内存8字节对齐,控制在16个字节,8字节的整数倍利用了操作系统的最佳特性。
3)散列原则
如果rowkey按照时间戳的方式递增,不要将时间放在二进制码的前面,建议将rowkey的高位作为散列字段,由程序随机生成,低位放时间字段,这样将提高数据均衡分布在每个RegionServer,以实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息,所有的数据都会集中在一个RegionServer上,这样在数据检索的时候负载会集中在个别的RegionServer上,造成热点问题,会降低查询效率。
HBase 的 rowkey 生成策略
加盐
如果rowkey按照时间戳的方式递增,不要将时间放在二进制码的前面,建议将rowkey的高位作为散列字段,由程序随机生成,低位放时间字段,这样将提高数据均衡分布在每个RegionServer,以实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息,所有的数据都会集中在一个RegionServer上,这样在数据检索的时候负载会集中在个别的RegionServer上,造成热点问题,会降低查询效率加盐:这里所说的加盐不是密码学中的加盐,而是在rowkey的前面增加随机数,具体就是给rowkey分配一个随机前缀以使得它和之前的rowkey的开头不同。分配的前缀种类数量应该和你想使用数据分散到不同的region的数量一致。加盐之后的rowkey就会根据随机生成的前缀分散到各个region上,以避免热点
哈希
哈希会使同一行永远用一个前缀加盐。哈希也可以使负载分散到整个集群,但是读却是可以预测的。使用确定的哈希可以让客户端重构完整的rowkey,可以使用get操作准确获取某一个行数据
反转
第三种防止热点的方法时反转固定长度或者数字格式的rowkey。这样可以使得rowkey中经常改变的部分(最没有意义的部分)放在前面。这样可以有效的随机rowkey,但是牺牲了rowkey的有序性。反转rowkey的例子以手机号为rowkey,可以将手机号反转后的字符串作为rowkey,这样的就避免了以手机号那样比较固定开头导致热点问题
时间戳反转
一个常见的数据处理问题是快速获取数据的最近版本,使用反转的时间戳作为rowkey的一部分对这个问题十分有用,可以用Long.Max_Value - timestamp 追加到key的末尾,例如[key][reverse_timestamp] ,[key] 的最新值可以通过scan [key]获得[key]的第一条记录,因为HBase中rowkey是有序的,第一条记录是最后录入的数据。比如需要保存一个用户的操作记录,按照操作时间倒序排序,在设计rowkey的时候,可以这样设计[userId反转][Long.Max_Value - timestamp],在查询用户的所有操作记录数据的时候,直接指定反转后的userId,startRow是[userId反转][000000000000],stopRow是[userId反转][Long.Max_Value - timestamp]如果需要查询某段时间的操作记录,startRow是[user反转][Long.Max_Value - 起始时间],stopRow是[userId反转][Long.Max_Value - 结束时间]