ITPub博客

首页 > 大数据 > > HBase 模式设计介绍(译:Introduction to HBase Schema Design)

HBase 模式设计介绍(译:Introduction to HBase Schema Design)

作者:浪花小雨滴 时间:2020-05-09 11:06:30 0 删除 编辑

原题:Introduction to hbase Schema Design

作者:Amandeep Khurana


最近的几年中,处理海量数据的应用程序的数据迅猛增长。为了支持这种新的应用程序,以及为旧有程序扩容,许多新的数据管理系统被开发了出来。一些人称之为大数据革命。许多这些新系统是开源的、社区驱动的,并且被很多大公司应用了起来。Apache HBase 就是其中之一。它是一个开源的分布式数据库,以 Google Bigtable 为原型发展而来。对于需要快速随机读写海量数据的应用,它成为越来越受欢迎的数据库选择。HBase 建立于 Apache Hadoop 之上,紧密地与之整合在一起。


相对于传统的关系型数据库,比如 MySQL, PostgreSQL 以及 Oracle 等,HBase 是非常不一样的,这主要体现在架构差异以及为应用程序所提供的特性差异。HBase 牺牲掉一些关系数据库的特性,换取了更好的可扩展性和模式的灵活性。也就是说, HBase 有着非常不同的数据模型。相对于关系型数据库,设计 HBase 表是完全不同的一种规则。我会通过解释 HBase 的数据模型来介绍 HBase 表设计的基本理念,并在此基础上以一个例子来进一步诠释在设计过程中各种各样的概念。


速学 HBase 数据模型


HBase 与你所熟知的关系型数据库非常不一样。正如原 Bigtable 论文中描述的那样,HBase 是一个稀疏的、分布式、持久、多维、排序的映射,它以行键(row key),列键(column key)和时间戳(timestamp)为索引。你可能会听到人们称呼它为 key-value 存储的、面向列族的数据库,或者是存储不同版本的映射的映射的数据库。所有这些描述都是正确的。这一部分来解释这些概念。


最简单和原始的描述 HBase 的数据模型的方式是表,包含着行和列。这可能和你对于关系型数据库的理解很相似。但是所有 HBase 与关系型数据库的相似之处就止步与此了。事实上,即使是对于行和列的概念,HBase 与关系型数据库都是有所不同的。首先,我要定义一些后面会用到的概念。


  • 表:HBase 将数据组织起来存放进表里。表名是字符串,由一系列在文件系统里可安全使用的字符组成。
  • 行:在一张表里,数据按照不同的行存储。每行由唯一可识别的行键来定义。行键没有数据类型,所有的数据都作为字节来处理(byte array)。
  • 列族:每行的数据以列族而分组。列族还会影响数据在 HBase 中的物理存储,因此它们必须被首先定义而且不可轻易修改。同一个表中所有行的数据都有着相同的列族,但是每行数据不一定在每个列族都存储数据。列族名的是字符串,由一系列在文件系统里可安全使用的字符组成。
  • 列修饰符:在一个列族中,数据被称为列修饰符,或者简单的称为列。列修饰符不需要提前定义。列修饰符不需要在不同行中保持一致。同行键一样,列修饰符没有数据类型,所有的数据都作为字节来处理(byte array)。
  • 单元:行键、列族和列修饰符唯一地确定一个单元。单元里存储的数据被称为单元的值,单元值同样没有数据类型,所有的数据都作为字节来处理(byte array)。
  • 时间戳:单元里的值有不同的版本,每个版本以版本号来标识,版本号一般默认是单元值被写入时的时间戳。如果写数据时没有指定时间戳,默认使用当前时间。如果读取数据时没有指定时间戳,则返回最新时间戳的版本的值。每个单元在 HBase 中保留的版本数由每个列族定义配置。默认的保留版本数是3.


一个 HBase 表可表示为图1.

图1: 一个 HBase 表有2个列族, Personal 和 Office,每个列族有2列。存储有数据的实体就是一个单元。行按照行键来排序。


这些概念在客户端的 API 中也有体现。HBase 为数据处理提供的 API 主要包括3种方法:Get, Put 和 Scan。Gets 和 Puts 是针对具体某一行的方法,使用时需要明确行键。Scan应用于一系列行,行的范围可以由起始行键和结束行键来定义,如果没有定义范围则会应用于整表。


有时候把数据模型理解成多维映射会更容易些。图2把图1中的第一行表示成了多维映射。

图2: HBase 中的一行被表示成多维映射。


一个行键对应一系列列族,一个列族对应一系列列修饰符,一个列修饰符对应一系列时间戳,一个时间戳对应一个值,也就是单元本身。如果你限定行键来取值,会得到所有的列。如果限定列族来取值,会得到所有的列修饰符以及它们对应的子映射。如果限定列修饰符来取值,会得到所有的时间戳和它对应的值。 HBase 对特定的模式进行了优化,默认只返回最新时间版本的值,但是你可以在查询中指定要求多版本。行键等同于关系型数据库中的主键,完成建表后你不能变更在 HBase 里哪一列作为行键。也就是说,一旦有数据存进表里,Personal 列族中的 Name 列不能被设置为行键。


前面提到了,有很多方式可以描述这样的数据模型。你可以把这些看成是 key-value 存储(图3),key是行键,value 是列里的其他数据。考虑到行键是获取一行数据的唯一入口,这样描述还挺贴切的。你还可以把 HBase 理解成这样的 key-value 存储,key 是行键、列族、列修饰符和时间戳,value 是它对应的单元里存储的值。我们后面会讲到底层的存储细节,你会知道当读取某行的特定单元时,实际上是读取了一系列数据,它包含了你想要单元或者还有其他单元。这正是 key-value 对象在 HBase API 和内部表示的方式。key的组成是 [行键,列族,列修饰符,时间戳],value 就是这个单元对应的内容了。


图3: HBase表用key-value的方式存储。key 可以被认为就是行键,或者是行键、列族、列修饰符和时间戳的组合,这取决于你想要获取什么数据。如果想获取一行中的所有单元,那么key 就是行键。如果仅仅想获取一些特定的单元,那么相应的列族和列修饰符就必须成为 key 的一部分。


HBase 表设计基础


我在前文中强调过,HBase 的数据模型与关系型数据库非常不同。因此,设计 HBase 表的方法与关系型数据库完全不同。如何设计 HBase 表可以从使用场景中下述问题得到答案:


  1. 行键的结构如何以及它包含什么数据?
  2. 表里应该有多少个列族?
  3. 每个列族存储什么数据?
  4. 每个列族有多少个列?
  5. 列名叫什么?虽然你不需要在建表的时候就定义列名,但是在读写数据的时候你必要要知道。
  6. 单元里存储什么信息?
  7. 每个单元保存多少个版本?


HBase 表设计最重要的就是定义行键。为了高效的定义行建,首先定义获取模式(读和写)是很重要的。要定义模式,你要清楚这一些特性。这里列了一个单子:


  1. 只有键会被索引。
  2. 表是基于行键排序存储的。表里的每个区域对应一个行键空间,由开始行键和结束行键来定义。这个区域包含着从开始行键到结束行键之间的排序的数据行。
  3. HBase 表中存储的所有数据都是字节。也就是说,没有类型的概念。
  4. 行级别可以保证原子性,跨行无法保证原子性,也就是说不支付多行事务。
  5. 列族必须首先在创建表的时候定义。
  6. 列修饰符是动态的,可以在写数据的时候再定义。它是作为字节存储的,所以你甚至可以把数据放进去。


看一个例子是很好的学习这些概念的方法。我们来尝试为 Twitter 关系(用户关注其他用户)设计 HBase 表。关注关系本质是图形式的,有一些专门的图形数据库可以更好的处理这类数据,而在这里它是一个设计 HBase 表的很好的例子而且可以强调一些有趣的概念。


设计表的第一步是定义应用的获取形式。在本文所讲的 Twitter 这种用户的关注关系里,它的获取形式可以被定义为以下:


读数据获取方式:

  1. 用户关注了谁?
  2. 个用户A是否关注了用户B?
  3. 谁关注了用户A?

写数据获取方式:

  1. 用户关注了一个新用户。
  2. 用户取消关注了一个原本关注的用户。


我们来讨论一下几个表设计的选项,看看它们各有什么优缺点。让我们从图4所示的设计说起。这张表里存储的每行数据表示某个用户关注的一系列用户,行键是关注者的用户名,每一列是被关注者的用户名。图5是以上设计存有具体数据的样例。


图4: HBase 表存储某个用户关注其他用户

图5: 图4设计的样例数据。


这个设计对于上文列出的第一项读取方式来说非常合适。它也能解决第二项读取方式,但是如果这个用户关注的其他用户非常多这将是一件成本高昂的事情,而且它需要循环遍历整个列表才能回答这个问题。新增关注用户有点麻烦。除非新增前读取了整行记录,否则你并不知道应该把它放在第几列,因为并没有一个计数统计。这实在是太贵了!一个解决方法是,保留一个计数值,那么它会变成图6的样子。


图6:在图4设计的基础上进行了扩展的样例数据,增加了记录每个用户关注其他用户的数量的计数值。

图7:以图6的设计为基准,用户新增关注的操作步骤


图6的设计比最初的那个版本好一些,但并不能解决所有问题。取消关注还是很麻烦,因为你还是必须读取整行数据才能知道应该删除哪一列。而且对计数器也不友好,因为删除某一列会留下空隙。最大的问题是新增关注,你必须通过客户端代码实现某种事务逻辑,因为 HBase 不支持行事务,RPC调用也不行。这种新增关注的步骤在图7里有表示。


前面提过一个特性,列修饰符是动态的以字节存储的,就像单元一样。如此,你可以用任何数据,这就是这个案例的救星。请看图8所示的表。在这个设计里,不需要计数器,所以新增关注用户没那么复杂了。取消关注也很简单。单元里面存储的数据只是某个随便什么小数值,这并不影响结果。

图8:关注关系表现在以被关注用户的用户名作为列修饰符,任意字符作为单元值。


最后这个版本的设计几乎解决了我们定义的所有获取方式的问题。唯一没解决的只有读取方式的第三项:谁关注了某个用户A?在当然的设计模式下,因为数据是根据行键索引的,你必须描述全表才能回答上述问题。这就明示了,应该想办法让被关注用户在索引有所体现。解决这个问题有两个方法。第一个方法是,再建一个表来保存与这个表相反的信息(被关注用户与所有关注他的用户)。第二个方法是,为这个表设计一个不同的行键(记住它只是字节, HBase 不关心你用了什么数据)。这两种方法都是通过把信息物理化,这样就可以快速获取信息而不必扫描全表。


当前表结构还有其他可以优化的地方,请看图9.

图9:关系表的行键包含了关注用户和被关注用户。


这个设计里有两点需要强调:现在行键包含了关注用户和被关注用户;列族名称被缩短成了f。缩短的列族名称与我们当前讨论的问题没有关系,在上一个版本里也可以实现。这样做减少从 HBase 读写的数据量,进而减小了 I/O 负载(对磁盘和网络而言),因为每一个返回客户端的 key-value 对象都包含着列族名称。第一点是比较重要的。获取所有被关注用户信息,从原来的 get 操作变成了简短的 scan 操作。这对于性能影响不大,因为本质上 get 是长度为1的 scan 操作。取消关注和回答“A是否关注了B?”,只需要分别删除数据和使用 get 操作,而不需要像在以前的版本中那样需要遍历整行数据。这样回答案这些问题要便宜的多,尤其是对于关注了很多人的用户而言。


以上设计的带有样本数据请看图10.

图10:基于图9设计的样例数据


我们注意到,行键的长度是不固定的。这种可变性让性能调优有点麻烦,因为对于每个查询要返回的数据量是不同的。解决这个问题的一个方法是对行键使用哈希值。这是个很有意思的概念,而且对行键设计有一些超出本文讨论范畴的影响。为了得到表里长度相同的行键,你可以把取用户名的哈希值并把它们连接起来,而不是直接连接用户名本身。你想要查询的用户是已知的,你可以先计算好这个用户的哈希值,并用这个哈希值去进行查询。使用哈希值的表看起来是表11的样子。

图11:使用MD5值作为行键以实现固定长度。这样做同时还可以让你不再需要我们上面讲的 “+”符号。现在行键定长16字节。


这个表设计非常高效地回答了所有前面提到的获取方式的问题。


总结


本文概述了 HBase 模式设计的基本概念。我首先简介了数据模型,接着讨论了在设计 HBase 表时需要考虑的因素。在此基础上,关于 HBase 模式设计还有很多可以学习和探索的。本文的主要所得在于:


  • 行键是进行 HBase 表设计最重要的因素,它决定了你的应用程序如何与表进行交互,同时也影响了 HBase 的使用性能。
  • HBase 表是很灵活的,你可以把它设计为任何字节。
  • 在同一个列族里,用相似的获取方式来存储数据。
  • 索引仅以键为标准,请善用这一特性。
  • 长表在一定程度上让操作更简单快速,但是这牺牲了原子性。有很多列的宽表保证了以行为基础的原子性。
  • 想办法让你的获取方式通过一个API请求完成,而不是多个API请求。HBase 不支持多行事务,而且你应该尽可能避免在客户端代码里实现这样的逻辑。
  • 哈希函数可以实现定长键值以及更均衡的分布,但是它失去了使用原字符的排序顺序。
  • 列修饰符可以用来存储数据,就像单元值本身一样。
  • 列修饰符的长度会影响存储印记(storage footprint),因为你可以让它本身存储数据。列名的长度也会影响磁盘和网络 I/O 成本,让它们尽量简短。
  • 列族名长度影响通过网线传输此客户端的数据量(作为 KeyValue 对应的形式),让它们尽量简短。


参考文献

[1] Apache Hadoop project: hadoop .apache .org .

[2] Apache HBase project: hbase .apache .org .

[3] HBase client API: hbase .apache .org/apidocs/org/apache/hadoop/hbase/client/package-summary .html .

[4] HBase KeyValue API: hbase .apache .org/apidocs/org/apache/hadoop/hbase/KeyValue .html .

[5] Fay Chang, Jeffrey Dean, Sanjay Ghemawat, Wilson C . Hsieh, Deborah A . Wallach, Mike Burrows, Tushar Chandra, Andrew Fikes, and Robert E . Gruber, “Bigtable: A Distributed Storage System for Structured Data,” Proceedings of the 7th USENIX Symposium on Operating Systems Design and Implementation (OSDI ’06), USENIX, 2006, pp . 205–218 .

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/31537575/viewspace-3138805/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2018-05-16

  • 博文量
    4259
  • 访问量
    40492