HBase的表结构你设计得不对!

正如我在前面章节强调的,HBase数据模型跟关系型数据库系统有非常大的差异。因此,设计Hbase的数据表的方法和思路跟关系型数据库不一样。设计HBASE表应该在具体业务场景的上下文中回答以下问题:

1、rowkey结构应该是什么,它应该包含什么?

2、表(table)应该有多少个列簇?

3、各个列族该存储什么数据?

4、每个列族(column family)有多少列(column)?

5、列名应该是什么?尽管列名不需要在表创建中定义时,但在编写或读取数据时需要了解它们。

6、单元格(cells)应该存储什么信息?

7、每个单元格(cell)应该存储多少个版本的数据?

设计Hbase数据表最重要的是定义rowkey结构。为了有效定义rowkey结构,有必要预先定义数据访问模式(读取和写入)。为了定义模式(schema),HBase表的一些特性必须考虑。快速再看一下:

1、只有Key(rowkey)上有索引。

2、表基于rowkey进行排序存储。表中的每个区域负责存储一部分rowkey范围,由开始行和结束行的rowkey标识。该区域包含从开始键到结束键的行排序列表。

3、HBASE表中的所有内容都存储为二进制字节(byte[]),没有类型。

4、原子性操作只在一行(row)上得到保证。没有跨行原子性保证,这意味着没有多行事务。

5、列族必须在创建表之前定义。

6、列限定符(column qualifiers)是动态的,可以在写入时定义。它们以字节(byte[])形式被储存,甚至可以将数据放入其中。

通过例子可以比较好的理解这些概念。让我们尝试在HBase表中对Twitter的用户关系进行建模(一些用户关注了另一些用户)。

关注与被关注(Follower-followed)关系本质上是图形(graphs),有专门的图形数据库可以更有效地处理这些数据集。然而,这个特定的用例为HBase表中的模型提供了一个很好的例子,并允许我们强调一些有趣的概念。

数据库表建模的第一步是定义应用程序的访问模式。在Twitter等应用程序的Follower-followed关系的上下文中,访问模式可以如下定义

读取访问模式:

1、用户关注谁?

2、特定用户A是否关注用户B?

3、谁关注了特定用户A?

写访问模式:

1、用户关注新用户。

2、用户取消关注某人。

让我们考虑集中表设计方式,看看它们的优缺点。从图1中所示的表设计开始。该表一行存储特定用户关注的所有用户列表,其中row key是关注者的用户ID,每列包含被关注用户的用户ID。具有数据的该设计表将如图2所示。

图1:HBase表用于保留特定用户正在关注的用户列表

图2:包含设计样本数据的表格(图1设计)

这个设计适用于读取模式的的第1条。它也解决了读模式的第2条,但是如果被关注用户列表很大,这个方法需要遍历整个列表才能回答读模式的第2条的问题,成本很高。在这个设计中添加用户有点棘手,由于没有存储计数,所以添加一个新的关注用户ID需要读取整行数据,才能找到下一个用户的编号。成本太高了!一个可能的解决方案就是保留一个计数器,现在表格如图3所示。

图3:包含示例数据的表(图1设计),但带有一个计数器,用于记录给定用户关注的用户数

图4:根据图3中的表设计将新用户添加到关注用户列表所需的步骤

图3中的设计比以前的设计更好,但并不能解决所有问题。取消关注用户仍然很棘手,因为您必须阅读整行以找出需要删除的列。它也不是理想的计数,因为取消关注将导致空洞(编号不连续)。最大的问题是,要添加用户,您必须在客户端代码中实现某种事务逻辑,因为HBase不会跨行或跨RPC调用执行事务。在此方案中添加用户的步骤如图4所示。

我之前提到的一个特性是列限定符是动态的,并且像单元格一样存储为byte []。您能够在其中放置任意数据,这点有可能改进之前的设计。考虑图5中的表。在此设计中,不需要计数,添加用户变简单。取消关注也得到简化。在这种情况下,单元格只包含一些任意小的值,且没有任何意义。

图5:被关注用户名作为列限定符,任意字符串作为单元格值

这种最新设计实现了我们定义的几乎所有访问模式,除了读取模式第3条:谁关注了特定用户A?在当前设计中,由于索引仅在row key上有效,因此您需要执行全表扫描来回答这个问题。您需要为关注(特定用户)的用户建立某种索引。有两种方法可以解决这个问题。首先是维护另一个包含反向列表的表(用户和所有关注这个用户的用户列表)。第二种是使用不同的row key将该信息保存在同一个表中(它全是字节数组,而HBase并不关心你放在那里的内容)。这两种方式,您都需要单独处理该信息,这样就无需进行大规模扫描,可以快速访问它。

当前的表结构中还可以进一步优化。看看图6:

图6:包含关注者和被关注用户的row key设计的表

在这个设计中有两点需要注意:row key现在包含关注者和被关注用户;列族名称已缩短为f。短列族名称是一个不相关的概念,之前的表设计也能很好实现功能。短列簇名只是通过减少需要从HBase读取/写入的数据来减少I / O负载(磁盘和网络),列簇名称是返回给客户端的每个KeyValue 对象的一部分。第一点在这里更重要。获取关注用户列表从get操作变为简短的scan操作。由于get在内部实现是长度为1的扫描,因此性能影响很小。取消关注与回答“A是否关注B?”分别成为简单的delete和get操作,并不需要像之前那样遍历整个用户列表。这个设计显然成本更低,尤其是当被关注用户列表很大的时候。

基于此设计的样本数据的表格如图7所示。

图7:基于图6设计,带有样本数据的表

请注意,row key长度在表中是可变的。由于每次调用表传输的数据长度不定,因此难以推断性能。这个问题的解决方案是在row key中使用哈希值。就其本身而言,这是一个有趣的概念,并且具有超出本文范围的row key设计相关的其他含义。要在当前表中获得统一的row key长度,您可以散列各个用户ID并将它们连接起来,而不是串联用户ID本身。由于您始终知道要查询的用户,因此可以使用用户ID生成的哈希值去查询数据表。具有哈希值的表将如图8所示。

图8:使用MD5作为row key的一部分来实现固定长度。这也允许你摆脱我们到目前为止所需的+分隔符。row key现在由固定长度部分组成,每个用户ID为16个字节。

这个表设计有效地回答我们之前概述的所有访问模式问题。

总结

本文介绍了HBase架构设计的基础知识。我首先介绍了数据模型(这部分没有翻译,可以参看HBase官方文档),然后讨论了设计HBase表时要考虑的一些因素。在HBase表设计中还有更多可供探索和学习的东西,这些东西可以建立在这些基础之上。本文的主要内容是:

row key是HBase表设计中最重要的一个方面,它决定了应用程序与HBase表的交互方式,还会影响您从HBase中提取数据的性能。

HBase表非常灵活,可以以byte []的形式存储任何内容。

将具有相似访问模式的数据存储在同一列族中。

只有Keys上有索引,好好利用它。

高表(tall table),可以让操作更快更简单,但你要权衡原子性。宽表(wide table),每行有很多列,允许行级原子性。

思考如何在单个API调用中完成访问模式,而不是通过多个API调用。HBase没有跨行事务,您需要避免在客户端代码中构建该逻辑。

Hashing允许使用固定长度的keys,具有更好的数据分布,但她移除了使用字符串作为keys的数据顺序。

列限定符(Column qualifiers)可用于存储数据,就像单元格本身一样。

列限定符(Column qualifiers)的长度会影响存储空间,因为您可以将数据放入其中。访问数据时,长度也会影响磁盘和网络I / O成本。列限定符要简明扼要。

列簇名称的长度会影响通过线路发送到客户端的数据大小(在KeyValue对象中)。列簇名要简明扼要。

快速理解HBase基本结构《

快速理解HBase和BigTable

更多内容可以阅读《Introduction to HBase Schema Design》

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181030B11I2V00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励