hbase源码系列(十)HLog与日志恢复-阿里云开发者社区

开发者社区> 岑玉海> 正文

hbase源码系列(十)HLog与日志恢复

简介: hbase在写入数据之前会先写入MemStore,成功了再写入HLog,当MemStore的数据丢失的时候,还可以用HLog的数据来进行恢复。本文将介绍“HLog与日志恢复”。
+关注继续查看

HLog概述

hbase在写入数据之前会先写入MemStore,成功了再写入HLog,当MemStore的数据丢失的时候,还可以用HLog的数据来进行恢复,下面先看看HLog的图。

ec07324214aa2093c02d62991fd47b28c202fb63

旧版的HLog是实际上是一个SequceneFile,0.96的已经使用Protobuf来进行序列化了。从Writer和Reader上来看HLog的都是Entry的,换句话说就是,它的每一条记录就是一个Entry。

class Entry implements Writable {
    private WALEdit edit;
    private HLogKey key;
}

所以上面那个图已经不准确了,HLogKey没变,但是Value缺不是KeyValue,而是WALEdit。

下面我们看看HLogKey的五要素,region、tableName、log的顺序、写入时间戳、集群id。

public HLogKey(final byte [] encodedRegionName, final TableName tablename,
      long logSeqNum, final long now, List<UUID> clusterIds){
    init(encodedRegionName, tablename, logSeqNum, now, clusterIds);
 }

protected void init(final byte [] encodedRegionName, final TableName tablename,
      long logSeqNum, final long now, List<UUID> clusterIds) {
    this.logSeqNum = logSeqNum;
    this.writeTime = now;
    this.clusterIds = clusterIds;
    this.encodedRegionName = encodedRegionName;
    this.tablename = tablename;
 }

下面看看WALEdit的属性, 这里只列出来一个重要的,它是内部持有的一群KeyValue。。

public class WALEdit implements Writable, HeapSize {
  ......private final ArrayList<KeyValue> kvs = new ArrayList<KeyValue>();

HLog的具体实现类是FSHLog,一个Region Server有两个FSHLog,一个负责RS上面所有的用户region的日志,一个负责RS上面的META表的region的日志。

对于日志来说,我们关心的是它如何保证一致性和准确性,在需要它的时候可以发挥救命作用。

HLog同步

对于meta region的HLog写入之后,它会立即同步到硬盘,非meta表的region,它会先把Entry添加到一个队列里面等待同步。

while(!this.isInterrupted() && !closeLogSyncer.get()) {
          try {
            if (unflushedEntries.get() <= syncedTillHere) {
              synchronized (closeLogSyncer) {
                closeLogSyncer.wait(this.optionalFlushInterval);
              }
            }// 同步已经添加的entry
            sync();
          } catch (IOException e) {
            LOG.error("Error while syncing, requesting close of hlog ", e);
            requestLogRoll();
            Threads.sleep(this.optionalFlushInterval);
          }
}

它这里是有一个判断条件的,如果判断条件不成立就立即同步,等待this.optionalFlushInterval时间,默认的同步间隔是1000,它是通过参数hbase.regionserver.optionallogflushinterval设置。unflushedEntries是一个AtomicLong在写入entry的时候递增,syncedTillHere是一个volatile long,同步完成之后也是变大,因为可能被多个线程调用同步操作,所以它是volatile的,从条件上来看,如果没有日志需要同步就等待一秒再进行判断,如果有日志需要同步,也是立马就写入硬盘的,如果发生错误,就是调用requestLogRoll方法,进行回滚,这个回滚比较有意思,它是跑过去flush掉MemStore中的数据,把他们写入硬盘。

下面是回滚的方法。中间我忽略了几步,然后找到LogRoller中的这段代码。

byte [][] regionsToFlush = getWAL().rollWriter(rollLog.get());
        if (regionsToFlush != null) {
          for (byte [] r: regionsToFlush) scheduleFlush(r);
}

找出来需要flush的region,然后计划flush。

regions = findMemstoresWithEditsEqualOrOlderThan(this.outputfiles.firstKey(),
          this.oldestUnflushedSeqNums);

static byte[][] findMemstoresWithEditsEqualOrOlderThan(
      final long walSeqNum, final Map<byte[], Long> regionsToSeqNums) {
    List<byte[]> regions = null;
    for (Map.Entry<byte[], Long> e : regionsToSeqNums.entrySet()) {
      //逐个对比,找出小于已输出为文件的最小的seq id的region
      if (e.getValue().longValue() <= walSeqNum) {
        if (regions == null) regions = new ArrayList<byte[]>();
        regions.add(e.getKey());
      }
    }
    return regions == null ? null : regions
        .toArray(new byte[][] { HConstants.EMPTY_BYTE_ARRAY });
}

逐个对比,找出来未flush MemStore的比输出的文件的HLog流水号还小的region,当它准备flush MemStore之前会调用startCacheFlush方法来把region从oldestUnflushedSeqNums这个map当中去除,添加到已经flush的map当中。

从日志恢复

看过《HMaster启动过程》的童鞋都知道,如果之前有region失败的话,在启动之前会把之前的HLog进行split,把属于该region的为flush过的日志提取出来,然后生成一个新的HLog到recovered.edits目录下,中间的过程控制那块有点儿类似于snapshot的那种,在zk里面建立一个splitWAL节点,在这个节点下面建立任务,不一样的是,snapshot那块是自己处理自己的,这里是别人的闲事它也管,处理完了之后就更新这个任务的状态了,没有snapshot那么复杂的交互过程。

那啥时候会用到这个呢,在region打开的时候,我们从HRegionServer的openRegion方法一路跟踪,中间历经OpenMetaHandler,再到HRegion.openHRegion方法,终于在initializeRegionStores方法里面找到了那么一句话。

 // 如果recovered.edits有日志的话,就恢复日志
    maxSeqId = Math.max(maxSeqId, replayRecoveredEditsIfAny(
        this.fs.getRegionDir(), maxSeqIdInStores, reporter, status));

高潮来了!!!

HLog.Reader reader = null;
    try {
      //创建reader读取hlog
      reader = HLogFactory.createReader(fs, edits, conf);
      long currentEditSeqId = -1;
      long firstSeqIdInLog = -1;
      long skippedEdits = 0;
      long editsCount = 0;
      long intervalEdits = 0;
      HLog.Entry entry;
      Store store = null;
      boolean reported_once = false;

      try {//逐个读取
        while ((entry = reader.next()) != null) {
          HLogKey key = entry.getKey();
          WALEdit val = entry.getEdit();
          //实例化firstSeqIdInLog
          if (firstSeqIdInLog == -1) {
            firstSeqIdInLog = key.getLogSeqNum();
          }
          boolean flush = false;
          for (KeyValue kv: val.getKeyValues()) {
            // 从WALEdits里面取出kvs
            if (kv.matchingFamily(WALEdit.METAFAMILY) ||
                !Bytes.equals(key.getEncodedRegionName(),
                  this.getRegionInfo().getEncodedNameAsBytes())) {//是meta表的kv就有compaction
              CompactionDescriptor compaction = WALEdit.getCompaction(kv);
              if (compaction != null) {
                //完成compaction未完成的事情,校验输入输出文件,完成文件替换等操作
                completeCompactionMarker(compaction);
              }

              skippedEdits++;
              continue;
            }
            // 获得kv对应的store
            if (store == null || !kv.matchingFamily(store.getFamily().getName())) {
              store = this.stores.get(kv.getFamily());
            }
            if (store == null) {
              // 应该不会发生,缺少它对应的列族
              skippedEdits++;
              continue;
            }
            // seq id小,呵呵,说明已经被处理过了这个日志
            if (key.getLogSeqNum() <= maxSeqIdInStores.get(store.getFamily().getName())) {
              skippedEdits++;
              continue;
            }
            currentEditSeqId = key.getLogSeqNum();
            // 这个就是我们要处理的日志,添加到MemStore里面就ok了
            flush = restoreEdit(store, kv);
            editsCount++;
          }
          //MemStore太大了,需要flush掉
          if (flush) internalFlushcache(null, currentEditSeqId, status);

         }
      } catch (IOException ioe) {
        // 就是把名字改了,然后在后面加上".时间戳",这个有毛意思?
        if (ioe.getCause() instanceof ParseException) {
          Path p = HLogUtil.moveAsideBadEditsFile(fs, edits);
          msg = "File corruption encountered!  " +
              "Continuing, but renaming " + edits + " as " + p;
        } else {// 不知道是啥错误,抛错误吧,处理不了
          throw ioe;
        }
      }
      status.markComplete(msg);
      return currentEditSeqId;
    } finally {
      status.cleanup();
      if (reader != null) {
         reader.close();
      }
    }

呵呵,读取recovered.edits下面的日志,符合条件的就加到MemStore里面去,完成之后,就把这些文件删掉。大家也看到了,这里通篇讲到一个logSeqNum,哪里都有它的身影,它实际上是FSHLog当中的一个递增的AtomicLong,每当往FSLog里面写入一条日志的时候,它都会加一,然后MemStore请求flush的时候,会调用FSLog的startCacheFlush方法,获取(logSeqNum+1)回来,然后写入到StoreFile的sequenceid字段,再次拿出来的时候,就遍历这个HStore下面的StoreFile的logSeqNum,取出来最大的跟它比较,小于它的都已经写过了,没必要再写了。

好了,HLog结束了,累死我了,要睡了。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
hbase源码系列(四)数据模型-表定义和列族定义的具体含义
hbase是一个KeyValue型的数据库,在《hbase实战》描述它的逻辑模型【行键,列族,列限定符,时间版本】,物理模型是基于列族的。但实际情况是啥?还是上点代码吧。
2438 0
hbase源码系列(五)Trie单词查找树
在hbase当中单独拿了一个工程出来实现了Trie的数据结果,既达到了压缩编码的效果,亦达到了方便查询的效果,一举两得,设置的方法是在上一章的末尾提了。
1483 0
所有控制文件损坏的恢复--resetlogs方式
        此方式和 所有控制文件损坏的恢复--noresetlogs方式恢复时的前五个步骤是一样的。 1)先备份控制文件            SQL> alter database backup controlfile to 'f:\lib\control.ctl' reuse;数据库已更改。
858 0
MySQL · TokuDB · 日志子系统和崩溃恢复过程
TokuDB日志子系统 MySQL重启后自动加载InnoDB和其他的动态plugin,包括TokuDB。每一plugin在注册的时候指定init和deinit回调函数。TokuDB的init/deinit函数分别是tokudb_init_func和tokudb_done_func。 MySQL重
1417 0
仿酷狗音乐播放器开发日志二十二 动态调色板控件第二版(性能大幅提升附源码)
转载请说明原出处,谢谢~~         在上次写的博客《仿酷狗音乐播放器开发日志二十一 开发动态调色板控件(附源码)》发布后,我在群里和网友讨论这个控件的性能和优 缺点,发现了他很多不足,还有很多提升空间,之后我简单的修改了代码提升了控件的响应速度。
853 0
当前日志组全部损坏的恢复
        当前日志组是指在被后台进程LGWR写入事务变化的日志组。如果在关闭状态下,当前日志组全部损坏或出现介质失败,那么数据库不能重启。 1)查看当前的日志组状态。
556 0
恢复重做日志
 关闭情况下,非活动日志组成员全部损坏。此时DBA可以增加新日志组,删除原有日志组,然后打开数据库。 1)查看当前的日志状态。SQL> select group#,thread#,sequence#,status from v$log;    GROUP#  ...
449 0
一起谈.NET技术,Xml日志记录文件最优方案(附源代码)
  Xml作为数据存储的一种方式,当数据非常大的时候,我们将碰到很多Xml处理的问题。通常,我们对Xml文件进行编辑的最直接的方式是将xml文件加载到XmlDocument,在内存中来对XmlDocument进行修改,然后再保存到磁盘中。
555 0
hbase源码系列(三)Client如何找到正确的Region Server
Client如何找到正确的Region Server ?
2015 0
+关注
64
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
文娱运维技术
立即下载
《SaaS模式云原生数据仓库应用场景实践》
立即下载
《看见新力量:二》电子书
立即下载