翻译一篇:HBase MVCC and built-in Atomic Operations

作者:Lars Hofhansl

HBase 有一些特殊的原子操作:

  • checkAndPut, checkAndDelete - 检查列值作为执行 put 和 delete 的前置条件,检查成功则执行。
  • Increment, Append - 对一个列 value 的原子操作,将一个整数值增加,或者在值的尾部附加(译注:我理解是非整数值,比如 String

checkAndPut 和 checkAndDelete 操作是幂等的,可以执行多次。但是在重试的过程中,如果有其他修改发生,结果可能就不一样了。

Increment 和 Append 不是幂等的。他们是 HBase 中唯一的不可重复操作。他们也是唯一的 HBase MVCC 模型提供的快照隔离机制不能完全管用的操作。

但是后来发现 checkAndPut 和 checkAndDelete 并没有像预期那样是原子的(HBASE-7051)。

通过代码很容易发现:

HBASE-4528 对 Put 做了优化,允许修改在同步到 WAL 之前释放行锁(rowlock)。这同时需要在 MVCC 修改提交之前释放锁,保证修改确认持久化之前对其他事务不可见。

其他的操作(比如 checkAndXXX)请求行锁做原子修改,尽管持有了行锁,但事实上可能并没有看到当前的快照,因为仍然有未完成的 MVCC 修改。这个快照只有在行锁被释放、重新获取之后才变成可见。所以后来的可能操作脏数据。在 HBASE-4528之后,持有行锁不再够用。

Increment 和 Append 有同样的问题。

修复 checkAndXXX 这个问题相对简单:实现一个"MVCC barrier"。比起先前在更新阶段结束的时候执行一个单独的 MVCC 事务(等待之前所有的事务结束),现在只需要在开始原子操作的检查获取(check and get)阶段之前就开始等待 ( 译注:checkAndXXX 分两个阶段,读检查阶段和修改阶段,之前是在修改阶段用一个 MVCC 事务,它会等待先前的事务结束,现在在读检查阶段就要等待先前的事务结束,避免脏读。MVCC 对于读操作是宽容的,而这个在读的时候就要等待了,所以降低了并发效率)。这样只是稍微降低了并发效率:在当前事务结束之前,需要无条件等待所有先前的事务。HBASE-7051完全是按照这样的方式实现了 checkAndXXX 操作。

前面提到,Increment 和 Append 有另一个问题,它们需要可串行化的事务。快照隔离不能满足需求。
比如:从0开始,一个操作 Increment 1,另一个操作 Increment 2,期望结果应该是3。但如果两个操作基于同一个快照0开始执行,结果要么是1,要么是2,取决于哪个先结束(显然这是不对的)。

Increment 和 Append 当前用一种很丑的办法解决了问题:当这些操作向 memstore 写入修改的时候,把所有新 KeyValue 的 memstoreTS 设置为0,效果是这些修改马上对其他事务可见,破坏了 HBase 的 MVCC 机制。查看ACID in HBase了解 memstoreTS 的含义(译注:不是一般提到的时间戳,写操作开始之前取回的先前最大的事务号,应该叫 WriteNumber,历史原因称为 memstore timestamp)。

这种做法保证了并发的 Increment 和 Append 操作的正确结果,但是对于并发 scanner 的可见度和预期是不一致的。在 Increment 和 Append 执行修改的过程中,并发的 scanner 本来应该看到先前版本的数据快照,但是现在却可以看到部分行修改的结果(译注:Increment 和 Append 修改应该是一个事务,但是前面的这种取巧的做法,使得部分修改结果提前曝光

Increment 和 Append 出于高吞吐量的设计考虑,他们会把HBase memstore 中刚刚修改后的列的老版本移除。这样一来,以丢失修改的版本历史为代价避免了 memstore 被 Increment 和 Append 的历史版本挤爆。这就是 HBase 中的 "插上(upsert)"(译注:upsert,与 insert 对比,in 表示插入,不覆盖,up 表示插上,历史版本没有了,相当于覆盖)。Upsert 的优点在于避免了 memstore 中充满"无人问津"的旧数据,缺点在于它们是 memstore 上的一类特殊操作(没有了版本历史),与 MVCCC 的理念不搭调(w.r.t. with respect to),同时它们不能与 mslab 兼容(memstore-local allocation buffers,见 Avoiding Full GCs in Apache HBase with MemStore-Local Allocation Buffers: Part 3)。

如果不关注数据可见性的话,丢掉 memstore 中老的值并不算个问题,如果坚持 MVCC 原理的话,需要先证明这样删除一个 KV 是安全的(即对并发访问没有影响)。

本人一年前试图通过 HBASE-4583来解决这个问题,但是经过与同事讨论后放弃了这个方案。

前段重新打开了 HBASE-4583,提供了一个彻底的补丁,除去了所有的 upsert 类逻辑(将 memstoreTS 设为0),统一在开始 Increment/Append 操作之前等待先前的事务。基于HBASE-4241进行了修改,在对 memstore 进行 flush 的时候,仅仅将有必要 flush 的列的版本 flush 出去。测试发现比先前慢大约10-15%,因为这种机制会频繁 flush memstore(其实很多是空文件)。但是这种方案避免了 upsert 那样逻辑特殊的代码,使得 Increment 和 Append 操作符合 HBase 的惯例(译注:符合了 MVCC 的并发控制机制),所以是值得的。

另一个不彻底的解决方案:让 upsert 操作兼顾 MVCC 。

实现起来不像说的那么简单。从 memstore 中移除一个列(一个 KeyValue 对象)的版本时候,需要证明该对象:既不被当前的并发 scanner 看到,又不会被将来的scanner看到。为了验证这个条件,需要找到所有 scanner 最早的 readpoint,并且保证这个 KV 对象至少有一个版本比那个最早的 readpoint 还要老;这样我们就可以安全地删除 memstore 中所有更老的版本了,因为所有的 scanner 都看到了更新的版本。

译注:设所有 scanner 最早的 readpoint 为t1,要删除的kv 版本的版本号为 t2,只要保证未移除的 KV 中存在一个数据版本 t3,有 t3<t1, 即所有 scanner 看到的都是 t3之后的快照,且 t2<t3 ,则说明当前要移除的 t2 是安全的,并且 t3 之前的所有版本,都可以删除

"不彻底"的补丁就是按照上面的逻辑实现的。

最终用 HBASE-4583 提交的版本是这样做的:

如果列族中有列经过 Increment 或 Append 操作后,VERSIONS 被设置为 1,执行一个兼顾 MVCC 的 upsert 操作;如果 VERSIONS>1,使用通常的逻辑(不用 upsert)将一个 KeyValue 添加到 memstore。这样在所有的情况下执行结果都符合预期:如果有请求访问多个版本,这多个版本都会被保留,即使在有 Increment 和 Append 情况下,做时间范围查找(time range)也能正常返回(译注:time range 查询查的是时间戳,会返回时间范围内所有版本的 KeyValue); 同时在 VERSIONS 为1的情况下保持了 upsert 操作的高性能。
译注:这里以 VERSIONS 作为判断的基准,是因为可以在建表的时候,设置保存 VERSIONS 的值,如果设为1,也就是默许了都是用 upsert,在折中里面选择了性能,如果设为>1,就是作者说的两种情况分别处理

[翻译]HBase 的 MVCC 和内建的原子操作的更多相关文章

  1. OpenGL ES着色器语言之语句和结构体(官方文档第六章)内建变量(官方文档第七、八章)

    OpenGL ES着色器语言之语句和结构体(官方文档第六章) OpenGL ES着色器语言的程序块基本构成如下: 语句和声明 函数定义 选择(if-else) 迭代(for, while, do-wh ...

  2. (转)8个有力的Awk内建变量

    8个有力的Awk内建变量 翻译原文:8 Powerful Awk Built-in Variableshttp://www.thegeekstuff.com/这个博客真是不错. 这篇文章是Awk Tu ...

  3. Bash shell的内建命令:type

    type指令是用来观察指令时来自于外部指令还是内建在bash中的指令. type  [-tpa]  name 选项与参数: :不加任何选项与参数时,type会显示出name是外部指令还是bash内建指 ...

  4. Bash Shell内建命令和保留字

    Bash Shell内建命令和保留字命令含义!保留字,逻辑非:不做任何事,只做参数展开.读取文件并在shell中执行它alias设置命令或命令行别名bg将作业置于后台运行bind将关键字序列与read ...

  5. Python内建的对象列表

    Python内建的对象列表 刚写Python肯定会遇到这样的情况,想写些什么,但又不知从何写起... 在我看来问题在于我们不知道有什么东东可以拿来玩,这里列出Python的内建对象,稍微归类了一下,多 ...

  6. javascript 对象初探 (四)--- 内建对象之旅之Array

     我们不要去纠结神马是内建对象,神马是內建构造器.到后来你们便会发现其实她们都是对象. Array()是一个构建数组的內建构造器函数: var arr = new Array(); 与下面的是等效的: ...

  7. javascript内建对象

    内建对象等价于内建构造器内建对象大致分为三类:数据封装类对象--Object.Array.Boolean.Number和String工具类对象--Math.Date.RegExp等用于提供遍历的对象错 ...

  8. RealtekRTL8111内建网卡-黑苹果之路

    真是服了这神一样的黑苹果.好不容易配好显卡,登陆appstore却报“无法验证您的设备或电脑”,查了一圈,又说要配网卡为en0的,有说要在clover中配FIXLAN的,最准确的是网卡必须是内建(Bu ...

  9. Paip.最佳实践-- Buildin variale 内建变量 ,魔术变量,预定义变量,系统常量,系统变量 1

    Paip.最佳实践-- Buildin variale 内建变量 ,魔术变量,预定义变量,系统常量,系统变量 1.1.1       C++内建变量(__LINE__).... 1.1.2       ...

随机推荐

  1. jsp 说明标签

    page指令 Page指令用来定义整个JSP页面的一些属性和这些属性的值. 比如我们能够用page指令定义JSP页面的contentType属性的值是text/html;charset=GB2312, ...

  2. sql小总结2

    SQL NULL 值 如果表中的某个列是可选的,那么我们可以在不向该列添加值的情况下插入新记录或更新已有的记录.这意味着该字段将以 NULL 值保存. NULL 值的处理方式与其他值不同. NULL ...

  3. js中frame的操作问题

    这里以图为例,在这里把frame之间的互相操作简单列为:1变量2方法3页面之间元素的互相获取. A  首先从 父(frameABC)------->子(frameA,frameB,frameC) ...

  4. C#操作Xml:XPath语法 在C#中使用XPath示例

    XPath可以快速定位到Xml中的节点或者属性.XPath语法很简单,但是强大够用,它也是使用xslt的基础知识. 示例Xml: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ...

  5. event.srcElement与event.target的区别

    window.event.srcElement与window.event.target 都是指向触发事件的元素,它是什么就有什么样的属性 srcElement是事件初始化目标html元素对象引用,因为 ...

  6. 【Swift初见】Swift词典

    顾名思义.当我们仰望的时候,我们将基于索引查找我们需要找到的资源.在swift这同样适用,每个对象包括字典key和value.我们key为了找到当前这个key相应的value.与数组不同的是,字典项字 ...

  7. 使用SAX解析XML文件

    SAX这是Simple API for XML缩写,它不是由引起W3C拟议标准正式.尽管如此,使用SAX很少几个,点儿全部的XML解析器都会支持它. 与DOM比較而言,SAX是一种轻量型的方法. 我们 ...

  8. PHP进口Excel至MySQL方法

    PHP-ExcelReader,下载地址: http://sourceforge.net/projects/phpexcelreader 注意点: reader.php 中的以下这行要改动  1.将 ...

  9. FindBugs:Compiler output path for module can not be null. check your module/project settings问题原因

    这可能是很多人在使用Android studio 该插件会发现此错误信息:Compiler output path for module can not be null. check your mod ...

  10. Roundabout for jQuery

    效果图: Roundabout是一个转换静态HTML元素结构为交互式播放区域的jQuery插件(而且并不仅仅是一个转盘,还有许多的形状) 首先你要下载好Jquery.min.js,和Jquery-Ro ...