Data Model

在Hbase中数据存于行列表中。这个和关系型数据是一个术语的重复,但不是一个有效的比喻。反而,把Hbase表想成多维图更合意。

HBase Data Model Terminology (HBase数据模型专业术语)

Table

由多行组成的HBase表

Row

HBase中的一个Row由一个row key和一个或多个带有相关值的columns组成。Rows是按row key的字母序列排序的。因为这个原因,row key的设计十分重要。以这种方式存储数据的目的是使相关的rows距离相近。一个常见的row key模式是网页域名。如果row key是域名,那你应该反过来存储它们 (org.apache.www, org.apache.mail, org.apache.jira),这样所有的Apache域名在表中都在一起,而不会根据子域的第一个字母而分散开。

Column

HBase中的Comlumn由一个column family 和一个column qualifier组成,二者由:分隔

Column family

通常出于性能原因,Column families将一套columns和它们的值在物理上定位在一起(colocate),每个column family有一套存储性能,例如它的值是否应缓存到内存,它的数据是如何压缩的,或它的row keys是编码过的。表中的每行都有同样的column family,即使一个已知的row可能没在已知的column family中存储任何东西。

Column Qualifier

column qualifier是加在column family后面用来为一块给定数据提供索引的。假定column family是content(内容),一个column qualifier 可能是content:html ,或者content:pdf。尽管column family在表创建时就固定下来了,但column qualifier是可变的,而且不同行之间可能差的很多。

Cell

cell是row、column family、column qualifier、和其中包含值、时间戳的组合,代表来值的版本

Timestamp

时间戳被记录随着每个值,是某个特定版本值的标识符。默认情况下,时间戳代表数据何时被写入RegionServer,但当你传入数据时,也可以指定一个不同的时间戳

19. 概念视图

You can read a very understandable explanation of the HBase data model in the blog post Understanding HBase and BigTable by Jim R. Wilson. Another good explanation is available in the PDF Introduction to Basic Schema Design by Amandeep Khurana.

阅读不同观点能帮助我们对HBase 架构设计有一个很深的理解。上面的文章包含来这小结中的观点。

The following example is a slightly modified form of the one on page 2 of the BigTable paper. 有个table叫webtalbe,其包含2行(com.cnn.wwwcom.example.www)、3个comlumn families(contents, anchor,people )。这个例子中,对一行com.cnn.www,作者包含两列(anchor:cssnsi.com, anchor:my.look.ca),内容包含一列(contents:html)。row keycom.cnn.www包含5个版本的row,而row keycom.example.www只有一个版本的key。column qualifier contents:html包含已知网页的全部HTML。column familyanchor的qualifier包含外部页面,可以链接至row中代表的网页,along with the text it used in the anchor of its link。column family people连接这个网页的人。

column name

按照惯例,一个column名字是由comlumn family前缀和一个qualifier组成。例如,列contents:html是由column family contents 和 qualifier html 组成。‘冒号:’从column family qualifier中限定出column family

Table 4. Table webtable

ROW KEY Time Stamp ColumnFamily
contents
ColumnFamily
anchor
ColumnFamily
people
“com.cnn.www” t9 anchor:cnnsi.com=”CNN”
“com.cnn.www” t8 anchor:my.look.ca =”CNN.com”
“com.cnn.www” t6 contents:html= “…”
“com.cnn.www” t5 contents:html= “…”
“com.cnn.www” t3 contents:html= “…”

这个表中的Cells似乎是空的而不占空间,但在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. }

20. 物理视图

尽管在概念试图中,表被看成稀疏行的集合,但物理上它们是按column family存储的。一个新的column qualifier可以随时加入到一个一存在的column family中。

Table 5. ColumnFamily anchor

Row key Time Stamp Column Family anchor
“com.cnn.www” t9 anchor:cnnsi.com = “CNN”
“com.cnn.www” t9 anchor:my.look.ca = “CNN.com”

Table 6. ColumnFamily contents

Row key Time Stamp Column Family contents:
“com.cnn.www” t6 contents:html = “…​”
“com.cnn.www” t5 contents:html = “…​”
“com.cnn.www” t3 contents:html = “…​”

概念视图中的空格没有被存储.因此,当请求列contents:html中时间戳t8时的值是,其返回为无值.同样的,请求时间戳t9时的anchor:my.look.ca值时返回无值。当然,如果没有提供时间戳,特定列的最近的值将被返回。至于多个版本,最近的是指第一个被找到的(时间戳降序排列)。因此,当请求行com.cnn.www中所有的列值时,如果没有指定时间戳,将得到the value of contents:html from timestamp t6, the value of anchor:cnnsi.com from timestamp t9, the value of anchor:my.look.ca from timestamp t8

21. 命名空间

在关系型数据库系统中,一个命名空间是一个相似表的逻辑集合。这个抽象概念为下面这些多层租户相关的特征奠定来基础:

× 定额管理(Quota Management):限制一个命名空间可以消耗的资源数量

× 命名空间安全管理(NameSpace Security Administration):为租户提供其他安全管理等级

× 区域服务器群(Region server groups) :一个命名空间或表可以被固定到一个RS的子集上,这样能保证进程级的独立性(a course level of isolation)

21.1. 命名空间管理

命名空间可以被创建、移动、和替换。命名空间的成员是在表创建时决定的,通过以下形式指定满足条件的表名:

  1. <table namespace>:<table qualifier>

Example 12. Examples

Create a namespace

  1. create_namespace 'my_ns'

create my_table in my_ns namespace

create ‘my_ns:my_table’, ‘fam’

drop namespace

drop_namespace ‘my_ns’

alter namespace

alter_namespace ‘my_ns’, {METHOD => ‘set’, ‘PROPERTY_NAME’ => ‘PROPERTY_VALUE’}

21.2 预定义的命名空间

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

× hbase- 系统命名空间,包含HBase 内部的表

× default- 没有具体指定命名空间的表将自动进入这个命名空间

Example 13. Examples

namespace=foo and table qualifier=bar

  1. create 'foo:bar', 'fam'

namespace=default and table qualifier=bar

  1. create 'bar', 'fam'

22. 表 Table

表是在schema声明的时候定义的。

23. 行 Row

行键是不可分割的字节数组。行按字典排序由低到高存储在表中。空的字节数组是用来标识表命名空间的起始和结束。

24. 列族 Column Family

是Apache Hbase中列的集合。一个列族中的所有列成员有相同的前缀,例如列 courses:history和列courses:math都是列族coursees的成员。冒号作为分隔符,分隔列名和前缀。列族前缀必须是可以打印的字符。剩下的叫做列族的qualifier,可以由任意字节组成。列族必须在表建立时声明,而列不用,且可以随时加入。

物理上,所有类族成员在文件系统中存储在一起。因为tunings和storage specifications都是在列族上进行的,所以建议列族的所有成员都采用同样的访问方式和尺寸特征。

25. Cells

HBase中元组 {row, column, version}称为一个Cell,其内容是不可分割的字节。

26. 数据模型操作

四个主要的操作是Get, Put, Scan和Delete。这些操作是对Table实例进行的

26.1. Get

返回特定行的属性,Gets通过Table.get执行

26.2 Put

在表中增加新的一行(如果key值不存在),可以更新存在的一行(key已经存在)。通过Table.put(写缓冲)或Table.batch(不写缓冲)执行

26.3 Scans

Scans允许某个属性的多行结果迭代

下面是一个Table实例的Scan操作举例。假定一个table中有keys为“row1”、“row2”、“row3”的的行,以及keys为“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();
  15. }

Note that generally the easiest way to specify a specific stop point for a scan is by using the InclusiveStopFilter class.

26.4. Delete

Delete从表中删除一行。Table.delete

HBase不会去修改数据,所以删除操作是同过添加一个新的标记“tombstones”来进行的,当主合并阶段,这些“tombstones”和死值才被清除。

See version.delete for more information on deleting versions of columns, and see compaction for more information on compactions.

27. Versions

HBase中元组 {row, column, version}称为一个Cell,可能会有许多cells的行和列是相同,这些cells通过version来区分

行和列值使用字节数组表示的,而version是一个长整型类型。这个long值包含java.util.Date.getTime()或 System.currentTimeMillis()返回的时间实例,即当前时间和1970-01-01 UTC的时间差,单位毫秒

HBase的版本是按降序排列的,所以当从一个存储的文件中读取数据时,最先找到最近的值。

会有很多人不理解HBase中cell的版本。如:

× 如果多次写入cell具有同一个版本,只有最后一次写入是可取出的

× 写入到非增版本顺序的cells是可以的。

下面我们描述version现在在HBase中是如何工作的。See HBASE-2406 for discussion of HBase versions. Bending time in HBase makes for a good read on the version, or time, dimension in HBase. It has more detail on versioning than is provided here. As of this writing, the limitation Overwriting values at existing timestamps mentioned in the article no longer holds in HBase. This section is basically a synopsis of this article by Bruno Dumon.

27.1. 指定存储的版本号

对一个给定列的最大存储版本数量是列表的一部分,且在创建时指定,通过alter命令或HColumnDescriptor.DEFAULT_VERSIONS。Prior to HBase 0.96, the default number of versions kept was 3, but in 0.96 and newer has been changed to 1.

Example 14. Modify the Maximum Number of Versions for a Column Family

这个例子是使用HBase Shell来保存column famliy f1所有列的最多5个版本,也可以使用HColumnDescriptor

  1. hbase> alter 't1', NAME=> 'f1', VERSION=> 5

Example 15. Modify the Minimum Number of Versions for a Column Family

也可以指定存储每个列族的最少版本树。默认情况下,这个值是0,意思是这个功能是禁用的,下面例子是设置列族f1所有列的最少版本数量为2。通过Hbase shell,也可以用HColumnDescriptor

hbase> alter ‘t1’, NAME=> ‘f1’, MIN_VERSIONS=> 2

Starting with HBase 0.98.2, you can specify a global default for the maximum number of versions kept for all newly-created columns, by setting hbase.column.max.version in hbase-site.xml. See hbase.column.max.version.

27.2 版本和HBase操作

在这一章我们来仔细看看在HBase的各个主要操作中版本起到了什么作用。

27.2.1 Get/Scan

Gets是在Scans的基础上实现的。下面Get的操作也使用Scans

默认情况下,当不指定版本时,执行get,返回的是最大版本号的cell(可能不是最后被修改的那一个),默认行为可用下面的方法修改:

  • 返回更多版本,见Get.setMaxVersions()
  • 返回版本不只最新的,见Get.setTimeRange()
    要想取得最新版本小于或等于某个给定值,因此要在某个时间点及时给记录设定“latest”状态,使用0到某个想要的版本,并把最大版本数设为1.

27.2.2 Default Get Example

下面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);

27.2.3. Versioned Get Example

下面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); //将返回行的最后3个版本
  6. Result r = table.get(get);
  7. byte[] b = r.getValue(CF, ATTR); //返回当前版本打值
  8. List<KeyValue> kv = r.getColumn(CF,ATTR); //返回这一列打所有版本

27.2.4 Put

执行put总会在某个时间戳创建一个cell的新版本。默认情况系统使用服务器的currentTimeMillis。但你也可以为每列指定版本(长整型数据),这意味着你可以设定一个过去或未来的时间,或使用一个非时间值。

如果你想覆盖某个cell,就对这个cell 执行put相同的row、column和版本就可以覆盖存在的值。

Implicit Version Example

下面的Put没有指明版本,Hbase将使用现在的时间

  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);

Explicit Version Example

这里的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 exolicitTimeInMs = 555;
  6. put.add(CF, ATTR, explicitTimeInMs, Bytes.toBytes(data));
  7. table.put(put);

注意:时间戳版本被Hbase内部用来进行像计算生命周期之类的事情。所以最好不要自己设置时间戳。推荐使用行的分隔时间戳属性或使时间戳作为row key的一部分,或两者都采取。

27.2.5 删除

有三种不同内部删除标记。

  • Delete:删除列的特定版本
  • Delete column: 删除列的所有版本
  • Delete family: 特定列族的所有列

当删除整行时Hbase会在内部为每个列族创建一个tombstone标志(不是为每个单独的列)

删除工作通过创建tombstone标记完成。例如,假定我们想要删除一列,为此你可以指定一个版本或使用默认的currentTimeMillis。这意味着删除比这个版本更早的所有版本。Hbase不会去修改数据,即一个delete操作不会立刻删除满足条件的整个存储在文件中的数据。相反,一个叫tombstone的用来标记那些要删除的值。当HBase进行主compaction时,那些tombstones就被处理来移除死值以及tombstone标志。若你指定的版本比行中任意版本都晚,你可以认为整行都要被删除。

For an informative discussion on how deletes and versioning interact, see the thread Put w/timestamp → Deleteall → Put w/ timestamp fails up on the user mailing list.

Also see keyvalue for more information on the internal KeyValue format.

删除标志会在下个存储的主compaction时清除,除非在列族中设置了KEEPDELETED_CELLS选项。如果想多保持要删除标志一段时间(可以配置),可以在hbase-site.xml中修改hbase.hstore.time.to.purge.deletes (To keep the deletes for a configurable amount of time, you can set the delete TTL via the hbase.hstore.time.to.purge.deletes property in hbase-site.xml).如果hbase.hstore.time.to.purge.deletes被设置成0或没有设置,所有的删除标记,包括未来时间戳,将在下个主compaction时被清除。否则,具有未来时间戳的删除标志将保留下来,直到时间戳加上hbase.hstore.time.to.purge.deletes_的值所代表的时间之后的主compaction发生。

27.3. 现有限制

27.3.1. 删除标志标记Puts

删除标志标记puts,即使这个puts发生在删除标志加入之后。记住删除标志写下一个tombstone,这个tombstone只在下个主compaction进行时才会消失。假设你执行删除所有时间=T的数据,之后你又新输入一个时间戳=T的数据。这个输入,即使是发生在删除操作之后,也会被标记为tombstone。执行这个put不会失败,但是当你执行get时,你会注意到put没有生效。只有一个主compaction运行后,才会恢复正常。如果你的puts总是使用升序的版本,这个问题不会有什么影响。但即使你不关心时间,这个也会发生:删除和puts相互跟随,会有可能它们发生在同毫秒内(just do delete and put immediately after each other, and there is some chance they happen within the same millisecond)

27.3.2. 主compaction改变查询结果

当最大版本被设为2时,在t1、t2、t3创建三版cell。当使用get获取所有版本时,只能得到t2和t3的值。但如果你删除t2或t3处的版本,t1处又会出现。很明显,一旦主compaction进行,结果将不一样。(See Garbage Collection in Bending time in HBase.)

28. 排序 Sort Order

所有数据模型操作Hbase按序返回。First by row, then by ColumnFamily, followed by column qualifier, and finally timestamp (sorted in reverse, so newest records are returned first).

29. 列的元数据 Column Metadata

对列族来说,没有内部键值之外存储列元数据的例子(列的数据只存在Keyvalue内部)。这样,Hbase不仅支持每行多列,还要支持行间列不同。由你来追踪列名。

对列族来说,处理所有行才能获得存在的完整列,For more information about how HBase stores data internally, see keyvalue.

30. joins

HBase是否支持joins是dist-list上很常见的问题,简单来说,不支持,至少不像RDBMS那样支持,如(如 SQL中带 equi-joins 或 outer-joins),正如本章描述的,HBase中读数据模型操作是Get和Scan

当然,那不意味着equivalent join不能在应用程序中使用,但你要自己实现它。两种方法,非规范化将要写入Hbase中的数据,或创建查找表并在你的应用或MR代码中的HBase表间做join(如RDBMS中那样,有几个方法来实现,依赖于表的大小,如nested loops和hash-joins)。所以哪个才是最好的方法?,这由你正在作出事情决定,没有固定的答案。

31. ACID

See ACID Semantics. Lars Hofhansl has also written a note on ACID in HBase.