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

来源:互联网 发布:vb代码大全 编辑:程序博客网 时间:2024/05/29 18:24

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

 

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

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

 Runnable runnable = new Runnable()        {            public void run()            {                if (CompactionManager.instance.getActiveCompactions() == 0)                {                    CompactionManager.instance.submitBackground(AbstractCompactionStrategy.this.cfs);                }            }        };        StorageService.optionalTasks.schedule(runnable, 5 * 60, TimeUnit.SECONDS);

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层,具体代码如下:

       for (SSTableReader ssTableReader : cfs.getSSTables())        {            if (manifest.levelOf(ssTableReader) < 0)                manifest.add(ssTableReader);        }

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

 

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

submitBackground 函数是在CompactionManager.java中,

        Runnable runnable = new WrappedRunnable()        {            protected void runMayThrow() throws IOException            {                compactionLock.readLock().lock();                try                {                    AbstractCompactionStrategy strategy = cfs.getCompactionStrategy();                    AbstractCompactionTask task = strategy.getNextBackgroundTask(getDefaultGcBefore(cfs));                    if (task == null || !task.markSSTablesForCompaction())                        return;                    try                    {                        task.execute(executor);                    }                    finally                    {                        task.unmarkSSTables();                    }                    submitBackground(cfs);                }                finally                {                    compactionLock.readLock().unlock();                }            }        };    return executor.submit(runnable);
该函数主要工作就是创建一个线程类对象runnable,然后进行提交。这里executor类似于一个线程池,大小默认是节点的processor的数目。那么看上去LeveledCompactionStrategy的整个compaction过程像是一个多线程的过程。其实不然。LeveledCompactionStrategy采用了特殊的机制,确保这是但一线程在进行操作,这样的目的是保证LeveledCompactionStrategy的L0以上的层都能够是有序的这一特性。

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

        LeveledCompactionTask currentTask = task.get();        if (currentTask != null && !currentTask.isDone())        {            logger.debug("Compaction still in progress for {}", this);            return null;        }
这段代码就保证了单线程的操作,currentTask.isDone()这函数中用到了java中CountDownLatch这个比较好用的线程同步手段,类似于join的功能。

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

        if (level == 0)        {            // because L0 files may overlap each other, we treat compactions there specially:            // a L0 compaction also checks other L0 files for overlap.            Set<SSTableReader> candidates = new HashSet<SSTableReader>();            // pick the oldest sstable from L0, and any that overlap with it            List<SSTableReader> ageSortedSSTables = new ArrayList<SSTableReader>(generations[0]);            Collections.sort(ageSortedSSTables, SSTable.maxTimestampComparator);            List<SSTableReader> L0 = overlapping(ageSortedSSTables.get(0), generations[0]);            L0 = L0.size() > MAX_COMPACTING_L0 ? L0.subList(0, MAX_COMPACTING_L0) : L0;            // add the overlapping ones from L1            for (SSTableReader sstable : L0)                candidates.addAll(overlapping(sstable, generations[1]));            return candidates;        }        // for non-L0 compactions, pick up where we left off last time        Collections.sort(generations[level], SSTable.sstableComparator);        for (SSTableReader sstable : generations[level])        {            // the first sstable that is > than the marked            if (sstable.first.compareTo(lastCompactedKeys[level]) > 0)                return overlapping(sstable, generations[(level + 1)]);        }        // or if there was no last time, start with the first sstable        return overlapping(generations[level].get(0), generations[(level + 1)]);
从代码中可以看出对于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);  方法。

        if (notification instanceof SSTableAddedNotification)        {            SSTableAddedNotification flushedNotification = (SSTableAddedNotification) notification;            manifest.add(flushedNotification.added);        }        else if (notification instanceof SSTableListChangedNotification)        {            SSTableListChangedNotification listChangedNotification = (SSTableListChangedNotification) notification;            switch (listChangedNotification.compactionType)            {                // Cleanup, scrub and updateSSTable shouldn't promote (see #3989)                case CLEANUP:                case SCRUB:                case UPGRADE_SSTABLES:                    manifest.replace(listChangedNotification.removed, listChangedNotification.added);                    break;                default:                    manifest.promote(listChangedNotification.removed, listChangedNotification.added);                    break;            }        }
通过代码我们可以看出两种类型的notification ,一种是SSTableAddedNotification(这是在一个新的sstable加入的时候,那时也会调用该函数,通知compaction新的sstable加入了,可能需要进行compaction了),第二个就是SSTableListChangedNotification,这就是compaction结束之后需要处理的部分,可以看到有不少的分支,compaction过程需要走的是manifest.promote(listChangedNotification.removed, listChangedNotification.added)这条分支,在相应的函数中会删除老的sstable,添加新的sstable。


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