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

 

从这一章开始要讲Region Server这块的了,但是在讲Region Server这块之前得讲一下StoreFile,否则后面的不好讲下去,这块是基础,Region Sever上面的操作,大部分都是基于它来进行的。

HFile概述

HFile是HBase中实际存数据的文件,为HBase提供高效快速的数据访问。它是基于Hadoop的TFile,模仿Google Bigtable 架构中的SSTable格式。文件格式如下:

文件是变长的,唯一固定的块是File Info和Trailer,如图所示,Trailer有指向其它块的指针,这些指针也写在了文件里,Index块记录了data和meta块的偏移量,meta块是可选的。

下面我们从原来上来一个一个的看它们到底是啥样的,先从入口看起,那就是StoreFile.Writer的append方法,先看怎么写入的,然后它就怎么读了,不知道怎么使用这个类的,可以看看我写的这篇文章《非mapreduce生成Hfile,然后导入hbase当中》。

往HFile追加KeyValue

不扯这些了,看一下StoreFile里面的append方法。

    public void append(final KeyValue kv) throws IOException {
//如果是新的rowkey的value,就追加到Bloomfilter里面去 appendGeneralBloomfilter(kv);
//如果是DeleteFamily、DeleteFamilyVersion类型的kv appendDeleteFamilyBloomFilter(kv);
writer.append(kv);
//记录最新的put的时间戳,更新时间戳范围 trackTimestamps(kv);
}

在用writer进行append之前先把kv写到generalBloomFilterWriter里面,但是我们发现generalBloomFilterWriter是HFile.Writer里面的InlineBlockWriter。

generalBloomFilterWriter = BloomFilterFactory.createGeneralBloomAtWrite(
conf, cacheConf, bloomType,
(int) Math.min(maxKeys, Integer.MAX_VALUE), writer);
//在createGeneralBloomAtWriter方法发现了以下代码
......
CompoundBloomFilterWriter bloomWriter = new CompoundBloomFilterWriter(getBloomBlockSize(conf),
err, Hash.getHashType(conf), maxFold, cacheConf.shouldCacheBloomsOnWrite(),
bloomType == BloomType.ROWCOL ? KeyValue.COMPARATOR : KeyValue.RAW_COMPARATOR);
writer.addInlineBlockWriter(bloomWriter);

我们接下来看HFileWriterV2的append方法吧。

public void append(final KeyValue kv) throws IOException {
append(kv.getMvccVersion(), kv.getBuffer(), kv.getKeyOffset(), kv.getKeyLength(),
kv.getBuffer(), kv.getValueOffset(), kv.getValueLength());
this.maxMemstoreTS = Math.max(this.maxMemstoreTS, kv.getMvccVersion());
}

为什么贴这段代码,注意这个参数maxMemstoreTS,它取kv的mvcc来比较,mvcc是用来实现MemStore的原子性操作的,在MemStore flush的时候同一批次的mvcc都是一样的,失败的时候,把mvcc相同的全部干掉,这里提一下,以后应该还会说到,继续追杀append方法。方法比较长,大家展开看看。

 

从上面我们可以看到来,HFile写入的时候,是分一个块一个块的写入的,每个Block块64KB左右,这样有利于数据的随机访问,不利于连续访问,连续访问需求大的,可以把Block块的大小设置得大一点。好,我们继续看checkBlockBoundary方法。

  private void checkBlockBoundary() throws IOException {
if (fsBlockWriter.blockSizeWritten() < blockSize)
return; finishBlock();
writeInlineBlocks(false);
newBlock();
}

简单交代一下

1、结束一个block的时候,把block的所有数据写入到hdfs的流当中,记录一些信息到DataBlockIndex(块的第一个key和上一个块的key的中间值,块的大小,块的起始位置)。

2、writeInlineBlocks(false)给了一个false,是否要关闭,所以现在什么都没干,它要等到最后才会输出的。

3、newBlock方法就是重置输出流,做好准备,读写下一个块。

Close的时候

close的时候就有得忙咯,从之前的图上面来看,它在最后的时候是最忙的,因为它要写入一大堆索引信息、附属信息啥的。

public void close() throws IOException {
boolean hasGeneralBloom = this.closeGeneralBloomFilter();
boolean hasDeleteFamilyBloom = this.closeDeleteFamilyBloomFilter();
writer.close();
}

在调用writer的close方法之前,close了两个BloomFilter,把BloomFilter的类型写进FileInfo里面去,把BloomWriter添加到Writer里面。下面进入正题吧,放大招了,我折叠吧。。。

 

和图片上写的有些出入。

1、输出HFileBlocks

2、输出HFileBlockIndex的二级索引(我叫它二级索引,我也不知道对不对,HFileBlockIndex那块我有点儿忘了,等我再重新调试的时候再看看吧)

3、如果有的话,输出MetaBlock

下面的部分是打开文件的时候就加载的

4、输出HFileBlockIndex的根索引

5、如果有的话,输出MetaBlockIndex的根索引(它比较小,所以只有一层)

6、输出文件信息(FileInfo)

7、输出文件尾巴(Trailer)

Open的时候

这部分打算讲一下实例化Reader的时候,根据不同类型的文件是怎么实例化Reader的,在StoreFile里面搜索open方法。

this.reader = fileInfo.open(this.fs, this.cacheConf, dataBlockEncoder.getEncodingInCache());

 // 加载文件信息到map里面去,后面部分就不展开讲了
metadataMap = Collections.unmodifiableMap(this.reader.loadFileInfo());

我们进入F3进入fileInfo.open这个方法里面去。

    FSDataInputStreamWrapper in;
FileStatus status; if (this.link != null) {
// HFileLink
in = new FSDataInputStreamWrapper(fs, this.link);
status = this.link.getFileStatus(fs);
} else if (this.reference != null) {
// HFile Reference 反向计算出来引用所指向的位置的HFile位置
Path referencePath = getReferredToFile(this.getPath());
in = new FSDataInputStreamWrapper(fs, referencePath);
status = fs.getFileStatus(referencePath);
} else {
in = new FSDataInputStreamWrapper(fs, this.getPath());
status = fileStatus;
}
long length = status.getLen();
if (this.reference != null) {
hdfsBlocksDistribution = computeRefFileHDFSBlockDistribution(fs, reference, status);
//如果是引用的话,创建一个一半的readerreturn new HalfStoreFileReader(
fs, this.getPath(), in, length, cacheConf, reference, dataBlockEncoding);
} else {
hdfsBlocksDistribution = FSUtils.computeHDFSBlocksDistribution(fs, status, 0, length);
return new StoreFile.Reader(fs, this.getPath(), in, length, cacheConf, dataBlockEncoding);
}

它一上来就判断它是不是HFileLink是否为空了,这是啥情况?找了一下,原来在StoreFile的构造函数的时候,就开始判断了。

 

它有4种情况:

1、HFileLink

2、既是HFileLink又是Reference文件

3、只是Reference文件

4、HFile

说HFileLink吧,我们看看它的构造函数

public HFileLink(final Path rootDir, final Path archiveDir, final Path path) {
Path hfilePath = getRelativeTablePath(path);
this.tempPath = new Path(new Path(rootDir, HConstants.HBASE_TEMP_DIRECTORY), hfilePath);
this.originPath = new Path(rootDir, hfilePath);
this.archivePath = new Path(archiveDir, hfilePath);
setLocations(originPath, tempPath, archivePath);
}

尼玛,它计算了三个地址,原始位置,archive中的位置,临时目录的位置,按照顺序添加到一个locations数组里面。。接着看FSDataInputStreamWrapper吧,下面是三段代码

this.stream = (link != null) ? link.open(hfs) : hfs.open(path);
//走的link.open(hfs)
new FSDataInputStream(new FileLinkInputStream(fs, this));
//注意tryOpen方法
public FileLinkInputStream(final FileSystem fs, final FileLink fileLink, int bufferSize)
throws IOException {
this.bufferSize = bufferSize;
this.fileLink = fileLink;
this.fs = fs;
this.in = tryOpen();
}

tryOpen的方法,会按顺序打开多个locations列表。。

 

恩,这回终于知道它是怎么出来的了,原来是尝试打开了三次,直到找到正确的位置。

StoreFile的文件格式到这里就结束了,有点儿遗憾的是HFileBlockIndex没给大家讲清楚。

补充:经网友"东岸往事"的提醒,有一个地方写错了,在结束一个块之后,会把它所有的BloomFilter全部输出,HFileBlockIndex的话,如果满了默认的128*1024个就输出二级索引。

具体的的内容在后面说查询的时候会说,下面先交代一下:

通过看继承InlineBlockWriter的类,发现了以下信息

1、BlockIndexWriter 不是关闭的情况下,没有超过默认值128*1024是不会输出的,每128*1024个HFileBlock 1个二级索引。

HFileBlockIndex包括2层,如果是MetaBlock的HFileBlock是1层。

二级索引 curInlineChunk 在结束了一个块之后添加一个索引的key(上一个块的firstKey和这个块的firstKey的中间值)。

byte[] indexKey = comparator.calcIndexKey(lastKeyOfPreviousBlock, firstKeyInBlock);
curInlineChunk.add(firstKey, blockOffset, blockDataSize);

一级索引 rootChunk 输出一次二级索引之后添加每个HFileBlock的第一个key,这样子其实二级索引里面是包括是一级索引的所有key的。

firstKey = curInlineChunk.getBlockKey(0);
rootChunk.add(firstKey, offset, onDiskSize, totalNumEntries);

2、CompoundBloomFilterWriter也就是Bloom Filter,在数据不为空的时候,就会输出。

对于HFileV2的正确的图,应该是下面这个,但是上面的那个图看起来好看一点,就保留了。

9 hbase源码系列(九)StoreFile存储格式的更多相关文章

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

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

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

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

  3. HBase源码系列之HFile

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

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

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

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

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

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

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

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

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

  8. hbase源码系列(一)Balancer 负载均衡

    看源码很久了,终于开始动手写博客了,为什么是先写负载均衡呢,因为一个室友入职新公司了,然后他们遇到这方面的问题,某些机器的硬盘使用明显比别的机器要多,每次用hadoop做完负载均衡,很快又变回来了. ...

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

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

随机推荐

  1. 3ds Max实例教程-顽皮的小孩

    本教程介绍使用3ds Max制作设计一个顽皮的小孩,这个作品的灵感来源于作者的亲身经历,也是以真实人物为原型做出来这么一个小人. 作者: Claudius Vesting 使用软件:3ds Max,P ...

  2. 大数相乘(牛客网ac通过)

    2019-05-172019-05-17 大数相乘基本思想: 相乘相加,只不过大于10先不进位到计算完后统一进位 #include <iostream> #include <stri ...

  3. div控制最小高度又自适高度

    div控制最小高度又自适高度我们在用div布局的时候经常会遇到这样的一种情况:我们需要设置一个div的高度,当里面的东西超过这个高度时,让这个容器自动被撑开,也就是自适应高度.当里面的信息很少时候,我 ...

  4. React 第二天

    第二天 01 关于Vue和React中key的作用 在循环的时候一定要为组件加key 02关于jsx语法的注意事项 jsx中的注释 {/*  */} class要写成className label标签 ...

  5. centos部署nginx服务

    1.准备安装程序 pcrl-8.43.tar.gz  zlib-1.2.11.tar.gz  openssl-1.0.1j.tar.gznginx-1.9.9.tar.gz 2.将下载的包拷贝到/us ...

  6. Github README.md中添加图片

    1.先把图片上传到你的项目中:然后在github网站上按路径打开图片,如下打开的图片链接: 2.复制图片的地址 3.然后在README.md写上: ![这里随便写文字](你刚复制的图片路径) 注意  ...

  7. Linux内存管理与C存储空间

    ELF文件 在学习之前我们先看看ELF文件. ELF分为三种类型:.o 可重定位文件(relocalble file),可执行文件以及共享库(shared library),三种格式基本上从结构上是一 ...

  8. Redit集群搭建-Sentinel模式搭建

    Redit集群搭建 学习了: Windows:http://blog.csdn.net/mrxiagc/article/details/52799081 Linux:https://www.cnblo ...

  9. leetcode笔记:Merge Sorted Array

    一.题目描写叙述 二.解题技巧 这道题不存在复杂的分析过程和边界条件.假设单纯得考虑从小到大地将两个数组进行合并的话.每次在num1中插入一个数的话,须要将后面的元素都向后移动一位.这样.整个处理过程 ...

  10. 【翻译自mos文章】开启dblink的 oracle net trace/tracing --对dblink进行跟踪的方法

    开启dblink的 oracle net trace/tracing --对dblink进行跟踪的方法. 參考原文: DBLINK: How to Enable Oracle Net Tracing ...