当 client 向 hregion 端 put() 数据时, HRegion 会判断当前的 memstore 的大小是否大于参数hbase.hregion.memstore.flush.size 值,如果大于,则执行 flushcache() 操作,将 hregion 上的 memstore 刷新到 store
files 文件里。

而在 flushcache 时,会先判断当前的 region 是否满足以下条件

Store files number > 参数hbase.hstore.blockingStoreFiles 值

如果满足,则会将该 region 放入 CompactSplitThread 线程的队列中,等待被compactsplit 。

接着继续执行flushcache() 操作,将建立快照,然后将快照刷新到hdfs 的store files 上。

Flushcache 完成后,还会再判断store file 数是否大于

参数 hbase.hstore.compaction.min ,如果是,还会将其加入到 CompactSplitThread 队列中。

而在线程中 CompactSplitThread 会对region 进行compact 和split 。

Compact 就是将之前多次刷新到hdfs 上的store files 合并成一个文件,同时将这之前的日志清理干净。

Split 就是将一个大的region 拆分成二个region 。

下面讲一个compact 的合并过程。

之前的 store file 是从 memstore 内存中刷新到 hdfs 上,每个文件是有顺序的,现在就要将这多个有顺序的 store files 组装成一个有顺序的 file 。 Hbase 中是如何进行的呢?

首先对每个store file 包装成StoreFileScanner 类。该类有以下方法。

public KeyValue peek();// 取得当前的kv 。

public KeyValue next()// 取下一个kv 。就是从hdfs 读取下一个kv 。

public boolean seek(KeyValue key)// 对当前的KV 赋值。

然后再对要合并的store files 新建StoreFileScanner 对象。如下代码:

filesToCompact 队列为最原始的Store files 。

List<StoreFileScanner> scanners =

new ArrayList< StoreFileScanner >(filesToCompact.size());

for (StoreFile file : filesToCompact ) {

StoreFile.Reader r = file.createReader();

scanners.add(r.getStoreFileScanner(cacheBlocks, usePread));

}

然后再用 StoreScanner 对象将前面的 StoreFileScanner 对象队列包装一下。

scanner = new StoreScanner( this , scan, scanners, !majorCompaction);

这样一来,只需要调用 scanner 对象的 next() 方法就可以从这些 store files 依次得到有序的 kv 对。

然后再将这些有序的 KV 对有序地 append 到最终新建的 store file 上。

这样就完成了整个合并过程,代码如下:

ArrayList<KeyValue> kvs = new ArrayList<KeyValue>();

while (scanner.next(kvs)) {

if (writer == null && !kvs.isEmpty()) {

writer = createWriterInTmp(maxKeyCount,

this.compactionCompression);

}

if (writer != null) {

// output to writer:

for (KeyValue kv : kvs) {

writer.append(kv);

}

}

}

接下来需要关注的是 StoreScanner 类中的 next() 方法是如何从它的多个 store files 的 StoreFileScanner 对象中获取有序的 kv 对的呢?

首先看 StoreScanner 类的构造函数,这里定义了 KeyValueHeap 对象,将之前定义的 store files 的StoreFileScanner 包装起来。

StoreScanner (Store store, Scan scan, List<? extends KeyValueScanner>
scanners,

boolean retainDeletesInOutput)

throws IOException {

this.store = store;

this.cacheBlocks = false;

this.isGet = false;

matcher = new ScanQueryMatcher(scan, store.getFamily().getName(),

null, store.ttl, store.comparator.getRawComparator(),

store.versionsToReturn(scan.getMaxVersions()), retainDeletesInOutput);

// Seek all scanners to the initial key

for(KeyValueScanner scanner : scanners) {

scanner.seek( matcher.getStartKey() );

}// 将所有的 store files 中 scanner 对象定位到起始的 key 开始。

// Combine all seeked scanners with a heap

heap = new KeyValueHeap (scanners, store.comparator);

// 定义一个 KV 对的堆。

}

StoreScanner.next() 方法中可以看出来在 StoreScanner 取下一个有序的 KV 时,只需要调用KeyValueHeap 的 next() 方法即可返回当前最小的 kv 对象。

KeyValue peeked = this.heap.peek();

if (peeked == null) {

close();

return false;

}

那么最关键的地方就到 KeyValueHeap 类中。

这个类有三个变量,分别如下:

private PriorityQueue<KeyValueScanner> heap = null;

files 的 scanner 对象的队列

private KeyValueScanner current = null;

当前 kv 所在的 scanner 对象

private KVScannerComparator comparator;

scanner 对象的比较类,默认的类是 KVScannerComparator 。

在这里用到 java 中的 PriorityQueue 优先级队列。此队列按照在构造时所指定的顺序对元素排序,可以根据我们指定的 Comparator 来排序。

而这里的比较为就是对调用之前的 store files 的 scanner 对象获得它们当前的 kv ,然后对其进行比较。

public int compare(KeyValueScanner left, KeyValueScanner right) {

int comparison = compare(left.peek(), right.peek());

if (comparison != 0) {

return comparison;

} else {

long leftSequenceID = left.getSequenceID();

long rightSequenceID = right.getSequenceID();

if (leftSequenceID > rightSequenceID) {

return -1;

} else if (leftSequenceID < rightSequenceID) {

return 1;

} else {

return 0;

}

}

}

在构建队列 heap 时,将 store files 的 scanner 对象加入到队列时,会按照 KVScannerComparator 对所有的 store files 的 scanner 对象进行排序,同时保证队首永远是 kv 最小的,并将 kv 最小的 scanner 对象赋值给current 变量。

public KeyValueHeap(List<? extends KeyValueScanner> scanners,

KVComparator comparator) {

this.comparator = new KVScannerComparator(comparator);

if (!scanners.isEmpty()) {

this.heap = new PriorityQueue<KeyValueScanner>(scanners.size(),

this.comparator);

for (KeyValueScanner scanner : scanners) {

if (scanner.peek() != null) {

this.heap.add(scanner);

} else {

scanner.close();

}

}

this.current = heap.poll();

}

}

而调用 Next() 方法时,从 store files 中的 scanner 对象队列中取得下一个最小的 kv 对象,具体代码如下:

public boolean next(List<KeyValue> result, int limit) throws IOException {

if (this.current == null) {

return false;

}

InternalScanner currentAsInternal = (InternalScanner)this.current;

boolean mayContainsMoreRows = currentAsInternal.next(result, limit);// 返回当前最小的 scanner 对象中当前的 k 的 kv 对象

// 对取完最小元素的 scanner 对象向后移动。

KeyValue pee = this.current.peek();

// 如果下一个 kv 对不为 null ,则可以加入到 heap 中,否则不加入。

if (pee == null || !mayContainsMoreRows) {

this.current.close();

} else {

this.heap.add(this.current);

}

this.current = this.heap.poll();// 再从 heap 中取最小元素的 scanner 对象。

return (this.current != null);

}

总结:

用 StoreFileScanner 将 store file 封装好,提供 next() 和 peek() 方法来获取文件的后面的元素和当前元素。

用 KeyValueHeap 将所有 file 的 StoreFileScanner 对象按照 StoreFileScanner 对象当前的 kv 大小进行排序,如果某个 scanner 对象是最小 KV ,则将其往后移获得下一个元素,依次这样下去,直到全部取完为止,来保证顺序。

同样,在 mapreduce 里,也有合并过程,就是在 reduce 端对 map 传输过来的分片进行合并。和 hbase 中的结果合并方法是一致的。

Mapredue 中用 segment 来包装从 map 过来的 file ,提供了对当前文件的读取下一个 kv 的方法, MergeQueue类继承了 PriorityQueue ,同样提供了 next() 方法有序的从多个文件里读取 KV 对,详细代码在 Merger 类中。

HBase Compaction的更多相关文章

  1. HBase Compaction详解

    HBase Compaction策略 RegionServer这种类LSM存储引擎需要不断的进行Compaction来减少磁盘上数据文件的个数和删除无用的数据从而保证读性能. RegionServer ...

  2. hbase实践之flush and compaction

    本文主要涉及flush流程,探讨flush流程过程中引入的问题并阐述2种解决策略,最后简要说明Flush执行策略. 对于Compaction,本文主要探讨Compaction要解决的本质问题以及由Co ...

  3. HBase in 2013

    2013年马上就要过去了,总结下这一年HBase在这么一年中发生的主要变化.影响最大的事件就是HBase 0.96的发布,代码结构已经按照模块化release了,而且提供了许多大家迫切需求的特点.这些 ...

  4. 一条数据的HBase之旅,简明HBase入门教程-开篇

    常见的HBase新手问题: 什么样的数据适合用HBase来存储? 既然HBase也是一个数据库,能否用它将现有系统中昂贵的Oracle替换掉? 存放于HBase中的数据记录,为何不直接存放于HDFS之 ...

  5. HBase运维和优化

    管理工具 HBase ShellHBase Shell是HBase组件提供的基于JRuby IRB的字符界面的交互式客户端程序,通过HBase Shell可以实现对HBase的绝大部分操作 通过hel ...

  6. hbase优化小结

    目录: 1,背景 2,GC 3,hbase cache 4,compaction 5,其他 1,背景 项目组中,hbase主要用来备份mysql数据库中的表.主要通过接入mysql binlog,经s ...

  7. 一条数据的HBase之旅,简明HBase入门教程1:开篇

    [摘要] 这是HBase入门系列的第1篇文章,主要介绍HBase当前的项目活跃度以及搜索引擎热度信息,以及一些概况信息,内容基于HBase 2.0 beta2版本.本系列文章既适用于HBase新手,也 ...

  8. 干货 | Kafka 内核知识梳理,附思维导图

    前面我们已经分享过几篇Kafka的文章,最近简单梳理了下Kafka内核相关的知识,涵盖了Kafka架构总结,副本机制,控制器,高水位机制,日志或消息存储,消息发送与消费机制等方面知识.文末含对应的Ka ...

  9. HBase MetaStore和Compaction剖析

    1.概述 客户端读写数据是先从HBase Master获取RegionServer的元数据信息,比如Region地址信息.在执行数据写操作时,HBase会先写MetaStore,为什么会写到MetaS ...

随机推荐

  1. Ubuntu 安装 texlive2013 及中文支持

    分享一下安装和配置经验. 1.材料准备 texlive的安装包:可以百度下,这里也提供一个下载地址: http://mirror.hust.edu.cn/CTAN/systems/texlive/Im ...

  2. Swift延迟加载的一种用途

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 有以下一种情况: 我们试图用Cocoa的语音合成类NSSpee ...

  3. 安卓高仿QQ头像截取升级版

    观看此篇文章前,请先阅读上篇文章:高仿QQ头像截取: 本篇之所以为升级版,是在截取头像界面添加了与qq类似的阴影层(裁剪区域以外的部分),且看效果图:   为了适应大家不同需求,这次打了两个包,及上图 ...

  4. 最简单的基于FFmpeg的编码器-纯净版(不包含libavformat)

    ===================================================== 最简单的基于FFmpeg的视频编码器文章列表: 最简单的基于FFMPEG的视频编码器(YUV ...

  5. Nginx Upstream模块源码分析(上)

    Upstream模块是一个很重要的模块,很多其他模块都会使用它来完成对后端服务器的访问, 达到反向代理和负载均衡的效果.例如Fastcgi.Memcached.SessionSticky等. 如果自己 ...

  6. Java进阶(三十六)深入理解Java的接口和抽象类

    Java进阶(三十六)深入理解Java的接口和抽象类 前言 对于面向对象编程来说,抽象是它的一大特征之一.在Java中,可以通过两种形式来体现OOP的抽象:接口和抽象类.这两者有太多相似的地方,又有太 ...

  7. 手把手教你轻松实现listview上拉加载

    上篇讲了如何简单快速的的实现listview下拉刷新,那么本篇将讲解如何简单快速的实现上拉加载更多.其实,如果你已经理解了下拉刷新的实现过程,那么实现上拉加载更多将变得轻松起来,原理完全一致,甚至实现 ...

  8. UNIX网络编程——套接字选项(心跳检测、绑定地址复用)

    /* 设置套接字选项周期性消息检测连通性 心跳包. 心博.主要用于长连接. * 参数:套接字, 1或0开启, 首次间隔时间, 两次间隔时间, 断开次数 */ void setKeepAlive( in ...

  9. java linux 项目经常无故被关闭 进程无故消息

    布了几个项目.居然天天会自动的挂掉.急了.花时间解决了一下.总结方案如下: 1.磁盘满了.这大家都懂,清一下 2.tomcat在关闭的或是重启的时候,常常后台进程没有被关闭.需要用ps aux|gre ...

  10. Activity, Service,Task, Process and Thread之间的关系

    Activity, Service,Task, Process and Thread之间到底是什么关系呢? 首先我们来看下Task的定义,Google是这样定义Task的:a task is what ...