HBase RowKey设计

Posted 爱是与世界平行

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HBase RowKey设计相关的知识,希望对你有一定的参考价值。

1 HBase表热点

1.1 什么是热点

  • 检索habse的记录首先要通过row key来定位数据行。
  • 当大量的client访问hbase集群的一个或少数几个节点,造成少数region server的读/写请求过多、负载过大,而其他region server负载却很小,就造成了“热点”现象。

1.2 热点现象产生

HBase中的行是按照Rowkey的字典顺序排序的,这种设计优化了scan操作,可以将相关的行以及会被一起读取的行存取在临近位置,便于scan。

然而糟糕的Rowkey设计是热点的源头。 热点发生在大量的client直接访问集群的一个或极少数个节点(访问可能是读,写或者其他操作)。

大量访问会使热点region所在的单个机器超出自身承受能力,引起性能下降甚至region不可用,这也会影响同一个RegionServer上的其他region,由于主机无法服务其他region的请求,这样就造成 数据热点现象。 (这一点其实和数据倾斜类似)

所以我们在向HBase中插入数据的时候,应优化RowKey的设计,使数据被写入集群的多个region,而不是一个。尽量均衡地把记录分散到不同的Region中去,平衡每个Region的压力。

1.3 热点的解决方案

1.3.1 预分区

  • 预分区的目的让表的数据可以均衡的分散在集群中,而不是默认只有一个region分布在集群的一个节点上。

默认情况下,在创建HBase表的时候会自动创建一个region分区。这个region的rowkey是没有边界的,即没有startkey和endkey,在数据写入时,所有数据都会写入这个默认的region,随着数据量的不断增加,此region已经不能承受不断增长的数据量,会进行split,分成2个region。

在此过程中,会产生两个问题:

  1. 数据往一个region上写,会有写热点问题。造成单机负载压力大,影响业务的正常读写。
  2. region split会消耗宝贵的集群I/O资源。

一种可以加快批量写入速度的方法是通过预先创建一些空的regions,这样当数据写入HBase时,会按照region分区情况,在集群内做数据的负载均衡。

示例:

# create table with specific split points
hbase>create 'table1','f1',SPLITS => ['\\x10\\x00', '\\x20\\x00', '\\x30\\x00','\\x40\\x00']
# create table with four regions based on random bytes keys
hbase>create 'table2','f1',  NUMREGIONS => 8 , SPLITALGO => 'UniformSplit' 
# create table with five regions based on hex keys
hbase>create 'table3','f1',  NUMREGIONS => 10, SPLITALGO => 'HexStringSplit' 

1.3.2 加盐

  • 这里所说的加盐不是密码学中的加盐,而是在rowkey的前面增加随机数,具体就是给rowkey分配一个随机前缀以使得它和之前的rowkey的开头不同

1.3.3 哈希

  • 哈希会使同一行永远用一个前缀加盐。哈希也可以使负载分散到整个集群,但是读却是可以预测的。使用确定的哈希可以让客户端重构完整的rowkey,可以使用get操作准确获取某一个行数据。
rowkey=MD5(username).subString(0,10)+时间戳	

1.3.4 反转

  • 反转固定长度或者数字格式的rowkey。这样可以使得rowkey中经常改变的部分(最没有意义的部分)放在前面。
  • 这样可以有效的随机rowkey,但是牺牲了rowkey的有序性。
电信公司:
移动-----------> 136xxxx9301  ----->1039xxxx631
				   136xxxx1234  
				   136xxxx2341 
电信
联通

user表
rowkey    name    age   sex    address
          lisi1   21     m      beijing
          lisi2   22     m      beijing
	  lisi3   25     m      beijing
	  lisi4   30     m      beijing
	  lisi5   40     f      shanghai
	  lisi6   50     f      tianjin
	          
需求:后期想经常按照居住地和年龄进行查询?	
rowkey= address+age+随机数
        beijing21+随机数
        beijing22+随机数
        beijing25+随机数
        beijing30+随机数
   
rowkey= address+age+随机数

1.3.5 时间戳反转

一个常见的数据处理问题是快速获取数据的最近版本,使用反转的时间戳作为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 - 结束时间] [Long.Max_Value - 结束时间]。

电话号码,身份证,纯数字的类型,都可以使用这个方式。

2 RowKey概念

HBase中RowKey可以唯一标识一行记录,在HBase查询的时候有以下几种方式:

1.通过get方式,指定RowKey获取唯一一条记录

2.通过scan方式,设置startRow和stopRow参数进行范围匹配

3.全表扫描,即直接扫描整张表中所有行记录

从字面意思来看,RowKey就是行键的意思,在曾删改查的过程中充当了主键的作用。它可以是 任意字符串,在HBase内部RowKey保存为字节数组。

HBase中的数据是按照Rowkey的ASCII字典顺序进行全局排序的,有伙伴可能对ASCII字典序印象不够深刻,下面举例说明:

假如有5个Rowkey:“012”, “0”, “123”, “234”, “3”,按ASCII字典排序后的结果为:“0”, “012”,“123”, “234”, “3”。

因此我们设计RowKey时,需要充分利用排序存储这个特性,将经常一起读取的行存储放到一起,要避免做全表扫描,因为效率特别低。

2.1 RowKey在查询中的作用

HBase中RowKey可以唯一标识一行记录,在HBase中检索数据有以下三种方式:

  1. 通过 get 方式,指定 RowKey 获取唯一一条记录

  2. 通过 scan 方式,设置 startRow 和 stopRow 参数进行范围匹配

  3. 全表扫描,即直接扫描整张表中所有行记录

2.2 RowKey设计技巧

2.2.1 越高频的查询字段排列越靠左

下面根据一个例子分别介绍下根据RowKey进行查询的时候支持的情况。

如果我们RowKey设计为uid+phone+name,那么这种设计可以很好的支持一下的场景:

uid= 873969725 AND phone=18900000000 AND name=zhangsan
uid= 873969725 AND phone=18900000000
uid= 873969725 AND phone=189?
uid= 873969725

难以支持的场景:

phone=18900000000 AND name = zhangsan
phone=18900000000 
name=zhangsan

从上面的例子中可以看出,在进行查询的时候,根据RowKey从前向后匹配,所以我们在设计RowKey的时候选择好字段之后,还应该结合我们的实际的高频的查询场景来组合选择的字段,越高频的查询字段排列越靠左

3 Rowkey 设计

3.1 rowkey长度原则

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

  • 建议尽可能短;但是也不能太短,否则rowkey前缀重复的概率增大

  • 设计过长会降低memstore内存的利用率和HFile存储数据的效率。

在HBase的底层存储HFile中,RowKey是KeyValue结构中的一个域。假设RowKey长度100B,那么1000万条数据中,光RowKey就占用掉 100*1000w=10亿个字节 将近1G空间,这样会极大影响HFile的存储效率。

我们目前使用的服务器操作系统都是64位系统,内存是按照8B对齐的,因此设计RowKey时一般做成8B的整数倍,如16B或者24B,可以提高寻址效率。

同样地,列族、列名的命名在保证可读的情况下也应尽量短。value永远和它的key一起传输的。当具体的值在系统间传输时,它的RowKey,列名,时间戳也会一起传输(因此实际上列族命名几乎都用一个字母,比如‘c’或‘f’)。如果你的RowKey和列名和值相比较很大,那么你将会遇到一些有趣的问题。Hfile中的索引最终占据了HBase分配的大量内存。

3.2 rowkey散列原则

  • 建议将rowkey的高位作为散列字段,这样将提高数据均衡分布在每个RegionServer,以实现负载均衡的几率。
  • 如果没有散列字段,首字段直接是时间信息。所有的数据都会集中在一个RegionServer上,这样在数据检索的时候负载会集中在个别的RegionServer上,造成热点问题,会降低查询效率。

比如设计RowKey的时候,当Rowkey 是按时间戳的方式递增,就不要将时间放在二进制码的前面,可以将 Rowkey 的高位作为散列字段,由程序循环生成,可以在低位放时间字段,这样就可以提高数据均衡分布在每个Regionserver实现负载均衡的几率。

结合前面分析的热点现象的起因思考: 如果没有散列字段,首字段只有时间信息,那就会出现所有新数据都在一个RegionServer上堆积的热点现象,这样在做数据检索的时候负载将会集中在个别RegionServer上,降低查询效率。

3.3 rowkey唯一原则

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

需要注意:由于HBase中数据存储的格式是Key-Value对格式,所以如果向HBase中同一张表插入相同RowKey的数据,则原先存在的数据会被新的数据给覆盖掉(和HashMap效果相同)。

3.4 排序原则

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

一个常见的数据处理问题是快速获取数据的最近版本,使用反转的时间戳作为RowKey的一部分对这个问题十分有用,可以用 Long.Max_Value-timestamp追加到key的末尾。

例如 [key][reverse_timestamp] , [key]的最新值可以通过scan [key]获得[key]的第一条记录,因为HBase中RowKey是有序的,第一条记录是最后录入的数据。

4 表设计

4.1 列簇设计

追求的原则是:在合理范围内能尽量少的减少列簇就尽量减少列簇。

最优设计是:将所有相关性很强的key-value都放在同一个列簇下,这样既能做到查询效率最高,也能保持尽可能少的访问不同的磁盘文件

以用户信息为例,可以将必须的基本信息存放在一个列族,而一些附加的额外信息可以放在另一列族hbase的列簇越少越好!尽量就是1个

5 问题考虑

  • 问题一:Rowkey是唯一的吗?

    相同的Rowkey在HBase中认为是同一条数据的多个版本,查询时默认返回最新版本的数据,所以通常Rowkey都需要保证唯一,除非用到多版本特性。

    最佳设计示例:Rowkey相当于数据库的主键。Rowkey表示一条记录。Rowkey可以是一个字段也可以是多个字段接起来。Rowkey为[userid]表示每个用户只有一条记录, Rowkey为[userid][orderid]表示每个用户有多条记录。

  • 问题二:满足哪种查询场景?

    Rowkey的设计限制了数据的查询方式,HBase有两种查询方式。

    • 根据完整的Rowkey查询(get方式),例如

      SELECT * FROM table WHERE Rowkey = ‘abcde’
      

      说明 get方式需要知道完整的Rowkey,即组成Rowkey所有字段的值都是确定的。

    • 根据Rowkey的范围查询(scan方式),例如

      SELECT * FROM table WHERE ‘abc’ < Rowkey <’abcx’
      

      说明 scan方式需要知道Rowkey左边的值,例如您使用英文字典查询pre开头的所有单词,也可以查询prefi开头的所有单词,不能查询中间或结尾为prefi的单词。

    最佳设计示例:在有限的查询方式下如何实现复杂查询?以下方法可以帮您实现。

    • 再新建一张表作为索引表。

    • 使用Filter在服务端过滤不需要的数据。

    • 使用二级索引。

    • 使用反向scan方法实现倒序(将新数据排在前面),

      scan.setReverse(true)
      

      说明 反向scan的性能比正常scan性能差,如果大部分是倒序场景可以体现在Rowkey设计上,例如[hostname][log-event][timestamp] => [hostname][log-event][Long.MAX_VALUE – timestamp]

  • 问题三:数据足够分散,会存在堆积的热点现象吗?

    散列的目的是将数据分散到不同的分区,不至于产生热点使某一台服务器终止,其他服务器空闲,充分发挥分布式和并发的优势。

    最佳设计示例:

    • 设计md5散列算法:[userId][orderid] => [md5(userid).subStr(0,4)][userId][orderid]
    • 设计反转:[userId][orderid] => [reverse(userid)][orderid]
    • 设计取模:[timestamp][hostname][log-event] => [bucket][timestamp][hostname][log-event]; long bucket = timestamp % numBuckets
    • 增加随机数:[userId][orderid] => [userId][orderid][random(100)]
  • 问题四:Rowkey可以再短点吗?

    短的Rowkey可以减少数据量,提高数据查询和数据写入效率。

    最佳设计示例:

    • 使用Long或Int代替String,例如'2015122410' => Long(2015122410)
    • 使用编码代替名称,例如’淘宝‘ => tb
  • 问题五:使用scan方式会查询出不需要的数据吗?

    会的。场景举例:table1的Rowkey为column1+ column2+ column3,如果您需要查询column1= host1的所有数据,使用scan 'table1',startkey=> 'host1',endkey=> 'host2'语句。如果有一条记录为column1=host12,那么此记录也会查询出来。

    最佳设计示例:

    • 设计字段定长,[column1][column2] => [rpad(column1,'x',20)][column2]
    • 添加分隔符,[column1][column2] => [column1][_][column2]

6 常见设计实例

  • 日志类、时间序列数据。列举出三个场景设计Rowkey。
    • 查询某台机器某个指标某段时间内的数据,Rowkey设计为[hostname][log-event][timestamp]
    • 查询某台机器某个指标最新的几条数据,Rowkey设计为timestamp = Long.MAX_VALUE – timestamp; [hostname][log-event][timestamp]
    • 查询的数据存在只有时间一个维度或某一个维度数据量巨大的情况,Rowkey设计为long bucket = timestamp % numBuckets; [bucket][timestamp][hostname][log-event]
  • 交易类数据。列举出四个场景设计Rowkey。
    • 查询某个卖家某段时间内的交易记录,Rowkey设计为[seller id][timestmap][order number]
    • 查询某个买家某段时间内的交易记录,Rowkey设计为[buyer id][timestmap][order number]
    • 根据订单号查询,Rowkey设计为[order number]
    • 查询中同时满足三张表,一张买家维度表Rowkey设计为[buyer id][timestmap][order number]。一张卖家维度表Rowkey设计为[seller id][timestmap][order number]。一张订单索引表Rowkey设计为[order number]

以上是关于HBase RowKey设计的主要内容,如果未能解决你的问题,请参考以下文章

实时即未来,大数据项目车联网之原始数据实时ETL落地HBase

使用 Impala 查询加盐的 Hbase 行键

HBase RowKey详细设计

Hbase Rowkey设计原则

HBase Rowkey设计

HBase Rowkey的设计原则