综述
HBase支持行级事务,也即用户对于HBase的一条数据的读写,HBase可以保证其原子性。对于多行事务,HBase并不能保证其原子性。
为了保证行级事务,需要考虑写写控制和读写控制,对于读读操作,由于不涉及到数据的修改,因此无须进行并发控制。如果不考虑这两种控制,可能会出现数据不一致情况,也就不是事务了。
下面对于写写控制和读写控制,分别考虑。
写写控制
无并发控制
先考虑不进行并发控制的情况。
假设有两个写线程,同一时间段内,对一行数据进行修改,由于HBase的写入需要经过好两个步骤:
- 写WAL
- 写入memstore
如果不进行并发,那么在更新完WAL之后,在更新memstore时,可能第二个写线程先更新info:company,第一个写线程后更新info:company; 第一个线程先更新info:role,第二个线程后更新info:role,如下图所示。
那么我们最终读取到的数据就是下面这样,即数据出现不一致。
并发控制
为了解决上面的问题,HBase引入了行锁机制,以rowkey为锁,当写线程需要更新数据时,需要先获取行锁,然后才能更新,对于没有获取到行锁的写线程,只能等待,直至获取到行锁。具体的流程如下。
- 写操作开始
- 获取行锁
- 更新WAL
- 更新memstore
- 释放锁
读写控制
无并发控制
读写场景,同样先考虑不进行并发控制,可能会出现的情况。
假如现在有两个写线程要更新同一行数据,同时一个读线程要对该行数据进行读取操作。读线程读取的时机是红线部分,此时第一个写线程已经完成,第二个写线程更新了一部分,如下图所示。
那么读线程读取的数据就是下面这样子,同样会出现数据不一致情况。
并发控制
为了解决上面的问题,HBas引入了MVCC机制,采用无锁机制,实现读写控制。为了实现该机制,HBase在进行写操作时,会进行如下操作:
- 获取行锁
- 分配一个写事务id
- 更新WAL
- 更新memstore
- 完成写事务id
- 释放锁
与此同时,对于每一次的读操作,HBase都会为其分配一个已经完成、且值最大的写事务id。
如图所示,两个写线程开启写事务时,HBase分别为其分配一个写id,当读事务开启时,由于写事务1已经完成,而写事务2还未完成,因此分配的已完成、其值最大的写事务id是1,称之为read point。因此对该读事务而言,所有小于等于read point的数据都是对其可见的,大于read point的数据是不可见的。
因此,在这种情况下,读线程最终读取的结果是这个样子。