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

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

HBase版本维度按递减顺序存储,以便在从存储文件中读取时,首先找到最近的值。

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

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

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

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

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

29.1.指定要存储的版本数

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

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

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

hbase> alter ‘t1′, NAME => ‘f1′, VERSIONS => 5 

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

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

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.Get/Scan

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

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

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

  • 要返回最新版本以外的其他版本,请参见 Get.setTimeRange()

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

29.2.2.默认版本的Get示例

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

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

29.2.3.给定版本的Get示例

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

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

29.2.4.Put

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

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

隐式版本示例

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

public static final byte[] CF = "cf".getBytes();
public static final byte[] ATTR = "attr".getBytes();
...
Put put = new Put(Bytes.toBytes(row));
put.add(CF, ATTR, Bytes.toBytes( data));
table.put(put); 

显式版本示例

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

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

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

29.2.5.Delete

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

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

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

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

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

通过创建tombstone标记删除工作。例如,假设我们要删除一行。为此您可以指定版本,否则默认使用currentTimeMillis。这意味着 删除版本小于或等于此版本的所有单元格。 HBase 从不在原址修改数据,因此例如delete操作不会立即删除(或标记为已删除)存储文件中与delete条件相对应的条目。相反,标记一个tombstone标志,它将屏蔽删除的值。当HBase进行主要压缩时,将处理tombstone来实际删除值,同时删除tombstone标记。如果删除行时指定的版本大于行中任何值的版本,则删除整行。

有关删除和版本控制如何交互的信息性讨论,请参阅用户邮件列表中的思路 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以及链接的问题。

目前使用这种新配置运行成本;每次比较时,我们都将计算单元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.Deletes屏蔽Puts

Deletes操作屏蔽Puts操作,甚至Puts操作发生在Deletes操作之后。参见 HBASE-2256 。请记住,删除会写一个tombstone(逻辑删除标记),只有在下一次主要压缩运行后才会消失。假设你删除了所有内容⇐T。在此之后你运行了一个带有时间戳⇐T 的新 put。这个 put,即使它发生在删除之后,也会被删除tombstone标记屏蔽。同时执行put操作并不会失败,但是当执行get时,您会注意到put操作没有效果。在主要压缩运行后它将再次开始工作。如果您对行添加使用始终增加的版本,则这些问题不应成为问题。但即使你不关心时间,它们也会发生:只需完成删除并立刻进行put操作,它们有可能在同一毫秒内发生。

29.4.2.主压缩会更改查询结果

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