{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 撰写的概要。
要存储给定列的最大版本数是列模式的一部分,该值在表创建时指定,或通过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 。
在本节中,我们将查看每个HBase 核心操作的版本维度的行为。
Get是在 Scans 之上实现的。以下对 Get 的讨论同样适用于扫描。
默认情况下,即如果未指定显式版本,则在执行get
时,将返回版本具有最大值的单元格(可能是也可能不是最新编写的单元格,请参阅后面的内容)。可以通过以下方式修改默认行为:
要返回多个版本,请参阅 Get.setMaxVersions()
要返回最新版本以外的其他版本,请参见 Get.setTimeRange()
要检索小于或等于给定值的最新版本,从而在某个时间点给出记录的“最新”状态,只需使用从 0 到所需版本的范围,并将最大版本设置为 1 。
以下 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
以下 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
执行 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 内部使用版本时间戳来处理生存时间计算等事情。通常最好避免自己设置此时间戳。首选使用行的单独时间戳属性,或将时间戳作为行键的一部分,或两者都使用。
有三种不同类型的内部删除标记。请参阅 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 和更新的分支机构。
在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 版本中发布),就像现在一样,突变到达的顺序是一个关键因素。
以下限制在 hbase-2.0.0 中得到解决。请参阅上面的部分,可选新版本和 HBase-2.0.0中的删除行为。
Deletes操作屏蔽Puts操作,甚至Puts操作发生在Deletes操作之后。参见 HBASE-2256 。请记住,删除会写一个tombstone(逻辑删除标记),只有在下一次主要压缩运行后才会消失。假设你删除了所有内容⇐T。在此之后你运行了一个带有时间戳⇐T 的新 put。这个 put,即使它发生在删除之后,也会被删除tombstone标记屏蔽。同时执行put操作并不会失败,但是当执行get时,您会注意到put操作没有效果。在主要压缩运行后它将再次开始工作。如果您对行添加使用始终增加的版本,则这些问题不应成为问题。但即使你不关心时间,它们也会发生:只需完成删除并立刻进行put操作,它们有可能在同一毫秒内发生。
...在 t1,t2 和 t3 创建三个单元版本,最大版本设置为 2.因此,在获取所有版本时,仅返回 t2 和 t3 处的值。但是如果你在 t2 或 t3 删除了版本,t1 上的版本将再次出现。显然,一旦主压缩运行,这样的行为将不再存在......(参见 HBase中的弯曲时间 中的垃圾回收部分)