cassandra1.1.0中Compaction部分源代码解析——LeveledCompactionStrategy

近两天事情有点小多,更新速度不太给力,当然这都是借口。Ok,开始LeveledCompactionStrategy的分析。

前篇博客中也提到Compaction启动是在cfs中进行的,其实流程就是调用相应Compaction的构造函数初始化对应的对象。具体构造函数的完成的任务如下:

1)调用父类的构造函数,生成一个延时启动的线程,该线程的功能是在启动5分钟之后调用CompactionManager.instance.submitBackground()方法,该方法的作用前一篇博客也讲到就是提交compaction任务,就是具体进行compaction的工作,也是整个compaction机制的核心。之所以延时启动这个线程,可能是因为compaction是一个非常耗资源的过程,在整个程序启动一段时间后再进行执行是比较合适的。

1)确定LeveledCompactionStrategy每个sstable的大小,因为LeveledCompactionStrategy中管理的每个sstable大小都是基本相同的(除去每次compaction的最后一个sstable,原因很明显)。

2)通过cfs.getDataTracker().subscribe(this);将自身传递给DataTracker,DataTracker是cfs具体管理数据的一个类,在后续的读写部分的源代码解析中也会详细的介绍。其实这个传递起到的主要作用是,cfs与compaction之间的一种信息交互手段,类似于回调函数。

3)创建manifest =LeveledManifest.create(cfs, this.maxSSTableSizeInMB); LeveledManifest其实就是扫描获取LeveledCompactionStrategy管理的sstable的一下元信息。这些元信息都存储在一个XXX.json的数据文件中,其实就是每个层次包含哪些sstable的信息。

那我们可以看到构造函数完成的工作还是非常多的,前三点工作介绍的已经比较清楚了,下面我们分析下第4点。因为LeveledManifest类是LeveledCompactionStrategy的一个很重要的辅助类。我们分析下在它的create函数中发生了那些剧情。

1)创建一个LeveledManifest对象。

LeveledManifest manifest = new LeveledManifest(cfs,maxSSTableSize);

LeveledManifest的构造函数主要完成generations和sstableGenerations这个数据结构的创建,从名字很容易看出两者的功能generations存储的每层存储的sstable的情况,而sstableGenerations存储的每个sstable对应的层次。

2)通过遍历管理的sstable,结合xxx.json确定sstable和层次之间的对应关系,具体确定是通过一个generation的成员变量,其实也就是我们能够看到的每个sstable的编号,这个编号是唯一的。

3)将不在xxx.json中的sstable放入到第0层,具体代码如下:

4)最后返回一个LeveledManifest对象。

下边我们来看compaction的核心部分,就是上文提到延时启动线程调用的部分CompactionManager.instance.submitBackground,当然在前一篇博文中也提到有三个部分可以调用该函数,也就是有三个入口可以触发compaction的过程。

submitBackground 函数是在CompactionManager.java中,

该函数主要工作就是创建一个线程类对象runnable,然后进行提交。这里executor类似于一个线程池,大小默认是节点的processor的数目。那么看上去LeveledCompactionStrategy的整个compaction过程像是一个多线程的过程。其实不然。LeveledCompactionStrategy采用了特殊的机制,确保这是但一线程在进行操作,这样的目的是保证LeveledCompactionStrategy的L0以上的层都能够是有序的这一特性。

具体实现实在getNextBackgroundTask()这个函数中,

这段代码就保证了单线程的操作,currentTask.isDone()这函数中用到了java中CountDownLatch这个比较好用的线程同步手段,类似于join的功能。

具体获取要进行compaction的sstable的功能在manifest.getCompactionCandidates()的函数中实现。获取要compaction的sstable的顺序是从高层向底层检查的,之所以这样做的原因,在其对应代码的注释中有例子进行了解释,这里不再赘述。我们来看一下在每个层次是如何获取待compaction的sstable的,具体代码如下:

从代码中可以看出对于L0层的有特殊的处理方式,因为在LeveledCompactionStrategy中只有L0不同的sstable之间是有重复数据的,所以L0向L1合并时需要在L0找到与合并块有交集的块,这一点与其他层不同,还有一点就是需要限制L0向L1合并时sstable的数目,不能超过32块多余的将进行截断。

这些工作完成以后将形成一个任务(task),最终执行是调用该task的execute()方法。对与不同的compaction机制,这个执行方法都是相同的,具体方法在CompactionTask.java中的execute方法。

具体的compaction过程是:要进行compaction的sstable存储一个叫做tocompact的list中,合并需要先预留出于tocomapct所存储的所有sstable同样大小的磁盘空间,如果没有足够的磁盘空间此次任务将会失败(此处与SizetieredCompactionStrategy不同),原因还是LeveledCompactionStrategy层次之间不能存在重复数据的问题。因为LeveledCompactionStrategy规定每个sstable的大小,所以在compaction之后内容写出的时候,没超过设定的大小就新创建一个sstable。


ok,至此貌似整个compaction过程就结束了,其实细心的童鞋可能已经发现了问题,compaction新的sstable之后,老的sstable是不是该删除了,这些在cfs和compaction中都是怎么执行的呢。

cfs.replaceCompactedSSTables(toCompact, sstables, compactionType);

这个函数是cfs对新老sstable的处理。compaction部分对新老数据的处理是通过DataTracker的replaceCompactedSSTables函数进行的,具体是通过我们一开始提到的replaceCompactedSSTables获得的compactionStrategy的对象进行的,调用LeveledCompactionStrategy的subscriber.handleNotification(notification, this); 方法。

通过代码我们可以看出两种类型的notification ,一种是SSTableAddedNotification(这是在一个新的sstable加入的时候,那时也会调用该函数,通知compaction新的sstable加入了,可能需要进行compaction了),第二个就是SSTableListChangedNotification,这就是compaction结束之后需要处理的部分,可以看到有不少的分支,compaction过程需要走的是manifest.promote(listChangedNotification.removed, listChangedNotification.added)这条分支,在相应的函数中会删除老的sstable,添加新的sstable。


熬夜匆忙写完了,写的不是很仔细,可能里边也有不少的错别字,大家看后有什么不懂的地方,欢迎留言讨论,好了,回宿舍睡觉去。









你可能感兴趣的:(cassandra)