Loading

HBase WAL日志

date: 2020-11-16 15:18:00
updated: 2020-11-16 18:45:00

HBase WAL日志

参考网址1
参考网址2

每一个region servser维护一个或多个Hlog(1.X版本可以开启multiwal),而不是每一个region一个日志。这样不同 region(可能来自来自不同 table) 的日志会混在一起,这样做的目的是不断追加单个文件相对于同时写多个文件而言,可以减少磁盘寻址次数,因此可以提高对 table 的写性能。带来的麻烦是,如果一台 region server 下线,为了恢复其上的 region,需要将 region server 上的 log 进行拆分,然后分发到其它 region server 上进行恢复。

HLog文件基本结构

HLog中日志单元WALEntry表示一次行级更新的最小追加单元(图中红色/黄色小方框)。它由两部分组成:HLogKey和WALEdit,HLogKey中包含多个属性信息,包含table name、region name、sequenceid等;WALEdit用来表示一个事务中的更新集合,一次行级事务可以原子操作同一行中的多个列。上图中WALEdit包含多个KeyValue。

1. sequenceID

问题:

  1. memstore中的数据flush到hdfs中后,HLog对应的数据是不是可以删除了?不然HLog会无限增长
  2. HBase中单个HLog都有固定大小,日志文件最大个数也有设置,默认最大文件数量是8。如果文件数量超过了这个值,就必须删除最老的日志。那么如何知道HLog日志对应的所有数据都已经flush到磁盘?(如果知道哪些数据没有罗盘,就可以强制进行flush,然后删除HLog)
  3. regionserver宕机之后memstore中的数据会丢失,可以通过HLog进行恢复,哪些数据需要恢复?哪些不需要恢复?

=> 都需要一种介质来表示Memstore中数据Flush的那个点对应HLog哪个位置 => sequenceId 自增序号,每个region维护属于自己的id,不同region的id相互独立

sequenceID

1. HLog 什么时候可以过期回收

回收HLog

虚线右侧部分为超过单个HLog大小阈值后切分形成的一个HLog文件,问题是这个文件什么时候可以被系统回收删除。理论上来说只需要这个文件上所有Region对应的最大sequenceid已经落盘就可以删除,比如下图中如果RegionA对应的最大sequenceid(5)已经落盘,同时RegionB对应的最大sequenceid(5)也落盘,那该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。

可见,每次flush之后这个变量就会往前移动一段距离。

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

如果在当前HLog中存在一个regionA的oldestUnflushedSequenceId小于当前HLog文件中regionA最大的sequenceId,那么就不能删除这个HLog文件;如果到达了HLog的最大文件数量,必须要删除这个文件,那么就对regionA中执行flush操作,将数据落盘

2. RegionServer宕机恢复replay日志时哪些WALEntry需要被回放,哪些会被skip

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

org.apache.hadoop.hbase.regionserver.StoreFlusher

这样Region在宕机迁移重新打开之后加载HFile元数据就可以恢复出这个核心变量oldestUnflushedSequenceId(本次flush所生成的所有HFlie中都存储同一个sequenceId),这个sequenceId在恢复出来之后就可以用来在回放WALEntry的时候过滤哪些Entry需要被回放,哪些会被skip。

2.1 附加问题

Q: 有没有可能一次flush所生成的所有HFile中存储的sequenceId出现不一致,比如:region中所有store(store1、store2)都执行flush,其中store1执行flush成功,此时oldestUnflushedSequenceId变量成功追加到对应的HFile中;但在store2执行flush之前RegionServer发生宕机异常,store2对应的oldestUnflushedSequenceId变量还是上个文件对应的sequenceId,这种情况下回放数据会不会有影响?如果有,为什么?如果没有,是什么机制保证的?

A: 到目前为止,上面所有分析都基于一个事实:hbase中flush操作是region级别操作,即每次执行flush都需要整个region中的所有store全都执行flush。接下来作为延伸阅读内容,对Per-CF Flush比较感兴趣的可以继续阅读,Per-CF Flush允许系统对某个或某些列组单独执行flush。实现原理与上文所分析内容基本相似。不同的是上文中oldestUnflushedSequenceId是与region一一对应的,Per-CF Flush中这个参数需要细化到store,与store一一对应。

3. Per-CF Flush

region级别flush确实存在不少问题,在多个列族的情况下其中一个store大小超过了阈值(128M),不论其他store多大多小都会强制落盘,有些很小的列族(几兆)落盘后形成很多特别小的文件,对hbase的读并不是一件好事。

per-cf flush允许单个store执行flush,该feature在1.0.0以上版本已经存在,在1.2.0版本设置为默认策略。 实现这个功能有两个必要的工作,其一是提出一种新的flush策略能够在多个列族中选择一个或者多个单独进行进行flush,目前新策略称为FlushLargerStoresPolicy,即选择当前最大的一个store进行flush。其二是必须将oldestUnflushedSequenceId的粒度从region细化到store,即从map<region, oldestUnflushedSequenceId>改为map<region, map<store, oldestUnflushedSequenceId>>,上文所述三个问题的判断逻辑也需要修改为store级别判断逻辑。这里使用store级别判断逻辑简单对问题一和问题三进行复盘。

Per-CF Flush策略下,HLog在什么时候可以过期回收?
region级别的判断逻辑主要依赖于map<region, oldestUnflushedSequenceId>,详见上文。store级别的数据结构改为了map<region, map<store, oldestUnflushedSequenceId>>,其实很容易经过简单的转化又变回region级别,map<store, oldestUnflushedSequenceId>找到最小的oldestUnflushedSequenceId称为minSeqNum,这样region级别的数据结构就变出来了 – map<region, minSeqNum>,其他逻辑都不用变。

=> 根据每个store的id,可以取到region的最小id,然后按照这个最小id来进行flush

Per-CF Flush策略下,RegionServer宕机恢复replay日志时哪些数据需要被回放,哪些会被skip?
这个问题稍微复杂一点,第一个关注的问题是回放粒度的问题。需要回过头来看看HLog中Entry的组成,如图可以知道一个Entry由WALKey和WAKEdit两部分构成,WALKey包含一些基本信息,本文重点关注sequenceId这个变量;WALEdit包含插入\更新的KeyValue集合,这里需要重点注意,这些KeyValue可能包含一行中多个列族(列),因此可以说WALEdit会包含多个store更新的KeyValue。

在All-CF Flush策略下,我们以HLog-Entry为粒度进行数据回放没有任何问题,但是在Per-CF Flush策略下就不再行得通。因为一个HLog-Entry中多个CF的KeyValue是混在一起的,可能部分KV已经落盘,其他部分还没有。因此需要将回放粒度减小到KeyValue级别,一个一个KeyValue分别进行检查回放。

回放粒度问题摸清了,再来关注哪些KeyValue需要被回放,哪些会被skip。上文说过,每次flush的时候对应的oldestUnflushedSequenceId会被持久化到HFile的元数据中。在All-CF Flush策略下,一次flush操作中整个region所有store所持久化的oldestUnflushedSequenceId都相同,因此回放的时候HLog-Entry的sequenceId只需要与这一个oldestUnflushedSequenceId比较就可以,大的话就需要回放,小的话就skip。但在Per-CF的场景下又不再行得通,一个region中不同store都有自己独立的oldestUnflushedSequenceId,因此回放的时候需要根据KeyValue找到对应store,在与该store中的oldestUnflushedSequenceId比较,大的话需要回放,小的话skip。

posted @ 2020-11-25 16:02  猫熊小才天  阅读(895)  评论(0编辑  收藏  举报