数据模型

在 HBase 中,数据存储在具有行和列的表中。这是与关系数据库(RDBMS)重叠的术语,但这不是一个有用的类比。相反,将 HBase 表视为多维映射会很有帮助。

HBase 数据模型术语

HBase 表由多行组成。

HBase 中的一行由一个行键和一个或多个具有与之关联的值的列组成。行存储时,行按字母顺序排序。因此,行键的设计非常重要。目标是以相关行彼此靠近的方式存储数据。常见的行键模式是网站域。如果您的行键是域,则应该反向存储它们(org.apache.www,org.apache.mail,org.apache.jira)。这样,所有 Apache 域都在表中彼此靠近,而不是基于子域的第一个字母展开。

HBase 中的列由列族和列限定符组成,它们由:(冒号)字符分隔。

列族

列族通常出于性能原因而物理地分配一组列及其值。每个列族都有一组存储属性,例如是否应将其值缓存在内存中,如何压缩其数据或对其行键进行编码等。表中的每一行都具有相同的列族,但给定的行可能不会在给定的列族中存储任何内容。

列限定符

列限定符将添加到列族中,以提供给定数据的索引。给定列族content,列限定符可能是content:html,另一个可能是content:pdf。虽然列族在创建表时是固定的,但列限定符是可变的,并且行之间可能有很大差异。

细胞

单元格是行,列族和列限定符的组合,并包含值和时间戳,表示值的版本。

时间戳

时间戳与每个值一起写入,并且是给定版本值的标识符。默认情况下,timestamp 表示写入数据时 RegionServer 上的时间,但是当您将数据放入单元格时,可以指定不同的时间戳值。

21.概念观

您可以在 Jim R. Wilson 的博客文章了解 HBase 和 BigTable 中阅读关于 HBase 数据模型的非常容易理解的解释。另一个很好的解释可以在 Amandeep Khurana 的 PDF 基本模式设计简介中找到。

阅读不同的观点可能有助于深入了解 HBase 架构设计。链接的文章与本节中的信息具有相同的基础。

以下示例是 BigTable 论文第 2 页上的一个略微修改的形式。有一个名为webtable的表包含两行(com.cnn.wwwcom.example.www)和三个列族,名为contentsanchorpeople。在此示例中,对于第一行(com.cnn.www),anchor包含两列(anchor:cssnsi.comanchor:my.look.ca),contents包含一列(contents:html)。此示例包含具有行键com.cnn.www的行的 5 个版本,以及具有行键com.example.www的行的一个版本。 contents:html列限定符包含给定网站的整个 HTML。 anchor列系列的限定符每个都包含链接到该行所代表的站点的外部站点,以及它在其链接的锚点中使用的文本。 people列系列表示与该站点关联的人员。

列名称

按照惯例,列名由其列族前缀和 限定符 组成。例如,列 内容:html 由列族contentshtml限定符组成。冒号字符(:)从列族 限定符 中分隔列族。

行键 时间戳 ColumnFamily contents ColumnFamily anchor ColumnFamily people
“com.cnn.www” T9 主播:cnnsi.com =“CNN”
“com.cnn.www” T8 主播:my.look.ca =“CNN.com”
“com.cnn.www” T6 内容:html =“…”
“com.cnn.www” T5 contents:html = “…​”
“com.cnn.www” T3 contents:html = “…​”
“com.example.www” t5 contents:html = “…​” 人物:作者=“John Doe”

此表中看起来为空的单元格在 HBase 中不占用空间,或实际上存在空间。这就是 HBase“稀疏”的原因。表格视图不是查看 HBase 中数据的唯一可能方式,甚至是最准确的方法。以下表示与多维地图相同的信息。这只是用于说明目的的模型,可能不是严格准确的。

  1. {
  2. "com.cnn.www": {
  3. contents: {
  4. t6: contents:html: "<html>..."
  5. t5: contents:html: "<html>..."
  6. t3: contents:html: "<html>..."
  7. }
  8. anchor: {
  9. t9: anchor:cnnsi.com = "CNN"
  10. t8: anchor:my.look.ca = "CNN.com"
  11. }
  12. people: {}
  13. }
  14. "com.example.www": {
  15. contents: {
  16. t5: contents:html: "<html>..."
  17. }
  18. anchor: {}
  19. people: {
  20. t5: people:author: "John Doe"
  21. }
  22. }
  23. }

22.物理视图

虽然在概念级别,表可以被视为稀疏的行集,但它们是由列族物理存储的。可以随时将新列限定符(column_family:column_qualifier)添加到现有列族。

Row Key Time Stamp 列族anchor
“com.cnn.www” t9 anchor:cnnsi.com = "CNN"
“com.cnn.www” t8 anchor:my.look.ca = "CNN.com"
Row Key Time Stamp ColumnFamily contents:
“com.cnn.www” t6 contents:html = “…​”
“com.cnn.www” t5 contents:html = “…​”
“com.cnn.www” t3 contents:html = “…​”

概念视图中显示的空单元格根本不存储。因此,在时间戳t8处对contents:html列的值的请求将不返回任何值。类似地,在时间戳t9处对anchor:my.look.ca值的请求将不返回任何值。但是,如果未提供时间戳,则将返回特定列的最新值。给定多个版本,最新版本也是第一个版本,因为时间戳按降序存储。因此,如果没有指定时间戳,则对行com.cnn.www中所有列的值的请求将是:来自时间戳t6contents:html的值,来自时间戳t9anchor:cnnsi.com的值,来自时间戳t8anchor:my.look.ca

有关 Apache HBase 如何存储数据的内部结构的更多信息,请参见 regions.arch

23.命名空间

命名空间是与关系数据库系统中的数据库类似的表的逻辑分组。这种抽象为即将出现的多租户相关功能奠定了基础:

  • 配额管理( HBASE-8410 ) - 限制命名空间可以使用的资源量(即区域,表)。

  • 命名空间安全管理( HBASE-9206 ) - 为租户提供另一级别的安全管理。

  • 区域服务器组( HBASE-6721 ) - 可以将命名空间/表固定到 RegionServers 的子集上,从而保证粗略的隔离级别。

23.1。命名空间管理

可以创建,删除或更改命名空间。通过指定表单的完全限定表名,在表创建期间确定命名空间成员资格:

  1. <table namespace>:<table qualifier>

实施例 7.实施例

  1. #Create a namespace
  2. create_namespace 'my_ns'
  1. #create my_table in my_ns namespace
  2. create 'my_ns:my_table', 'fam'
  1. #drop namespace
  2. drop_namespace 'my_ns'
  1. #alter namespace
  2. alter_namespace 'my_ns', {METHOD => 'set', 'PROPERTY_NAME' => 'PROPERTY_VALUE'}

23.2。预定义的名称空间

有两个预定义的特殊命名空间:

  • hbase - 系统命名空间,用于包含 HBase 内部表

  • default - 没有明确指定名称空间的表将自动落入此名称空间

实施例 8.实施例

  1. #namespace=foo and table qualifier=bar
  2. create 'foo:bar', 'fam'
  3. #namespace=default and table qualifier=bar
  4. create 'bar', 'fam'

24.表

在模式定义时预先声明表。

25.排

行键是未解释的字节。行按字典顺序排序,最低顺序首先出现在表格中。空字节数组用于表示表名称空间的开始和结束。

26.专栏系列

Apache HBase 中的列分组为 列族 。列族的所有列成员都具有相同的前缀。例如,列 课程:历史 课程:数学 都是 课程 专栏系列的成员。冒号字符(:)从列族限定符中分隔列族。列族前缀必须由 可打印 字符组成。符合条件的尾部,列族 限定符 ,可以由任意字节组成。必须在模式定义时预先声明列族,而不需要在模式时定义列,但可以在表启动并运行时动态生成列。

从物理上讲,所有列族成员都存储在文件系统中。由于调整和存储规范是在列族级别完成的,因此建议所有列族成员具有相同的通用访问模式和大小特征。

27.细胞

{row,column,version} 元组确切地指定了 HBase 中的cell。单元格内容是未解释的字节

28.数据模型操作

四个主要数据模型操作是 Get,Put,Scan 和 Delete。通过实例应用操作。

28.1。得到

Get 返回指定行的属性。获取通过 Table.get 执行

28.2。放

添加到表中的新行(如果键是新的)或者可以更新现有行(如果键已存在)。 Puts 通过 Table.put (非 writeBuffer)或 Table.batch (非 writeBuffer)执行

28.3。扫描

Scan 允许对指定属性的多行进行迭代。

以下是“扫描表”实例的示例。假设一个表填充了具有键“row1”,“row2”,“row3”的行,然后是另一组具有键“abc1”,“abc2”和“abc3”的行。以下示例显示如何设置 Scan 实例以返回以“row”开头的行。

  1. public static final byte[] CF = "cf".getBytes();
  2. public static final byte[] ATTR = "attr".getBytes();
  3. ...
  4. Table table = ... // instantiate a Table instance
  5. Scan scan = new Scan();
  6. scan.addColumn(CF, ATTR);
  7. scan.setRowPrefixFilter(Bytes.toBytes("row"));
  8. ResultScanner rs = table.getScanner(scan);
  9. try {
  10. for (Result r = rs.next(); r != null; r = rs.next()) {
  11. // process result...
  12. }
  13. } finally {
  14. rs.close(); // always close the ResultScanner!
  15. }

请注意,通常,为扫描指定特定停止点的最简单方法是使用 InclusiveStopFilter 类。

28.4。删除

删除从表中删除一行。删除通过 Table.delete 执行。

HBase 不会修改数据,因此通过创建名为 tombstones 的新标记来处理删除。这些墓碑以及死亡的价值观在主要的压缩中得到了清理。

有关删除列版本的更多信息,请参阅 version.delete ,有关压缩的详细信息,请参阅压缩

29.版本

{row,column,version} 元组确切地指定了 HBase 中的cell。可以有一个无限数量的单元格,其中行和列相同但单元格地址仅在其版本维度上有所不同。

虽然行和列键表示为字节,但使用长整数指定版本。通常这个 long 包含时间实例,例如java.util.Date.getTime()System.currentTimeMillis()返回的时间实例,即: 当前时间与 UTC 时间 1970 年 1 月 1 日午夜之间的差异(以毫秒为单位)

HBase 版本维度按递减顺序存储,因此从存储文件读取时,首先会找到最新值。

在 HBase 中,cell版本的语义存在很多混淆。特别是:

  • 如果对单元格的多次写入具有相同的版本,则只能读取最后写入的内容。

  • 可以按非增加版本顺序写入单元格。

下面我们将介绍 HBase 中的版本维度当前是如何工作的。有关 HBase 版本的讨论,请参见 HBASE-2406HBase 中的弯曲时间可以很好地读取 HBase 中的版本或时间维度。版本控制的详细信息比此处提供的更多。

在撰写本文时,文章中提到的限制 覆盖现有时间戳 的值不再适用于 HBase。本节基本上是 Bruno Dumon 撰写的这篇文章的概要。

29.1。指定要存储的版本数

要为给定列存储的最大版本数是列模式的一部分,并在创建表时指定,或通过alter命令通过HColumnDescriptor.DEFAULT_VERSIONS指定。在 HBase 0.96 之前,保留的默认版本数为3,但是在 0.96 中,更新版本已更改为1

示例 9.修改列族的最大版本数

此示例使用 HBase Shell 在列族f1中保留所有列的最多 5 个版本。您也可以使用 HColumnDescriptor

  1. hbase> alter t1′, NAME => f1′, VERSIONS => 5

示例 10.修改列族的最小版本数

您还可以指定每个列族存储的最小版本数。默认情况下,此值设置为 0,表示该功能已禁用。以下示例通过 HBase Shell 将列族f1中所有列的最小版本数设置为2。您也可以使用 HColumnDescriptor

  1. hbase> alter t1′, NAME => f1′, MIN_VERSIONS => 2

从 HBase 0.98.2 开始,您可以通过在 hbase-site.xml 中设置hbase.column.max.version,为所有新创建的列指定的最大版本数指定全局默认值。参见 hbase.column.max.version

29.2。版本和 HBase 操作

在本节中,我们将查看每个核心 HBase 操作的版本维度的行为。

29.2.1。获取/扫描

获取是在 Scans 之上实现的。以下对 Get 的讨论同样适用于扫描

默认情况下,即如果未指定显式版本,则在执行get时,将返回版本具有最大值的单元格(可能是也可能不是最新编写的单元格,请参阅后面的内容)。可以通过以下方式修改默认行为:

  • 要返回多个版本,请参阅 Get.setMaxVersions()

  • 要返回最新版本,请参见 Get.setTimeRange()

    要检索小于或等于给定值的最新版本,从而在某个时间点给出记录的“最新”状态,只需使用从 0 到所需版本的范围,并将最大版本设置为 1 。

29.2.2。默认获取示例

以下 Get 将仅检索行的当前版本

  1. public static final byte[] CF = "cf".getBytes();
  2. public static final byte[] ATTR = "attr".getBytes();
  3. ...
  4. Get get = new Get(Bytes.toBytes("row1"));
  5. Result r = table.get(get);
  6. byte[] b = r.getValue(CF, ATTR); // returns current version of value

29.2.3。 Versioned 获取示例

以下 Get 将返回该行的最后 3 个版本。

  1. public static final byte[] CF = "cf".getBytes();
  2. public static final byte[] ATTR = "attr".getBytes();
  3. ...
  4. Get get = new Get(Bytes.toBytes("row1"));
  5. get.setMaxVersions(3); // will return last 3 versions of row
  6. Result r = table.get(get);
  7. byte[] b = r.getValue(CF, ATTR); // returns current version of value
  8. List<KeyValue> kv = r.getColumn(CF, ATTR); // returns all versions of this column

29.2.4。放

执行 put 总是在某个时间戳创建cell的新版本。默认情况下,系统使用服务器的currentTimeMillis,但您可以在每列级别自己指定版本(=长整数)。这意味着您可以在过去或将来分配时间,或者将长值用于非时间目的。

要覆盖现有值,请在与要覆盖的单元格完全相同的行,列和版本上执行 put。

隐式版本示例

HBase 将使用当前时间隐式地对以下 Put 进行版本控制。

  1. public static final byte[] CF = "cf".getBytes();
  2. public static final byte[] ATTR = "attr".getBytes();
  3. ...
  4. Put put = new Put(Bytes.toBytes(row));
  5. put.add(CF, ATTR, Bytes.toBytes( data));
  6. table.put(put);
显式版本示例

以下 Put 具有显式设置的版本时间戳。

  1. public static final byte[] CF = "cf".getBytes();
  2. public static final byte[] ATTR = "attr".getBytes();
  3. ...
  4. Put put = new Put( Bytes.toBytes(row));
  5. long explicitTimeInMs = 555; // just an example
  6. put.add(CF, ATTR, explicitTimeInMs, Bytes.toBytes(data));
  7. table.put(put);

注意:HBase 内部使用版本时间戳来处理生存时间计算等事情。通常最好避免自己设置此时间戳。首选使用行的单独时间戳属性,或将时间戳作为行键的一部分,或两者兼而有之。

29.2.5。删除

有三种不同类型的内部删除标记。请参阅 Lars Hofhansl 的博客,讨论他试图添加另一个,扫描 HBase:前缀删除标记

  • 删除:对于特定版本的列。

  • 删除列:适用于列的所有版本。

  • 删除系列:适用于特定 ColumnFamily 的所有列

删除整行时,HBase 将在内部为每个 ColumnFamily 创建一个逻辑删除(即不是每个单独的列)。

通过创建 墓碑 标记删除工作。例如,假设我们要删除一行。为此您可以指定版本,否则默认使用currentTimeMillis。这意味着 删除版本小于或等于此版本 的所有单元格。 HBase 从不修改数据,因此例如删除不会立即删除(或标记为已删除)存储文件中与删除条件对应的条目。相反,编写了一个所谓的 墓碑 ,它将掩盖已删除的值。当 HBase 进行重大压缩时,会处理墓碑以实际删除死亡值以及墓碑本身。如果删除行时指定的版本大于行中任何值的版本,则可以考虑删除整行。

有关删除和版本控制如何交互的信息性讨论,请参阅用户邮件列表中的线程 Put w / timestamp→Deleteall→Put w / timestamp failed

有关内部 KeyValue 格式的更多信息,另请参见 keyvalue

除非在列族中设置KEEP_DELETED_CELLS选项,否则在下一次主要压缩存储期间将清除删除标记(请参阅保留已删除的单元格)。要将删除保留一段可配置的时间,可以通过 hbase-site.xml 中的 hbase.hstore.time.to.purge.deletes 属性设置删除 TTL。如果未设置hbase.hstore.time.to.purge.deletes或设置为 0,则在下一次主要压缩过程中将清除所有删除标记,包括将来具有时间戳的标记。否则,将保留具有将来时间戳的删除标记,直到在标记的时间戳加上hbase.hstore.time.to.purge.deletes的值所表示的时间之后发生的主要压缩,以毫秒为单位。

此行为表示修复了 HBase 0.94 中引入的意外更改,并在 HBASE-10118 中修复。该变更已被移植到 HBase 0.94 和更新的分支机构。

29.3。 HBase-2.0.0 中的可选新版本和删除行为

hbase-2.0.0中,操作员可以通过将列描述符属性NEW_VERSION_BEHAVIOR设置为 true 来指定备用版本并删除处理(要在列族描述符上设置属性,必须先禁用该表,然后更改列族描述符) ;有关编辑列族描述符属性的示例,请参阅保留已删除单元格

“新版本行为”撤消了下面列出的限制,即如果在同一位置,Delete总是遮蔽Put - 即相同的行,列族,限定符和时间戳 - 无论哪个首先到达。版本记帐也会更改,因为已删除的版本被视为总版本计数。这样做是为了确保在主要压缩进行调解时结果不会改变。请参阅HBASE-15968并链接问题以供讨论。

目前使用这种新配置运行成本;我们在每次比较时考虑 Cell MVCC,因此我们燃烧了更多的 CPU。减速将取决于。在测试中,我们发现降解率在 0%到 25%之间。

如果复制,建议您使用新的串行复制功能运行(请参阅HBASE-9465;串行复制功能未进入hbase-2.0.0但应该在后续的 hbase-2.x 版本中运行),因为现在订单突变到来是一个因素。

29.4。目前的限制

以下限制在 hbase-2.0.0 中得到解决。请参阅上面的部分,可选新版本和 HBase-2.0.0 中的删除行为。

29.4.1。删除蒙版 Puts

删除掩码放置,甚至删除输入删除后发生的放置。参见 HBASE-2256 。请记住,删除会写一个逻辑删除,只有在下一次主要压缩运行后才会消失。假设你删除了所有内容⇐T。在此之后你做了一个带有时间戳⇐T 的新 put。这个 put,即使它发生在删除之后,也会被删除墓碑掩盖。执行看跌期权不会失败,但是当你做到获得时你会注意到看跌期权没有效果。在主要压缩运行后它将再次开始工作。如果您对行添加使用始终增加的版本,则这些问题不应成为问题。但即使你不关心时间,它们也会发生:只需删除并立即放在彼此之后,并且它们有可能在同一毫秒内发生。

29.4.2。主要压缩会更改查询结果

…在 t1,t2 和 t3 创建三个单元版本,最大版本设置为 2.因此,在获取所有版本时,仅返回 t2 和 t3 处的值。但是如果你在 t2 或 t3 删除了版本,t1 上的版本将再次出现。显然,一旦主要的压缩运行,这样的行为将不再是这种情况…… (参见 垃圾收集 在 HBase 中的弯曲时间。)

30.排序顺序

所有数据模型操作 HBase 按排序顺序返回数据。首先是行,然后是 ColumnFamily,后面是列限定符,最后是时间戳(反向排序,因此首先返回最新的记录)。

31.列元数据

ColumnFamily 的内部 KeyValue 实例之外没有列元数据的存储。因此,尽管 HBase 不仅可以支持每行的大量列,而且还可以支持行之间的异类列,因此您有责任跟踪列名称。

获取 ColumnFamily 存在的完整列的唯一方法是处理所有行。有关 HBase 如何在内部存储数据的更多信息,请参阅 keyvalue

32.加入

HBase 是否支持连接是 dist-list 上的常见问题,并且有一个简单的答案:它不会,尤其是 RDBMS 支持它们的方式(例如,在 SQL 中使用 equi-joins 或 outer-joins) )。如本章所述,HBase 中的读取数据模型操作是 Get 和 Scan。

但是,这并不意味着您的应用程序不支持等效的连接功能,但您必须自己完成。两个主要策略是在写入 HBase 时对数据进行非规范化,或者使用查找表并在应用程序或 MapReduce 代码中的 HBase 表之间进行连接(并且正如 RDBMS 所示,有几种策略可以根据大小而定这些表,例如,嵌套循环与散列连接)。那么哪种方法最好?这取决于你想要做什么,因此没有一个答案适用于每个用例。

33.酸性

参见 ACID 语义。 Lars Hofhansl 还在 HBase 中写了关于 ACID 的说明。