[翻译]HBase 中的 ACID

同前面翻译的一篇关联的,同做者的另外一篇:ACID in HBasehtml

这一篇不是单纯地描述一个问题,而是以 ACID 为主题,介绍了其在 HBase 中各个部分的体现及实现。apache

ACID,即:原子性(Atomicity),一致性(Consistency),隔离性(Isolation),持久性(Durability)。并发

HBase 支持特定场景下的 ACID,即对同一行的 Put 操做保证彻底的 ACID(HBASE-3584增长了多操做事务,HBASE-5229增长了多行事务,但原理是同样的)oop

那么 HBase 内部的 ACID 是怎么样实现的呢?性能

HBase 实现了一种 MVCC,而且 HBase 没有混合读写事务。优化

因为历史的缘由,HBase 的命名有点奇怪(下面会有提到)。翻译

每一个 RegionServer 拥有严格地单调递增的事务号日志

一个写事务(一组 put 或 delete)开始时,该事务获取下一个最大的事务号,在 HBase 内叫作 WriteNumber。htm

一个读事务(一个 scan 或 get)开始时,该事务获取先前最新提交事务的事务号,在 HBase 内叫作 ReadPoint。对象

每一个新建立的 KeyValue 对象用它所在事务的 WriteNumber 标记(因为历史的缘由,这个标记在 HBase 内叫作 memstore timestamp,注意这个应用程序层面的、咱们常说的时间戳是两回事)。

宏观地,HBase 写事务的流程是这样的:

  1. 对行(一行或多行)加锁,屏蔽对相同行的并发写;
  2. 获取当前的 WriteNumber;
  3. 提交修改到 WAL;
  4. 提交修改到 Memstore(用前面获取的 WriteNumber 标记修改的 KeyValues);
  5. 提交事务,也就是把 ReadPoint 更新为当前获取的 WriteNumber;
  6. 释放行(一行或多行)锁。

宏观地,HBase 读事务的流程是这样的:

  1. 打开 scanner;
  2. 获取当前的 ReadPoint;
  3. 用获取的 ReadPoint 过滤全部扫描到的 KeyValues(KeyValues 的 memstore timestamp > ReadPoint,只看 ReadPoint 以前的);
  4. 关闭 scanner(scanner 由客户端初始化)。

实际在实现的时候会比上面说的复杂,可是上述也足够说明问题。注意,reader 在读的过程当中是彻底不加锁的,可是咱们依旧保证了 ACID。

须要注意的是:上述的机制只有在事务严格顺序提交的状况下管用。不然的话,一个先开始却未提交的事务将会对一个后开始先提交的事务可见(破坏了隔离性)。可是,HBase 中的事务通常都很短,因此这不是个问题。

HBase 确实实现了:全部事务顺序提交。

HBase 中提交一个事务,意味着将当前的的 ReadPoint 更新为该事务的 WriteNumber,这样就将事务提交的更改对全部新的 scan 可见。

HBase 维护一个未完成事务的列表,一个事务的提交会被推迟,直到先前的事务提交。注意:HBase 能够支持并发、全部的修改当即生效,只是提交的时候是顺序的。(译注:这里实际上隐含地表达了 HBase 性能优先,同时实现的是最终一致性

因为 HBase 不保证任何 Region 之间(每一个 Region 只保存在一个 Region Server 上)的一致性,故 MVCC 的数据结果只需保存在每一个 RegionServer 各自的内存中。

下一个有趣的事儿,在 compaction 期间发生了什么?

HBase 的 Compaction 过程,一般将多个小的 store 文件(将 memstore flush 到磁盘时产生)合并成一个大点儿的,并在合并过程当中移除垃圾。这里的"垃圾"要么是寿命超过了列族的 TTL(Time-To-Live)或 VERSIONS 设置、要么是被标记删除的那些 KeyValues。详情参见这里(Deletion in HBase, HBase data rentention options)。

假设在一个 scanner 扫描 KeyValues 过程当中发生了 Compaction,scanner 可能会看到一个不完整的行(HBase 中对行的定义为:Introduction to HBase),即该行数据不可能从任何一种顺序事务调度获得。(译注:也就是发生了不一致

HBase 的方案是跟踪全部打开的 scanner 使用的 ReadPoint 中最先的一个,而后滤掉全部大于该 ReadPoint 的 KeyValues。这一逻辑 连同其它的优化在HBASE-2856中增长进来,这一补丁后,容许 HBase 在并发 flush 的场景下保证 ACID。

HBASE-5569 为 delete marker 实现了一样的逻辑(译注:在 ReadPoint 过滤的逻辑,支持并发删除场景下的 ACID),HBase 是标记删除的,故实现了并发删除的 ACID。

最后,注意,当一个 KeyValue 的 memstore timestamp 比最老的scanner(实际是 scanner 持有的 ReadPoint)还要老时,会被清零(置为0),这样该 KeyValue会对全部的 scanner 可见,固然,此时比该 KeyValue 原 memstore timestamp 更早的 scanner 都已经结束了。

额外的几点:

  • (对于写事务)即便事务失败了,ReadPoint 也会被更新,以免阻拦后面等待提交的事务(从实现上说,实际上是同一个过程,没什么特别的处理,更新 ReadPoint 的代码写在 Java 代码final{}语句块内)(译注:前面提到了HBase 的事务是顺序提交的,后面的事务会等待前面的事务提交);
  • 更新写入到 WAL 后,全部的修改都只建立了一条记录(record),没有单独的 commit record(译注:我理解这个 commit record 可参考 2阶段提交);
  • 当一台 RegionServer 挂掉,若是 WAL 已经完整写入,全部执行中的事务能够重放日志以恢复,若是 WAL 未写完,则未完成的事务会丢掉(相关的数据也丢失了)。
相关文章
相关标签/搜索