在HBase 中,一个{row,column,version}元组精确指定了一个 cell。可能有无限数量的单元格,其中行和列是相同的,但单元格地址仅在其版本维度上有所不同。
虽然行和列键以字节表示,但版本是使用长整数指定的。通常,这个long包含时间实例,如由java.util.Date.getTime() 或者 System.currentTimeMillis() 返回的时间实例,即:1970年1月1日UTC的当前时间和午夜之间的差值(以毫秒为单位)。
HBase 版本维度按递减顺序存储,因此从存储文件读取时,会先查找最新的值。
在HBase 中,cell 版本的语义有很多混淆。尤其是:
- 如果对一个单元的多次写入具有相同的版本,则只有最后一次写入是可以读取的。
- 以非递增版本顺序编写单元格是可以的。
下面我们描述 HBase 当前的版本维度是如何工作的。HBase 中的弯曲时间使得 HBase 中的版本或时间维度得到很好的阅读。它在版本控制方面的细节比这里提供的更多。
在撰写本文时,文章中提到的限制覆盖现有时间戳的值不再适用于HBase。本节基本上是 Bruno Dumon 撰写的文章的简介。
指定要存储的HBase版本数量
为给定列存储的最大版本数是列架构的一部分,并在创建表时通过 alter 命令 HColumnDescriptor.DEFAULT_VERSIONS 指定 。在 HBase 0.96 之前,保留的版本的默认数量是3,但在 0.96 以及新版本中已更改为1。
示例– 修改一个列族的最大版本数量
本示例使用HBase Shell来保留列族中所有列的最多5个版本f1。你也可以使用HColumnDescriptor。
hbase> alter ‘t1′, NAME => ‘f1′, VERSIONS => 5
示例– 修改列族的最小版本数
您还可以指定每列家族存储的最低版本数。默认情况下,它被设置为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 操作
在下面的内容中,我们将了解每个核心 HBase 操作的版本维度的行为。
获取/扫描(Get/Scan)
获取在Scans 之上实现。以下关于 Get 的讨论同样适用于 Scans。
默认情况下,即如果你没有指定明确的版本,则在执行“get”操作时,会返回其版本值最大的单元格(可能是也可能不是最新版本,请参阅后面的内容)。默认行为可以通过以下方式进行修改:
- 要返回多个版本,请参阅 Get.setMaxVersions()
- 要返回除最新版本以外的其他版本,请参阅 Get.setTimeRange(),要检索小于或等于给定值的最新版本,从而在某个时间点给出记录的“最新”状态,只需使用从0到所需版本的范围,并将最大版本设置为1 。
默认获取示例
以下获取将只检索行的当前版本:
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(写)
在某个时间戳处进行Put(写)操作总是会创建一个新版本的 cell。默认情况下,系统使用服务器的 currentTimeMillis,但您可以在每列级别上自己指定版本(等于长整数)。这意味着您可以分配过去或未来的时间,或将长时间值用于非时间目的。
要覆盖现有值,请执行与要覆盖的单元格中的行、列和版本完全相同的 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 内部使用,用于诸如生存时间计算之类的事情。通常最好避免自己设置时间戳。优先使用该行的单独时间戳属性,或者将时间戳记作为行键的一部分,或者同时使用两者。
删除(delete)
有三种不同类型的内部删除标记。并添加另一个扫描 HBase:Prefix Delete Marker。
- 删除(Delete):针对特定版本的列。
- 删除列(Delete column):适用于所有版本的列。
- 删除系列(Delete family):针对特定 ColumnFamily 的所有列
当删除整行时,HBase 将在内部为每个 ColumnFamily(即不是每个单独的列)创建一个逻辑删除。
通过创建 tombstone标记来删除作品。例如,假设我们想要删除一行。为此,您可以指定一个版本,或者默认情况下使用该 currentTimeMillis。这意味着删除版本小于或等于此版本的所有单元格。HBase 从不修改数据,例如删除不会立即删除(或标记为已删除)存储文件中对应于删除条件的条目。相反,所谓的 tombstone被写入,这将掩盖已删除的值。当 HBase 进行重大压缩时,tombstone将被处理以实际移除不能用的值以及 tombstone本身。如果您在删除行时指定的版本大于行中任何值的版本,则可以考虑删除整行。
除非在列族中设置了 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-2.0.0中的可选新版本和删除行为
在hbase-2.0.0 中,操作员可以通过将列描述符属性 NEW_VERSION_BEHAVIOR 设置为 true 来指定备用版本和删除处理(若要在列族描述符上设置属性,您必须首先禁用表,然后改变列族描述符)。
“新版本行为”解除了以下列出的局限性,取消了以下所列的限制,即如果在同一位置,Delete 总是会超过一个Put(即相同的行,列族,限定符和时间戳),而不管哪一个先到达。版本记帐也会因为删除版本考虑到版本总数而发生变化。这是为了确保在重大压缩情况下不会改变结果。
运行这个新配置目前的成本;我们将每个比较的 Cell MVCC 因素考虑在内,因此我们消耗更多的 CPU,这是减速所取决于的,在测试中,我们已经看到0%到25%的降级。
如果正在复制,建议您使用新的串行复制功能运行,因为现在突变到达的顺序是因子。
HBase目前的局限性
以下限制在 hbase-2.0.0 中解决。请参阅上面的“HBase-2.0.0 中的可选新版本和删除行为”部分。
删除标记Put
删除掩码放入,甚至在输入删除后发生。请记住,删除操作会写入逻辑删除,只有在下一次主要压缩运行后才会消失。假设你删除了所有的* T 的内容,然后你做了一个带有时间戳 * T 的新放。这种情况下,即使在删除后发生,也会被删除 tombstone 屏蔽。执行投入不会失败,但是当你做出投注时,你会注意到投注没有任何效果。重大压缩后,它将再次开始工作。如果您使用不断增加的版本进行新的放置,则这些问题不应该是一个问题。但即使您不在乎时间,也可能发生这种情况:只需删除并立即放置在对方之后,并且有可能在同一毫秒内发生。
主要的压缩改变了查询结果
在t1,t2 和 t3 中创建三个单元格版本,最大版本设置为 2。因此,获取所有版本时,只会返回 t2 和 t3 的值。但是如果在 t2 或 t3 删除版本,则 t1 中的版本将再次出现。显然,一旦主要的压实运行后,这种行为将不会是这样了。
文章永久链接:https://ddkk.com/?p=12668