tair是一个分布式KV存储引擎,当新增机器或者有机器down掉的时候,tair的dataserver会根据ConfigServer生成的新的对照表进行数据的迁移和清理。在数据清理的过程中就用到了在tair中新增的Compaction方式——CompactRangeSelfLevel,顾名思义,这个CompactRangeSelfLevel就是对自己所在(指定)的Level进行一定Key范围的Compaction然后将生成的输出文件也写入到自己所在的Level而不是父层(L + 1)。下面我们来对这个CompactRangeSelfLevel进行分析。

// compact filenumber 小于limit的key 在 [begin, end)范围内的 sstable
// compact的时候只有level 0 SSTable会输出到 level 1, 其他的level都是输入输出在同一个level
Status DBImpl::CompactRangeSelfLevel(
uint64_t limit_filenumber,
const Slice* begin,
const Slice* end) {
// 初始化一个MannualCompaction对象
manual.limit_filenumber = limit_filenumber;
manual.bg_compaction_func = &DBImpl::BackgroundCompactionSelfLevel;
// use TimedCond() 防止丢失唤醒信号
// 每个层级逐次schedule manualcompaction
for (int level = ; level < config::kNumLevels && manual.compaction_status.ok(); ++level) {
ManualCompaction each_manual = manual;
each_manual.level = level;
while (each_manual.compaction_status.ok() && !each_manual.done) {
// still have compaction running 就等待
while (bg_compaction_scheduled_) {
bg_cv_.TimedWait(timed_us);
}
manual_compaction_ = &each_manual;
MaybeScheduleCompaction();
while (manual_compaction_ == &each_manual) {
bg_cv_.TimedWait(timed_us);
}
}
manual.compaction_status = each_manual.compaction_status;
}
return manual.compaction_status;
}

MaybeScheduleCompaction主要是判断是否新启动一个后台的Compaction线程,主要是以是否有Compaction的任务和是否已经有Compaction线程已经在运行为依据,这个函数已经在前面专门解释Compaction的文章中进行了分析,这里不再介绍。我们详细分析一下真正进行工作的BackgroundCompactionSelfLevel函数

void DBImpl::BackgroundCompactionSelfLevel() {
do {
// level-0 不对 filenumber 进行限制
/* CompactRangeOneLevel故名思议将该level中的所有符合filenumber 小于limit
key 在 [begin, end)范围内的 sstable找出来 */
c = versions_-> CompactRangeOneLevel(m->level,
m->level > ? m->limit_filenumber : ~(static_cast<uint64_t>()),
m->begin, m->end); if (NULL == c) { // no compact for this level
m->done = true; // done all.
break;
}
// 记录下manual Compaction的结束key
manual_end = c->input(, c->num_input_files() - )->largest; CompactionState* compact = new CompactionState(c);
status = DoCompactionWorkSelfLevel(compact); // 真正进行Compaction的函数
CleanupCompaction(compact);
c->ReleaseInputs();
DeleteObsoleteFiles();
delete c;
if (shutting_down_.Acquire_Load()) {
// Ignore compaction errors found during shutting down
} else if (!status.ok()) {
m->compaction_status = status; // save error
if (bg_error_.ok()) { // no matter paranoid_checks
bg_error_ = status;
}
break; // exit once fail.
}
} while (false);
if (!m->done) {
// We only compacted part of the requested range. Update *m
// to the range that is left to be compacted.
m->tmp_storage = manual_end;
m->begin = &m->tmp_storage;
}
// Mark it as done
manual_compaction_ = NULL;
}

这里DoCompactionWorkSelfLevel是真正进行KV读取和Compaction的地方,然而我们这里并不打算对其进行详细的分析,因为通过对比我们知道其主题过程和DoCompactionWork相同,只是在一些细微的判断方式和处理方式上稍微有所不同。具体的DoCompactionWork的过程请参考《leveldb源码分析--SSTable之Compaction》,我们下面通过对比 差异之处的方式让大家理解DoCompactionWorkSelfLevel的实际处理过程。

DoCompactionWorkSelfLevel 和 DoCompactionWork基本相同,只是流程上少了几个判断:

1. DoCompactionWorkSelfLevel 遍历到key以后不需要进行ShouldStopBefore的判断,因为这个是判断是否跟L + 2层有过多的重叠,这里selflevel是输出到当前层,所以必然不会影响跟L+ 2层的重叠情况;

Slice key = input->key();
// if (compact->compaction->ShouldStopBefore(key) &&
// compact->builder != NULL) {
// status = FinishCompactionOutputFile(compact, input);
// if (!status.ok()) {
// break;
// }
// }
// 注释掉的地方即是CompactionWorkSelfLevel减少的部分

2. 是否drop的时候少了seq<= smallest_snapshot && (type == Deletion || ShouldDrop) && IsBaseLevelForKey(ikey)) 为drop,也是因为当前层的Compaction,而IsBaseLevelForKey是判断的L + 2层以上有无该key的相关值,这里如果要加上判断就应该是将 L+1 层也包含在判断范围内。

} else if (ikey.sequence <= compact->smallest_snapshot &&
(ikey.type == kTypeDeletion || // deleted or ..
user_comparator()->ShouldDropMaybe(ikey.user_key.data(),
ikey.sequence, expired_end_time)) &&
// .. user-defined should drop(maybe),
// based on some condition(eg. this key only has this update.).
compact->compaction->IsBaseLevelForKey(ikey.user_key)) {
// For this user key:
// (1) there is no data in higher levels
// (2) data in lower levels will have larger sequence numbers
// (3) data in layers that are being compacted here and have
// smaller sequence numbers will be dropped in the next
// few iterations of this loop (by rule (A) above).
// Therefore this deletion marker is obsolete and can be dropped.
drop = true;
}

3. 在InstallCompactionResults 时第二个参数传入的是false,这样这个函数将新生成的SSTable放入当前层而不是L+ 1层

status = InstallCompactionResults(compact);
// 修改为:
status = InstallCompactionResults(compact, false); // output files is in current level, not level + 1

另外这里顺便提一下tair中的leveldb是对google开源的leveldb也有了一定的修改,比如添加expire等特性,在tair中comparator就添加了三个接口函数。而在tair中实现了两个这样的comparator分别是NumericalComparatorImpl和BitcmpLdbComparatorImpl,我们这里以BitcmpLdbComparatorImpl为例进行一下简单的介绍其功能。

// 判断这个key是否在需要回收的bucket中,如果是就返回true,那么Compaction的时候直接删除(即回收掉)
virtual bool ShouldDrop(const char* key, int64_t sequence, uint32_t now = ) const { return false;}
// 根据expire_time判断这个key是否已经过期,如过期则返回true
virtual bool ShouldDropMaybe(const char* key, int64_t sequence, uint32_t now = ) const { return false;}
// start_key和key是否依旧属于同一个bucket,是的放回false,否则返回true
virtual bool ShouldStopBefore(const Slice& start_key, const Slice& key) const { return false;}

有了这三个函数以后tair的ldb引擎就可以在Compaction的时候对key进行回收和判断是否写入同一个SSTable中,比如最直接的Compaction的时候如果ShouldDrop返回true那么直接标记这个key为drop不写入到新的SSTable中;而ShouldStopBefore则被用在是否生成新的SSTable文件,如果返回true则结束当前文件的写入生成下一个SSTable,这样就可以将不同的bucket写入到不同的SSTable文件中了。

tair源码分析——leveldb新增的CompactRangeSelfLevel过程的更多相关文章

  1. tair源码分析——leveldb存储引擎使用

    分析完leveldb以后,接下来的时间准备队tair的源码进行阅读和分析.我们刚刚分析完了leveldb而在tair中leveldb是其几大存储引擎之一,所以我们这里首先从tair对leveldb的使 ...

  2. SpringBoot源码分析之SpringBoot的启动过程

    SpringBoot源码分析之SpringBoot的启动过程 发表于 2017-04-30   |   分类于 springboot  |   0 Comments  |   阅读次数 SpringB ...

  3. Envoy 源码分析--程序启动过程

    目录 Envoy 源码分析--程序启动过程 初始化 main 入口 MainCommon 初始化 服务 InstanceImpl 初始化 启动 main 启动入口 服务启动流程 LDS 服务启动流程 ...

  4. Spring源码分析专题 —— IOC容器启动过程(上篇)

    声明 1.建议先阅读<Spring源码分析专题 -- 阅读指引> 2.强烈建议阅读过程中要参照调用过程图,每篇都有其对应的调用过程图 3.写文不易,转载请标明出处 前言 关于 IOC 容器 ...

  5. Spring源码分析之Bean的创建过程详解

    前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...

  6. 【Spring源码分析】原型Bean实例化过程、byName与byType及FactoryBean获取Bean源码实现

    原型Bean加载过程 之前的文章,分析了非懒加载的单例Bean整个加载过程,除了非懒加载的单例Bean之外,Spring中还有一种Bean就是原型(Prototype)的Bean,看一下定义方式: & ...

  7. springMVC源码分析--HandlerInterceptor拦截器调用过程(二)

    在上一篇博客springMVC源码分析--HandlerInterceptor拦截器(一)中我们介绍了HandlerInterceptor拦截器相关的内容,了解到了HandlerInterceptor ...

  8. 从壹开始微服务 [ DDD ] 之十一 ║ 基于源码分析,命令分发的过程(二)

    缘起 哈喽小伙伴周三好,老张又来啦,DDD领域驱动设计的第二个D也快说完了,下一个系列我也在考虑之中,是 Id4 还是 Dockers 还没有想好,甚至昨天我还想,下一步是不是可以写一个简单的Angu ...

  9. Dubbo源码分析之ExtensionLoader加载过程解析

    ExtensionLoader加载机制阅读: Dubbo的类加载机制是模仿jdk的spi加载机制:  Jdk的SPI扩展加载机制:约定是当服务的提供者每增加一个接口的实现类时,需要在jar包的META ...

随机推荐

  1. Mysql学习笔记(十二)触发器

    学习内容: 1.触发器: 什么是触发器?我们什么时候能够使用触发器?   触发器就是用来监听某个表的变化,当这个表发生变化的时候来触发某种操作..比若说两个表是相互关联的,当我们在对其中一个表格进行操 ...

  2. UWP开发入门(十三)——用Diagnostic Tool检查内存泄漏

    因为.NET的垃圾回收机制相当完善,通常情况下我们是不需要关心内存泄漏的.问题人一但傻起来,连自己都会害怕,几个页面跳啊跳的,内存蹭蹭的往上涨,拉都拉不住.这种时候我们就需要冷静下来,泡一杯热巧克力. ...

  3. sprint个人总结+读书博客

    读书感想:    第8章讲了需求分析,在我的日常软件编写中,肯定需要需求分析的,一个没有需求的软件,编写出来也没有什么意义,只能是丢在一个角落里发霉.需求有各种各样,要怎样才能满足客户的需求呢,那就要 ...

  4. 个人学习对UIView动画的总结

    我的博客之前已经开通五个月了,但是一直没有写东西.一是不敢写,二是也不知道写啥.毕竟是一个刚刚入行大半年的菜鸟,现在总想通过各种办法提高自己.之前总感觉用到一些东西,只是当时搞懂了一点,加上并没有总结 ...

  5. csharp: Socket

    https://github.com/joeandaverde/socket.io-csharp-client http://websocket4net.codeplex.com/ http://ww ...

  6. csharp: InvokeHelper

    Entity Framework https://entityframework.codeplex.com/ Enterprise Library https://entlib.codeplex.co ...

  7. [译]PrestaShop开发者指南 第一篇 基础

    # 第一篇 基础 PS(PrestaShop简称)一开始就设定了能够在它的基础上很简单的构建第三方模块的机制,让它成为一款具有极高定制性的电子商务软件. PS的可以在三个方面进行定制: * 主题 * ...

  8. gcd和拓展gcd算法

    gcd算法是用来求两个数最大公约数的算法,他是依靠辗转相除(中国好像叫辗转相减)法来求两个数的最大公约数,别的地方也有很多介绍不做过多赘述,主要提供代码供自己参考. gcd(int a,int b) ...

  9. java开发过程中从前台传到后台中文乱码《filter》

    在企业开发中,最常见的是javaweb项目,有web项目就免不了和后台打交道,比如我从jsp页面发送新增请求到后台,后台可能是servlet.struts2.springmvc等,这时就存在一个问题, ...

  10. linux backlog深入剖析以及netty设置backlog

    netty不同于socket,其上次API没有提供设置backlog的选项,而是依赖于操作系统的somaxconn和tcp_max_syn_backlog,对于不同OS或版本,该值不同,建议根据实际并 ...