hbase实践之写流程拾遗

keyvalue

KeyValue中包含了丰富的自我描述信息:
image

KeyValue是支撑”稀疏矩阵”设计的一个关键点:一些Key相同的任意数量的独立KeyValue就可以构成一行数据。但这种设计带来的一个显而易见的缺点:每一个KeyValue所携带的自我描述信息,会带来显著的数据膨胀。

为什么rowkey不能太长?columnfamily、qualifiter尽量短?

image

行级事务模型

写写并发控制

包括单行、批量多行。

  • 单行:行锁
  • 多行:两阶段锁协议,即:

(1) 获取所有待写入(更新)行记录的行锁

(2) 开始执行写入(更新)操作

(3) 写入完成之后再统一释放所有行记录的行锁

读写并发控制:MVCC

Mutil Version Concurrent Control。HBase中MVCC机制实现主要分为两步:

(1) 为每一个写(更新)事务分配一个Region级别自增的序列号

(2) 为每一个读请求分配一个已完成的最大写事务序列号

MVCC的精髓是写入的时候分配递增版本信息(SequenceId),读取的时候分配唯一的版本用于读取可见,比之大的版本不可见。这里需要注意版本必须递增,而且版本递增的范围一定程度上决定了事务是什么事务,比如HBase是Region级别的递增版本,那么事务就是region级别事务。

非必须情况,不要使用行锁。

sequenceId

为什么要有sequenceId?

  1. 先写HLog,再写memstore,两个地方的数据是一致的,两者如何一一对应?一一对应后,日志可删除(因为只能保留有限日志)。
  2. HLog日志如何删除?HLog只能保存一定数量的日志,如果删除日志文件,先删除谁? 先删除年龄最老的日志文件,则其sequenceId最小。如果这些数据没有落盘,就可以强制对其执行flush,之后就可以将HLog删除。
  3. RegionServer宕机,memsotre中数据丢失,数据从哪里开始恢复?

本质问题:mestore、Hlog中的数据是同一份,他们需要同一个标识。

  1. 读写并发控制
  • HLog文件的基本结构
    image

<logseq#-for-entire-txn>:<-1, 3, , , >

The -1 marker is just a special way of being backward compatible with an old HLog which would have contained a single . -1只是一个标志, 是为了对旧版本兼容.

问题一:HLog在什么时候可以过期回收?HLog在什么时候可以过期回收?

RegionServer会为每个Region维护了一个变量oldestUnflushedSequenceId(实际上是为每个Store,为了方便讲解,此处暂且认为是Region,不影响原理),表示这个Region最早的还未落盘的seqid ,即这个seqid之前的所有数据都已经落盘。

下图是flush过程中oldestUnflushedSequenceId变量变化的示意图,初始时为null,假设在某一时刻阶段二RegionA(红色方框)要执行flush,中间HLog中sequenceId为1~4对应的数据将会落盘,在执行flush之前,HBase会append一个空的Entry到HLog,仅为获取下一个sequenceId(5),并将这个sequenceId赋给OldestUnflushedSequenceId-RegionA。如图中第三阶段OldestUnflushedSequenceId-RegionA指向sequenceId为5的Entry。

image

image

场景一中右侧HLog还有未落盘的数据(sequenceid=5还未落盘),因此不能删除;而场景二中右侧HLog的所有数据都已经落盘,所以这个HLog理论上就已经可以被删除回收。

问题二:HLog数量超过阈值(maxlogs)之后删除最早HLog,应该强制刷新哪些Region?

image

HLog日志文件超过阈值,会删除最老的,根据OldestUnflushedSequenceId,如果该日志中有数据未写磁盘,则强制刷写磁盘,然后将该日志文件删除。

问题三:RegionServer宕机恢复replay日志时哪些WALEntry需要被回放,哪些会被skip?

理论上来说只需要回放Memstore中没有落地的数据对应的WALEntry,已经落地数据对应的WALEntry可以skip。可问题是RegionServer已经宕机了,<region, oldestUnflushedSequenceId> 对应信息肯定没有了,如何是好?想办法持久化呗,上文分析oldestUnflushedSequenceId变量是flush时产生的一个变量,这个变量完全可以以flush的时候以元数据的形式写到HFile中(代码见下图):

image

到目前为止,上面所有分析都基于一个事实:hbase中flush操作是region级别操作,即每次执行flush都需要整个region中的所有store全都执行flush。

Per-CF Flush

map<region, map<store, oldestUnflushedSequenceId>>

RS宕机与恢复

image

image

由HMaster来管理并切分日志>RegionServer切分日志(小文件多)>RegionServer切分日志后,直接在Region-Buffer中回放。

Region 切分

Region切分触发策略

  1. ConstantSizeRegionSplitPolicy
  2. IncreasingToUpperBoundRegionSplitPolicy:(#regions) * (#regions) * (#regions) * flush size * 2

在大集群条件下对于很多大表来说表现很优秀,但并不完美,这种策略下很多小表会在大集群中产生大量小region,分散在整个集群中。而且在发生region迁移时也可能会触发region分裂。

  1. SteppingSplitPolicy:如果region个数等于1,切分阈值为flush size * 2,否则为MaxRegionFileSize。

切分本质

image

虽然产生2个子Region,但还是指向原来的HFile文件。只有major compaction时,父region的数据才会迁移到子region目录。

参考文献

posted @ 2018-11-05 21:16  small_k  阅读(333)  评论(0编辑  收藏  举报