HBase写入以及MVCC

       HBase中的写入方法有主要分为实时的put以及批量导入bulkload,这里主要介绍一下实时写入put以及一些HBase里面与MVCC相关的东西,版本依旧是社区版1.0.0。

       在regionserver服务端,与put相关的操作差不多最后都会调用到HRegion的doMiniBatchMutation(BatchOperationInProgress)方法,以下截图是从RSRpcServices的mutate方法一直到该方法的调用栈(从下往上看)。

《HBase写入以及MVCC》 put(或者叫mutate)在regionserver中的方法调用栈

       下面主要看doMiniBatchMutation方法,这个方法主要处理mutate(例如put、delete)以及replay过程,关于replay的过程会选择性的跳过。

       函数体中有很详细的注释,主要把一个批量mutate过程分为了以下几个步骤:

1.获取相关的锁,由于HBase要确保行一级的原子性,所以获取锁的时候获取的是整个rowkey的锁而不是单个cell的锁;也只有当至少获取一个锁的时候,这个方法才会继续,否则直接返回。

2.更新cell中的时间戳(timestamp)以及获取mvcc相关参数,其中timestamp(也可以叫做version)可以在客户端自己手动指定,所以在一致性上不能用来做参考,也许正是因此才会引入一个叫做sequenceId的概念(当然更多的用途是为了保证修改操作在HLog里面的顺序)来完成mvcc,最后会介绍一下mvcc以及在这里HBase是如何处理mvcc的。

3.将这些put操作写入memstore,虽然数据库系统中写日志永远比写数据重要,但是这里可以认为当前“事务”尚未提交,即使现在挂了没有日志恢复也不要紧,因为这个“事务”是没有提交的。

4.构建walEdit,这一步主要是为了构建WALEdit类型的walEdit变量,这个变量主要是以list的形式聚合了很多HBase里面cell的概念,以后会写入到HLog中。

5.追加刚才构建好的walEdit:首先构造一个walKey,注意这里的walKey的sequenceId为默认值-1,到后面才会修改为跟region挂钩的唯一递增id;接着调用wal的append方法并返回一个递增数值(txid),用来表示这个追加到wal内存中日志条目的编号,在第七步中这个数值将会作为参数传入,确保该数值之前的日志信息都被写入到HLog日志文件中,而且在append方法中会保证walKey的sequenceId变成了region的sequenceId(也是一个递增序列)。

6.释放获取的锁。

7.将wal写入磁盘,正如第五步所说,这里保证txid以及之前的日志条目都被写入到日志文件中了,一旦写完便可以认为这个“事务”成功了,这里跟MySQL里面的auto commit很像。

8.提交本次操作,让put操作对读可见,核心步骤就是增加对应memstore的readpoint,使得以前讲的MemStoreScanner可以看见put过来的数据,这根后面讲的mvcc有关。

关于HBase里面的MVCC

       mvcc即多版本并发控制,针对的问题就就是在数据库系统中,什么样的数据是应该被看到的,什么样的数据即使有也不应该被读取,典型的情况就是uncommitted的数据。现在的数据库系统即使不是通过天然具有递增属性的时间也是通过类似的递增数列完成mvcc的,给每一条插入的数据按照时间打个标签T,读取事务开始的时候也获取当前时间t,这个读取事务只能读取T<t的数据。

      假设现在线程A执行完上面的第三步,将C这个cell插入到了memstore中,正在此时线程B要读取对应rowkey的所有数据,那么C该不该被读取到呢?HBase里面的事物隔离级别默认情况下可以说是“read committed”所以C明显不该被读取,要如何避免呢?timestamp字段默认情况下是服务端的系统时间,但是用户可以在客户端随意修改覆盖,不能用作mvcc。但是刚好put(或者说是mutate操作)在wal中是有序的,还是根据region提供的sequenceId生成的唯一递增序列,可以用这种唯一递增序列来做饭多版本并发控制的效果。接下来只需要保证每次到了第八步的时候保证memstore的readpoint大于已经提交的最大sequenceId,就可以正确读取了。生成这个与mvcc相关的sequenceId是在上面的第二个步骤中进行的。

       但是有个问题没有解决,很有可能先开始的事务A(假设sequenceId为1)比后开始的事务B(假设sequenceId为2)晚完成。B完成之后将memstore的readpoint设置为2了,这样后面的读取不就可以通过memstore暴露的api读取到A尚未提交的数据了吗?在HBase中与mvcc相关的类里面尚未提交的put操作对应的sequenceId都增加了10亿,以保证在没有提交之前这些数据是不能读取到的,这样一来,A在没有提交之前对应的sequenceId实际是1000000001,根据目前的sequenceId(2)是看不到的。

       以上是关于HBase里面写入的理解,但是有个问题就是找了好久都没发现第八步完成后是怎样从第三步的memstore里面的减去这多加的10亿。

    原文作者:肯草
    原文地址: https://www.jianshu.com/p/c69c620d350b
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞