HBase MVCC和内建的原子操作

来源:互联网 发布:为什么网络是脆弱的 编辑:程序博客网 时间:2024/05/18 18:46

原版英文地址:http://www.68idc.cn/help/mysqldata/nosql/20150302241120.html      

By Lars Hofhansl 


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

  • checkAndPut,checkAndDelete:这些简单的检查列值作为执行 put 和 delete 的前提条件,检查成功则执行。
  • Increment,Append:对一个列值进行原子操作,比如将一个整数值增加,或者在值的尾部追加
       checkAndPut和checkAndDelete操作是幂等的(幂等方法是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变),可以执行多次。但是在重试的过程中,如果有其他修改发生,结果可能就不一样了。

        Increment和Append并不是幂等的,他们在HBase中是唯一的不可重复操作,他们也是唯一的对HBase MVCC 模型提供的快照隔离机制无效的操作。

       事实证明,checkAndPut和checkAndDelete并不是我们期待那样的所谓原子操作(这个问题是Gregory提出的,虽然作者本人最开始并不相信这个问题是真的-见HBASE-7051)。

       看下代码会让这个问题变得清晰:

       一些针对Put的优化允许在修改同步到 WAL 之前释放行锁(HBASE-4528)。当然这需要MVCC的修改提交之前释放锁,以此来保证修改确认持久化之前对其他事务不可见。

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

        同理,Increment和Append操作同样有这方面的问题。

        然而对这种问题的修改也相对来说简单:我们需要一个MVCC路障,来取代之前在更新完成后完成一个单独的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(注:对于任何一个put,当其写入memstore的时候,都会设置其MemstoreTs的值。
而这个值在内存中是以一个递增的形式存在的,即新写入内存的数据比旧的其MemstoreTs大。而这个值就是用来保证在内存中的update的可靠性。这个值在《HBase内部的ACID》中提到过,它不是一般提到的时间戳,写操作开始之前取回的先前最大的事务号,应该叫WriteNumber,历史原因称为memstore timestamp),效果是这些修改马上对其他事务可见,但是破坏了HBase的MVCC机制。

       这种做法保证了并发的Increment和Append操作的结果是正确的,但是对于并发 scanner 的可见度和我们的预期是不一致的。在 Increment 和 Append 执行修改的过程中,并发的 scanner 本来应该看到先前版本的数据快照,但是现在却可以看到部分行修改的结果。

        Increment 和 Append 出于高吞吐量的设计考虑,他们会把HBase memstore 中刚刚修改后的列的老版本移除。这样一来,以丢失修改的版本历史为代价避免了 memstore 被 Increment 和 Append 的历史版本挤爆。这就是 HBase 中的“upsert”,(关于upsert的解释:upsert,与 insert 对比,in 表示插入,不覆盖,up 表示插上,历史版本没有了,相当于覆盖)。Upsert 的优点在于避免了 memstore 中旧数据的拥挤,缺点在于它们是memstore上没有了版本历史,违背了 MVCC 的理念。


0 0