Hbase热点问题、数据倾斜和rowkey的散列设计

1.什么是热点和数据倾斜

  热点发生在大量的client直接访问集群的一个或极少数个节点(访问可能是读,写或者其他操作)。大量访问会使热点region所在的单个机器超出自身承受能力,引起性能下降甚至region不可用,这也会影响同一个RegionServer上的其他region,由于主机无法服务其他region的请求,造成资源浪费。设计良好的数据访问模式以使集群被充分,均衡的利用。 
  数据倾斜:HBase可以被划分为多个Region,但是默认创建时只有一个Region分布在集群的一个节点上,数据一开始时都集中在这个Region,也就是集中在这一个节点上,就算region存储达到临界值时被划分,数据也是存储在少数节点上。这就是数据倾斜。

2.hbase的存储方式引起的热点问题和数据倾斜

  HBase中的行是按照rowkey的字典顺序排序的,这种设计优化了scan操作,可以将相关的行以及会被一起读取的行存取在临近位置,便于scan。
  rowkey设计是热点的源头。
  HBase中,表会被划分为1...n个Region,被托管在RegionServer中。Region有二个重要的属性:StartKey与EndKey表示这个Region维护的rowKey范围,当我们要读/写数据时,如果rowKey落在某个start-end key范围内,那么就会定位到目标region并且读/写到相关的数据。
  默认的情况下,创建一张表是,只有1个region,start-end key没有边界,所有数据都在这个region里装,然而,当数据越来越多,region的size越来越大时,大到一定的阀值,hbase认为再往这个region里塞数据已经不合适了,就会找到一个midKey将region一分为二,成为2个region,这个过程称为分裂(region-split)。而midKey则为这二个region的临界(这个中间值这里不作讨论是如何被选取的)。
  此时,我们假设假设rowkey小于midKey则为阴被塞到1区,大于等于midKey则会被塞到2区,如果rowkey还是顺序增大的,那数据就总会往2区里面写数据,而1区现在处于一个被冷落的状态,而且是半满的。2区的数据满了会被再次分裂成2个区,如此不断产生被冷落而且不满的Region,当然,这些region有提供数据查询的功能。
  这种设计是分布式系统一个很大的弊端,而且这样导致数据倾斜和热点问题,从而导致集群的资源得不到很好的利用。

3.预分区和rowkey的散列设计——解决数据倾斜和热点问题

预分区

预分区,让表的数据可以均衡的分散在集群中,而不是默认只有一个region分布在集群的一个节点上。(预分区个数=节点的倍数,看数据量估算,region不足了会被分列,预分区后每个region的rowkey还是有序的)
一个RegionServer能管理10-1000个Region,0.92.x版本后,默认的Region大小为10G,向下可以支持256MB,向上可以支持到20G,也就是说,每个RegionServer能管理的数据量为2.5GB-20TB。
如果有5个节点,3年内数据量为5T,那么分区数可以预设为:
5000G/10G=500个region这500个Region就会被均衡的分布在集群各个节点上(具体分布看机器的性能和存储空间而定),机器硬盘不足可以添加硬盘,性能不足可以添加新节点(添加新机器)。

加盐

这里所说的加盐不是密码学中的加盐,而是在rowkey的前面增加随机数,具体就是给rowkey分配一个随机前缀以使得它和之前的rowkey的开头不同。给多少个前缀?这个数量应该和我们想要分散数据到不同的region的数量一致(类似hive里面的分桶)。加盐之后的rowkey就会根据随机生成的前缀分散到各个region上,以避免热点。

哈希

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

反转

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

反转rowkey的例子:以手机号为rowkey,可以将手机号反转后的字符串作为rowkey,从而避免诸如139、158之类的固定号码开头导致的热点问题。

时间戳反转

一个常见的数据处理问题是快速获取数据的最近版本,使用反转的时间戳作为rowkey的一部分对这个问题十分有用,可以用Long.Max_Value - timestamp追加到key的末尾,例如[key][reverse_timestamp] ,[key] 的最新值可以通过scan [key]获得[key]的第一条记录,因为HBase中rowkey是有序的,第一条记录是最后录入的数据。整个rowkey(timestamp并不是必要的,视业务而定)。rowkey=哈希(主键<递增的id\手机号码等>)+Long.Max_Value - timestamp

4.预分区splitkeys选取

取样,先随机生成一定数量的rowkey(10万、100万),将取样数据按升序排序放到一个集合里。

根据预分区的region个数,对整个集合平均分割,即是相关的splitkeys。

HBaseAdmin.createTable(HTableDescriptor tableDescriptor,byte[][] splitkeys)可以指定预分区的splitkey,即指定region间的rowkey临界值。

5.rowkey设计原则

rowkey唯一原则

必须在设计上保证其唯一性,rowkey是按照字典顺序排序存储的,因此,设计rowkey的时候,要充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问的数据放到一块。

rowkey长度原则

rowkey是一个二进制码流,可以是任意字符串,最大长度 64kb ,实际应用中一般为10-100byte,以byte[] 形式保存,一般设计成定长。建议越短越好,不要超过16个字节,2个原因:

  • 数据的持久化文件HFile中是按照(Key,Value)存储的,如果rowkey过长,例如超过100byte,那么1000万行的记录计算,仅row key就需占用100*1000万=10亿byte,近1Gb。这样会极大影响HFile的存储效率!
  • MemStore将缓存部分数据到内存,若 rowkey字段过长,内存的有效利用率就会降低,就不能缓存更多的数据,从而降低检索效率。

rowkey散列原则

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

6.Column Family列族的设计数量不宜过多(建议不设置多个)

  • Region是由一到多个HStore组成的,HStore对应列族,也就是表中有多个CF,就会有多个个HStore;而分列的时候是根据Region的大小切分的。
  • 现在已经知道必须要先做预分区和key的散列了,那么,假设表中有多个列族,也就是多个CF,对应也就有多个HStore,而此时,假设多个列族的数据分配不均衡就会出现下面情况:如果某个Region里面的A HStore有1000万条数据,而B HStore里面只有100条数据。那么,这100条数据会被分到多个region中,读取B HStore的数据时,跨了多个region,导致查询效率降低。
  • Hbase的列族设计是为了加快读取速度的,同一个表的数据,按列族把数据划分后,数据查询时能缩小数据的范围(查询数据时指定列族),查询效率会加快,然而,如果数据分配不均衡就会导致效率降低,所以并不建议多个列族,可以建多个表,数据量小的表Region数量也可以设置小一点。

7.一对多设计和宽表

假设,现在有用户表和银行卡表,一个用户对应多张银行卡
传统的关系型数据(RMDB),我们会设计成两张表,通过关联查询获取数据;
如果Hbase也设计成两张表,那么如果想获取用户和银行卡的数据,就得查询两次才能获取到数据。
如果设计成一张宽表,把用户数据放到银行卡的表上,也就是用户的数据被存放了多次,但是获取数据的时候只需要查询一次就能把用户和用户银行卡的数据查询出来。

宽表的缺点:浪费存储空间,如果修改用户数据,那么是覆盖多条数据,操作繁琐,但是并不影响性能。

宽表的优点:查询效率提高。

两种设计都有优点和缺点,浪费性能还是浪费存储空间,这需要视具体情况而定,需要作出取舍。

猜你喜欢

转载自www.cnblogs.com/yn-huang/p/10890585.html