首页  >  文章  >  数据库  >  HBase MVCC 和内置原子操作

HBase MVCC 和内置原子操作

WBOY
WBOY原创
2016-06-07 16:28:101078浏览

作者:Lars Hofhansl(这是我今年 3 月发布的 HBase 中的 ACID 帖子的后续内容)HBase 有一些特殊的原子操作:checkAndPut、checkAndDelete - 这些只是检查列的值作为前提条件,然后应用 Put或 D

作者:Lars Hofhansl

(这是我今年 3 月份在 HBase 中的 ACID 帖子的后续)
HBase 有一些特殊的原子操作:
  • checkAndPut、checkAndDelete - 这些只是检查列的值作为先决条件,然后如果检查成功则应用 Put 或 Delete。
  • 增量、追加 - 这些允许原子添加到解释为整数,或附加到列的末尾,分别
checkAndPut 和 checkAndDelete 是幂等的,因为它们可以安全地多次应用(但请注意,当在重试)。

增量和追加不是幂等的。它们是 HBase 中唯一不可重复的操作。 Increment 和 Append 也是唯一 HBase 的 MVCC 模型提供的快照隔离不足以满足要求的操作......稍后会详细介绍。

事实证明, checkAndPut 和 checkAndDelete 并不像预期的那样原子(这个问题是由 Gregory 提出的,尽管我一开始并不相信他是对的 - 请参阅 HBASE-7051)。

看一下代码就可以很明显地看出这一点:
一些 Put 优化 (HBASE-4528) ) 允许在更改同步到 WAL 之前释放行锁。这也要求在提交 MVCC 更改之前释放锁,以便在保证更改持久之前,更改对其他事务不可见。
获取行锁以进行原子更改的另一个操作(例如 checkAndXXX)可能会事实上,尽管持有行锁,但看不到当前的情况,因为仍然可能存在未完成的 MVCC 更改,这些更改只有在行锁释放并重新获取后才可见。因此它可能会在陈旧的数据上运行。 HBASE-4528 后,持有行锁已经不够好了。
Increment 和 Append 也有同样的问题。

这部分的修复相对简单:我们需要某种“MVCC 屏障”。我们不是在更新阶段结束时完成单个 MVCC 事务(这将等待所有先前的事务完成),而是早一点等待先前的事务完成
之前我们开始检查或获取原子操作的阶段。这只会稍微降低并发性,因为在操作结束之前我们无论如何都必须等待所有先前的事务。 HBASE-7051 正是针对 checkAndXXX 操作执行此操作。
此外 - 如上所述 - Increment 和 Append 还有另一个问题,
它们需要是可序列化的事务。快照隔离不够好例如:如果您从 0 开始并发出增量 1 和另一个增量 2,则结果必须始终为 3。如果两者可以以相同的起始值开始(a快照)结果可能是 1 或 2,具体取决于哪一个先完成。

增量和追加目前通过一个丑陋的“黑客”解决了这个问题:当他们将更改写入memstore时,他们将所有新KeyValues的memstoreTS设置为0!结果是它们立即对其他事务可见,违反了 HBase 的 MVCC。再次参见 HBase 中的 ACID 以获取 memstoreTS 的说明。
这保证了并发增量和追加操作的正确结果,但对并发扫描器的可见性并不是您所期望的。即使扫描仪应该看到数据的早期快照,即使对于部分行,增量和附加值也可以随时对任何扫描仪可见。
增量和附加也设计用于非常高的吞吐量,因此它们实际上操纵 HBase memstore 删除刚刚修改的列的旧版本。因此,您会丢失更改的版本历史记录,以换取避免内存存储因许多增量或追加的版本而爆炸。这在 HBase 中称为“更新插入”。 Upsert 很好,因为它可以防止内存存储被填满,如果没有人关心它们,就会有很多旧值。缺点是这是对 memstore 的特殊操作,并且很难正确执行。 MVCC。它也不适用于 mslab(请参阅此 Cloudera 博客文章以了解 mslab 的说明)。

如果您不关心可见性,这是​​一个简单的问题,因为您可以只查看内存存储并删除旧的价值观。不过,如果您关心 MVCC,则必须首先证明删除 KV 是安全的。

我几乎在一年前尝试修复此问题(HBASE-4583),但经过与同事的一些讨论后我们集体放弃了这一点。

几天前,我重新打开了 HBASE-4583,并开始使用一个激进的补丁,该补丁消除了所有 upsert 类型的逻辑(将 memstoreTS 设置为 0),并在开始增量/追加之前等待先前的事务。然后,我依靠对 HBASE-4241 的更改,仅在将内存存储刷新到磁盘时刷新所需列的版本。事实证明这仍然慢了很多(10-15%),因为它需要经常刷新内存存储,即使它会导致大部分空文件。不过,这仍然是一个很好的尝试,因为它摆脱了很多特殊代码,并将增量和追加变成普通的 HBase 公民。

第二个不太激进的版本使 upsert MVCC 意识到。

那就是实际上并不像看起来那么容易。为了从 memstore 中删除列的版本(KeyValue),您必须证明它不会被任何并发或未来的扫描器看到。这意味着我们必须找到任何扫描仪的最早读取点确保至少有一个版本的KV早于该最小读取点;然后我们就可以安全地从 memstore 中删除该 KV 的所有旧版本 - 因为任何扫描仪都保证能看到更新版本的 KV。
中的“不太激进”补丁正是这样做的。

在最后,我最终使用 HBASE-4583 提交的补丁同时执行以下两项操作:
如果要递增或追加的列的列族的版本设置为 1,我们会执行补丁添加的 MVCC 感知更新插入。如果 VERSIONS 是 > 1、我们使用通常的逻辑向memstore添加一个KeyValue。所以现在这在所有情况下都符合预期。如果请求多个版本,它们将被保留,并且即使使用增量和追加,时间范围查询也将起作用;当 VERSIONS 设置为 1 时,它还保留了(大部分)性能特征。

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn