从这一章开始要讲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方法。方法比较长,大家展开看看。

private void append(final long memstoreTS, final byte[] key, final int koffset, final int klength,final byte[] value, final int voffset, final int vlength)throws IOException {boolean dupKey = checkKey(key, koffset, klength);checkValue(value, voffset, vlength);if (!dupKey) {//在写每一个新的KeyValue之间,都要检查,到了BlockSize就重新写一个HFileBlock
      checkBlockBoundary();}//如果当前的fsBlockWriter的状态不对,就重新写一个新块if (!fsBlockWriter.isWriting())newBlock();// 把值写入到ouputStream当中,怎么写入的自己看啊
    {DataOutputStream out = fsBlockWriter.getUserDataStream();out.writeInt(klength);totalKeyLength += klength;out.writeInt(vlength);totalValueLength += vlength;out.write(key, koffset, klength);out.write(value, voffset, vlength);if (this.includeMemstoreTS) {WritableUtils.writeVLong(out, memstoreTS);}}// 记录每个块的第一个key 和 上次写的keyif (firstKeyInBlock == null) {firstKeyInBlock = new byte[klength];System.arraycopy(key, koffset, firstKeyInBlock, 0, klength);}lastKeyBuffer = key;lastKeyOffset = koffset;lastKeyLength = klength;entryCount++;}

View Code

从上面我们可以看到来,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里面。下面进入正题吧,放大招了,我折叠吧。。。

public void close() throws IOException {if (outputStream == null) {return;}// 经过编码压缩的,把编码压缩方式写进FileInfo里面blockEncoder.saveMetadata(this);//结束块
    finishBlock();//输出DataBlockIndex索引的非root层信息writeInlineBlocks(true);FixedFileTrailer trailer = new FixedFileTrailer(2,HFileReaderV2.MAX_MINOR_VERSION);// 如果有meta块的存在的话if (!metaNames.isEmpty()) {for (int i = 0; i < metaNames.size(); ++i) {long offset = outputStream.getPos();// 输出meta的内容,它是meta的名字的集合,按照名字排序DataOutputStream dos = fsBlockWriter.startWriting(BlockType.META);metaData.get(i).write(dos);fsBlockWriter.writeHeaderAndData(outputStream);totalUncompressedBytes += fsBlockWriter.getUncompressedSizeWithHeader();// 把meta块的信息加到meta块的索引里
        metaBlockIndexWriter.addEntry(metaNames.get(i), offset,fsBlockWriter.getOnDiskSizeWithHeader());}}//下面这部分是打开文件的时候就加载的部分,是前面部分的索引//HFileBlockIndex的根层次的索引long rootIndexOffset = dataBlockIndexWriter.writeIndexBlocks(outputStream);trailer.setLoadOnOpenOffset(rootIndexOffset);//Meta块的索引
    metaBlockIndexWriter.writeSingleLevelIndex(fsBlockWriter.startWriting(BlockType.ROOT_INDEX), "meta");fsBlockWriter.writeHeaderAndData(outputStream);totalUncompressedBytes += fsBlockWriter.getUncompressedSizeWithHeader();//如果需要写入Memstore的最大时间戳到FileInfo里面if (this.includeMemstoreTS) {appendFileInfo(MAX_MEMSTORE_TS_KEY, Bytes.toBytes(maxMemstoreTS));appendFileInfo(KEY_VALUE_VERSION, Bytes.toBytes(KEY_VALUE_VER_WITH_MEMSTORE));}//把FileInfo的起始位置写入trailer,然后输出
    writeFileInfo(trailer, fsBlockWriter.startWriting(BlockType.FILE_INFO));fsBlockWriter.writeHeaderAndData(outputStream);totalUncompressedBytes += fsBlockWriter.getUncompressedSizeWithHeader();// 输出GENERAL_BLOOM_META、DELETE_FAMILY_BLOOM_META类型的BloomFilter的信息for (BlockWritable w : additionalLoadOnOpenData){fsBlockWriter.writeBlock(w, outputStream);totalUncompressedBytes += fsBlockWriter.getUncompressedSizeWithHeader();}//HFileBlockIndex的二级实体的层次
    trailer.setNumDataIndexLevels(dataBlockIndexWriter.getNumLevels());//压缩前的HFileBlockIndex的大小
    trailer.setUncompressedDataIndexSize(dataBlockIndexWriter.getTotalUncompressedSize());//第一个HFileBlock的起始位置
    trailer.setFirstDataBlockOffset(firstDataBlockOffset);//最后一个HFileBlock的起始位置
    trailer.setLastDataBlockOffset(lastDataBlockOffset);//比较器的类型
    trailer.setComparatorClass(comparator.getClass());//HFileBlockIndex的根实体的数量,应该是和HFileBlock的数量是一样的//它每次都把HFileBlock的第一个key加进去
    trailer.setDataIndexCount(dataBlockIndexWriter.getNumRootEntries());//把Trailer的信息写入硬盘,关闭输出流
    finishClose(trailer);fsBlockWriter.release();}

View Code

和图片上写的有些出入。

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) {// HFileLinkin = 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的构造函数的时候,就开始判断了。

this.fileStatus = fileStatus;Path p = fileStatus.getPath();if (HFileLink.isHFileLink(p)) {// HFileLink 被判断出来它是HFilethis.reference = null;this.link = new HFileLink(conf, p);} else if (isReference(p)) {this.reference = Reference.read(fs, p);//关联的地址也可能是一个HFileLink,snapshot的时候介绍了Path referencePath = getReferredToFile(p);if (HFileLink.isHFileLink(referencePath)) {// HFileLink Reference 如果它是一个HFileLink型的this.link = new HFileLink(conf, referencePath);} else {// 只是引用this.link = null;}} else if (isHFile(p)) {// HFilethis.reference = null;this.link = null;} else {throw new IOException("path=" + p + " doesn't look like a valid StoreFile");}

View Code

它有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列表。。

for (Path path: fileLink.getLocations()) {if (path.equals(currentPath)) continue;try {in = fs.open(path, bufferSize);in.seek(pos);assert(in.getPos() == pos) : "Link unable to seek to the right position=" + pos;if (LOG.isTraceEnabled()) {if (currentPath != null) {LOG.debug("link open path=" + path);} else {LOG.trace("link switch from path=" + currentPath + " to path=" + path);}}currentPath = path;return(in);} catch (FileNotFoundException e) {// Try another file location
        }
}

View Code

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

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的正确的图,应该是下面这个,但是上面的那个图看起来好看一点,就保留了。

转载于:https://www.cnblogs.com/cenyuhai/p/3722644.html

hbase源码系列(九)StoreFile存储格式相关推荐

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

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

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

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

  3. 框架源码系列九:依赖注入DI、三种Bean配置方式的注册和实例化过程

    一.依赖注入DI 学习目标 1)搞清楚构造参数依赖注入的过程及类 2)搞清楚注解方式的属性依赖注入在哪里完成的. 学习思路 1)思考我们手写时是如何做的 2)读 spring 源码对比看它的实现 3) ...

  4. hbase源码系列(五)Trie单词查找树

    在上一章中提到了编码压缩,讲了一个简单的DataBlockEncoding.PREFIX算法,它用的是前序编码压缩的算法,它搜索到时候,是全扫描的方式搜索的,如此一来,搜索效率实在是不敢恭维,所以在h ...

  5. hbase源码系列(八)从Snapshot恢复表

    在看这一章之前,建议大家先去看一下snapshot的使用.这一章是上一章snapshot的续集,上一章了讲了怎么做snapshot的原理,这一章就怎么从snapshot恢复表. restoreSnap ...

  6. adreno源码系列(九)全局内存申请

    1. kgsl_allocate_global struct kgsl_memdesc *kgsl_allocate_global(struct kgsl_device *device,u64 siz ...

  7. Spring源码系列(十二)Spring创建Bean的过程(二)

    1.写在前面 上篇博客主要Spring在创建Bean的时候,第一次调用的Bean的后置处理器的过程,同时笔者也打算将整个Spring创建的Bean的过程,通过这个系列,将Bean的创建过程给讲清楚,废 ...

  8. HBase源码分析之HRegion上compact流程分析(三)

    在<HBase源码分析之HRegion上compact流程分析(二)>一文中,我们没有讲解真正执行合并的CompactionContext的compact()方法.现在我们来分析下它的具体 ...

  9. 源码解读_入口开始解读Vue源码系列(二)——new Vue 的故事

    作者:muwoo 转发链接:https://github.com/muwoo/blogs/blob/master/src/Vue/2.md 目录 入口开始解读Vue源码系列(一)--造物创世 入口开始 ...

最新文章

  1. [原]对Linux环境下任务调度一点认识
  2. 序列处理工具|Seqkit
  3. wangEditor 菜单栏随页面滚动位置改变(吸顶)问题解决
  4. 【华为云踩坑】开启了入方向规则的 tcp/80 端口,仍然无法访问
  5. UBUNTU下双显示器设置
  6. 周鸿祎评互联网大佬编程能力:自己可以排前三!是谁排第一?
  7. html的table属性笔记
  8. ML 12 13 mixture of gaussions and EM
  9. 【java】doc转pdf
  10. 如何查看mysql数据库的端口
  11. C++ std::condition_variable wait() wait_for() 区别 怎么用 实例
  12. 电脑计算机无法安3.5,win10 net framework 3.5安装不了的完美解决办法
  13. Windows API实现弹出U盘
  14. yate怎样调出彩色的log日志实时调试信息
  15. 【报名开启】2021年博客之星总评选,属于你的年终表彰
  16. 2022朝花夕拾-持续快速成长
  17. 从“七宗罪”角度,看互联网产品与人性的深沉纠缠
  18. 入门3D游戏建模,是选择角色建模还是场景建模,看完你来选
  19. 【测试源】bbb_sunflower_1080p_30fps_normal.mp4 等下载地址
  20. js中ES6新增的数组方法reduce(),和数组去重,降维。

热门文章

  1. ORB-SLAM2源代码中ROS部分ros-mono源代码中subscribe /camera/image_raw topic谁发布publish的
  2. MRT(MODIS Reprojection Tool) 提取数据
  3. Matlab | Matlab中使用imaqtool工具箱获取摄像头数据及如何安装(摄像头)硬件适配器的图像采集支持包
  4. oracle表空间于表数据啥意思,初识Oracle表空间与数据文件
  5. php短信android,Android_Android短信操作常见协议和常用代码,content://sms/inbox 收件箱 conte - phpStudy...
  6. Windows消息:WM_USER与WM_APP的区别
  7. java 操作 word 表格和样式_poi 操作excel和word(修改样式和内容)
  8. 第四范式受邀参加APEC“人工智能创新应用发展国际论坛”
  9. wxWidgets随笔(2)-hello,world
  10. vb.net2019- 调用 opencv