HLog概述

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

旧版的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源码系列(十)HLog与日志恢复的更多相关文章

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

    hbase源码系列(十)HLog与日志恢复   HLog概述 hbase在写入数据之前会先写入MemStore,成功了再写入HLog,当MemStore的数据丢失的时候,还可以用HLog的数据来进行恢 ...

  2. hbase源码系列(十二)Get、Scan在服务端是如何处理

    hbase源码系列(十二)Get.Scan在服务端是如何处理?   继上一篇讲了Put和Delete之后,这一篇我们讲Get和Scan, 因为我发现这两个操作几乎是一样的过程,就像之前的Put和Del ...

  3. 11 hbase源码系列(十一)Put、Delete在服务端是如何处理

    hbase源码系列(十一)Put.Delete在服务端是如何处理?    在讲完之后HFile和HLog之后,今天我想分享是Put在Region Server经历些了什么?相信前面看了<HTab ...

  4. 9 hbase源码系列(九)StoreFile存储格式

    hbase源码系列(九)StoreFile存储格式    从这一章开始要讲Region Server这块的了,但是在讲Region Server这块之前得讲一下StoreFile,否则后面的不好讲下去 ...

  5. HBase源码系列之HFile

    本文讨论0.98版本的hbase里v2版本.其实对于HFile能有一个大体的较深入理解是在我去查看"到底是不是一条记录不能垮block"的时候突然意识到的. 首先说一个对HFile ...

  6. hbase源码系列(十二)Get、Scan在服务端是如何处理?

    继上一篇讲了Put和Delete之后,这一篇我们讲Get和Scan, 因为我发现这两个操作几乎是一样的过程,就像之前的Put和Delete一样,上一篇我本来只打算写Put的,结果发现Delete也可以 ...

  7. hbase源码系列(十五)终结篇&Scan续集-->如何查询出来下一个KeyValue

    这是这个系列的最后一篇了,实在没精力写了,本来还想写一下hbck的,这个东西很常用,当hbase的Meta表出现错误的时候,它能够帮助我们进行修复,无奈看到3000多行的代码时,退却了,原谅我这点自私 ...

  8. hbase源码系列(十四)Compact和Split

    先上一张图讲一下Compaction和Split的关系,这样会比较直观一些. Compaction把多个MemStore flush出来的StoreFile合并成一个文件,而Split则是把过大的文件 ...

  9. hbase源码系列(二)HTable 探秘

    hbase的源码终于搞一个段落了,在接下来的一个月,着重于把看过的源码提炼一下,对一些有意思的主题进行分享一下.继上一篇讲了负载均衡之后,这一篇我们从client开始讲吧,从client到master ...

随机推荐

  1. Android长度单位详解(dp、sp、px、in、pt、mm、dip)

    Android中定义的dimension单位有以下这些:px(Pixels ,像素):对应屏幕上的实际像素点.in(Inches ,英寸):屏幕物理长度单位.mm(Millimeters ,毫米):屏 ...

  2. ios支付宝问题整合

           1. 报错:rsa_private read error : private key is NULL     原因:私钥没有转成PKCS8   解决方法: 1)在RSADataSigne ...

  3. Android: 设置 app 字体大小不跟随系统字体调整而变化

    在做 app 内字体大小的需求,类似于 微信中设置字体大小. 那么就需要 app 不跟随系统字体大小调整而变化,找到了两个方法. 方法1: 重写 getResource() 方法,修改 configu ...

  4. git android.google 源码:Unknown SSL protocol error in connection to code.google.com:443

    想要提取android的源码.就必须要使用git.下面是本人安装的过程发生的问题: 1.1安装git.win的命令行的客户端(相当与svn的乌龟那样使用).http://git-scm.com/dow ...

  5. 初学FPGA

    刚开始感觉FPGA不过也就是和51,ARM单片机那样写写程序就完事了,现在看来根本不是那么回事.从夏宇闻老师的Verilog HDL,黑金教程开始学起,但是感觉看到黑金时序篇时感觉少点什么,原来是缺少 ...

  6. 菜鸟调错(一)——Maven项目部署到Jboss出现:Failed to create a new SAX parser

    今天调试的时候遇到一个错误,往Jboss的deploy目录扔war包的时候,报了一个“Failed to create a new SAX parser”的错误.在网上找了找解决方案,一般都说将项目中 ...

  7. 转:Git: git stash 用法小结

    一.应用场景 综合下网上的介绍和资料, git stash (git储藏)可用于以下情形: 发现有一个类是多余的,想删掉它又担心以后需要查看它的代码,想保存它但又不想增加一个脏的提交.这时就可以考虑  ...

  8. 一个分布式 MySQL Binlog 存储系统的架构设计

    1. kingbus简介 1.1 kingbus是什么? kingbus是一个基于raft强一致协议实现的分布式MySQL binlog 存储系统.它能够充当一个MySQL Slave从真正的Mast ...

  9. sql server2000导出表结构说明

    SELECT 表名 then d.name else '' end, 表说明 then isnull(f.value,'') else '' end, 字段序号=a.colorder, 字段名=a.n ...

  10. django中celery的使用

    1.什么是celery celery是一个异步任务框架,当我们的程序中存在一个比较耗时的操作时,可以启动这个异步任务框架, 将耗时操作,交给它来完成,这样节省了程序的执行时间. 2.celery的原理 ...