`
ahuaxuan
  • 浏览: 640663 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

深入浅出 jackrabbit 九 索引合并(下)

阅读更多

在上文中,ahuaxuan讲到了索引创建的主体流程,但是索引合并其实还有一个较为重要的细节ahuaxuan没有详细阐述。本文中,ahuaxuan将会详细阐述这个问题

         本文分成两部分内容

         1  考虑应用拓机时的数据正确性问题。

         2  jackrabbit是如何解决这些问题的。
 
         而这个细节将会直接影响我们对query module的改造,这个细节虽然不难,但是却很重要,是jackrabbit中一个比较重要的设计。下面让我们一起来看看这个是什么样的细节。
 
         回顾上文,我们知道一个目录合并的主要逻辑是10个以上同层次(一共10个层次,还记得否)的小目录会合并成上一个层次的目录,我们再来看看这幅图:


那么现在的问题是

1.       当低层次的多个小目录合并完成一个高层次的目录之后,我们需要把这些目录删除。

2.       并且要通知程序产生了一个新的目录。
 
但是这个时候程序突然挂掉,怎么办呢。那么就必须有一个恢复机制。

         需要被删除或者需要增加的目录的信息如果没有持久化的机制,那么程序再启动的时候就无法分辨哪些索引数据是要删除的,哪些是新增的。咋办呢,咋整呢?我们得有一个持久化的机制来证明哪些索引目录是需要被删除,哪些是有效的索引目录,这样Repository启动的时候就可以拿到正确的IndexReader。

         Ahuaxuan在前面的文章分析过,Action的接口以及其实现类:


 
其中AddIndex和DeleteIndex这两个Action值的关注,这两个Action一个是创建PersistentIndex,一个是删除PersistentIndex,前面讲过一个PersistentIndex对应一个索引目录,AddIndex和DeleteIndex中必然包含着PersistentIndex相关信息持久化的问题。
 
我们先来看看AddIndex类的execute方法:

public void execute(MultiIndex index) throws IOException {

            PersistentIndex idx = index.getOrCreateIndex(indexName);

            if (!index.indexNames.contains(indexName)) {

                index.indexNames.addName(indexName);

                // now that the index is in the active list let the merger know about it

                index.merger.indexAdded(indexName, idx.getNumDocuments());

            }

        }

 
Ok, 代码写得很清楚,如果indexNames不包含一个PersistentIndex的name,那么就将这个PersistentIndex的indexName加入到indexNames中。

再来看看DeleteIndex的execute方法:

public void execute(MultiIndex index) throws IOException {

            // get index if it exists

            for (Iterator it = index.indexes.iterator(); it.hasNext();) {

                PersistentIndex idx = (PersistentIndex) it.next();

                if (idx.getName().equals(indexName)) {

                    idx.close();

                    index.deleteIndex(idx);

                    break;

                }

            }

        }

 
也写的很清楚,当删除一个PersistentIndex时,检查一下indexs(PersistentIndex的集合)中是否包含这个要删除的PersistentIndex,如果包含就执行index.deleteIndex方法:

synchronized void deleteIndex(PersistentIndex index) {

        // remove it from the lists if index is registered

        indexes.remove(index);

        indexNames.removeName(index.getName());

        // during recovery it may happen that an index had already been marked

        // deleted, so we need to check if it is already marked deleted.

        synchronized (deletable) {

            if (!deletable.contains(index.getName())) {

                deletable.addName(index.getName());

            }

        }

}

 
进入这个方法后,我们可以看到,所谓的删除,就是把indexNames中的PersistentIndex的name删除掉,并把这个要删除的indexName加入到deletable中。
 
关键是indexNames和deletable到底是个什么东西,从这里我们可以看出来,其实indexNames和deletable就记录着PersistentIndex的name,也就是说这两个对象中保存着有效的索引目录和需要被删除的索引目录。当10个目录合并成一个目录的时候,就是把10个目录的name从indexNames中删除,并加入到deletable中去。

Jackrabbit就是通过这种方式来保证应用拓机时索引数据的正确性。

接下来,我们来看看deletable和indexNames到底是什么对象:

/**
     * Names of active persistent index directories.
     */
private final IndexInfos indexNames = new IndexInfos("indexes"); 

    /**
     * Names of index directories that can be deleted.
    */
private final IndexInfos deletable = new IndexInfos("deletable");

 

 

这样我们就明白了,indexs和deletable原来是同一种对象。而且可以肯定,IndexInfos这个类具有持久化的功能,它需要把自身包含的数据持久化到磁盘上。

接着我们来看看这个类中有些什么东西:

class IndexInfos {

    /**
     * For new segment names.
     */
    private int counter = 0;

    /**
     * Flag that indicates if index infos needs to be written to disk.
     */

    private boolean dirty = false;

    /**
     * List of index names
     */
    private List indexes = new ArrayList();
 
    /**
     * Set of names for quick lookup.
     */
    private Set names = new HashSet();

………
}
 


从这段代码看来,似乎这个类里面只有一个indexes是我们已知的(names是为了快速判断一个indexname是否在indexs这个list中),而且我们没有看到持久化的相关信息,抱着这样的想法,我们继续往下看,下面我们再来看看如何把一个PersistentIndex的数据indexName加入这个类中:

void addName(String name) {

        if (names.contains(name)) {

            throw new IllegalArgumentException("already contains: " + name);

        }

        indexes.add(name);

        names.add(name);

        dirty = true;

    }

  
还是没有持久化的信息,ahuaxuan很焦虑,肾上腺激素含量开始升高,啥都别说了,接着看吧,1秒钟之后,终于发现这个方法:

void write(File dir) throws IOException {

        // do not write if not dirty

        if (!dirty) {

/*目录没有变化,直接返回*/

            return;
       }

   /*有新的数据添加进来,需要持久化了,创建一个新文件indexs.new */

        File nu = new File(dir, name + ".new");

 

        OutputStream out = new FileOutputStream(nu);

        try {

            DataOutputStream dataOut = new DataOutputStream(out);

/*前4个byte写入new segment names, 但是我没有看到这个变量再什么地方被使用*/

            dataOut.writeInt(counter);

/*前5-8个byte写入目录总数*/

            dataOut.writeInt(indexes.size());

            for (int i = 0; i < indexes.size(); i++) {

/*写入每个indexName*/
                dataOut.writeUTF(getName(i));

            }
        } finally {
            out.close();
        }

        // delete old
        File old = new File(dir, name);
        if (old.exists() && !old.delete()) {
            throw new IOException("Unable to delete file: " + old.getAbsolutePath());
        }

/*删除索引目录中的indexs文件,并将indexs.new改名成indexs*/
        if (!nu.renameTo(old)) {
            throw new IOException("Unable to rename file: " + nu.getAbsolutePath());
        }
        dirty = false;

    }

 
由此可见,IndexInfos确实有把新添加的PersistentIndex对应的目录持久化起来,什么时候做这件事情呢,当然是在添加索引介绍的时候,比如说flush的时候,没错,就是前面讲到的multiIndex#update中的三大方法中的flush(记住它的触发条件哦),flush的时候,内存中的有效的索引目录的信息就会被持久化到磁盘上。

同样的道理,deletable中也是这样的逻辑,要删除的目录也会被持久化起来。

既然保存下来了,我们不妨看看什么时候会用到,于是乎查看read方法:

void read(File dir) throws IOException {

        InputStream in = new FileInputStream(new File(dir, name));

        try {
           DataInputStream di = new DataInputStream(in);
            counter = di.readInt();
            for (int i = di.readInt(); i > 0; i--) {

                String indexName = di.readUTF();

                indexes.add(indexName);

                names.add(indexName);

            }
        } finally {
            in.close();
        }
    }

 
果不其然,有这么一个read方法,这个方法就是负责解析文件,并把文件中的数据拿出来放到内存中。那么这个方法是谁来调用的呢:ctrl+shift+g.

发现在MultiIndex的构造方法里确实用到了:

MultiIndex(File indexDir,

               SearchIndex handler,

               Set excludedIDs,

               NamespaceMappings mapping) throws IOException {

 

        this.indexDir = indexDir;
        this.handler = handler;
        this.cache = new DocNumberCache(handler.getCacheSize());
        this.redoLog = new RedoLog(new File(indexDir, REDO_LOG));
        this.excludedIDs = new HashSet(excludedIDs);
        this.nsMappings = mapping;
 
        if (indexNames.exists(indexDir)) {
/*读取有效的目录*/
            indexNames.read(indexDir);

        }

        if (deletable.exists(indexDir)) {
/*读取无效的目录信息*/
            deletable.read(indexDir);
        }
 
        // try to remove deletable files if there are any

/*删除之*/
        attemptDelete();
……….
}
 

通过这种方式,jackrabbit就可以保证在程序在不执行添加索引,或者索引合并(因为这两个操作中都有AddIndex和DeleteIndex被执行,也就是说这个两个操作都会导致目录变更)的时候突然拓机的情况下,程序重启还能正常提供服务。

如果程序正在执行merge操作,产生了新目录,需要删除老的目录,这个时候情况比较麻烦:

1在这两个信息没有被持久化到磁盘上之前程序歇菜了,那可能还好办,毕竟原始的数据还在磁盘上,不过产生的新目录不能被读取到,因为不在indexes文件里。

2但是indexNames持久化成功,deletable持久化失败,那就没有办法了,这样就会导致这些个需要删除的目录信息不存在于deletable中,而新的有效目录也存在于indexNames中,那么程序重启的时候能读到这个目录,但是不知道哪些目录需要被删除。

在这样的场景下会产生一些冗余目录和冗余文件,但是不影响正常数据,后面会讲到redolog和indexes的关系,很重要,是保证数据完整性的重要一步。
 
总结

真相如此简单,但是却不得不考虑,由此证明,写代码,写框架,尤其数据库之类的东西,重要的是逻辑的严谨性,最重要的还是逻辑的严谨性,如同设计模式这类的东西只是辅助技巧,切不可舍本求末,亦不可舍主求次。主次分明才是最好的平衡。

 

To be continue

分享到:
评论

相关推荐

    深入浅出 jackrabbit 1

    《深入浅出 Jackrabbit 1》 Jackrabbit 是一个开源的、实现了 Java Content Repository (JCR) API 的内容管理系统,它允许程序通过统一的方式访问、存储和管理各种数据,包括文本、图像、视频等多媒体信息。这篇...

    jackrabbit最全入门教程

    这个“jackrabbit最全入门教程”将会带你深入理解这个强大的内容管理解决方案。 首先,我们需要了解什么是JCR。JCR提供了一种统一的方式来访问和管理数字内容,无论这些内容是文档、图像、视频还是其他形式的数据。...

    JackRabbit 学习参考资料总汇

    JackRabbit学习参考资料总汇涉及了深入浅出的JackRabbit内容仓库API的学习,内容涉及多个专题,整个学习资料是PDF文档格式。从标签来看,这份资料主要涉及JackRabbit以及JCR(Java Content Repository)的内容仓库...

    jackrabbit

    在项目实施过程中,尤其是那些需要管理树状结构内容的应用场景下,Jackrabbit 的优势尤为明显。例如,管理主题树、仪表盘树、资源树等,并处理它们之间的关联。 #### 二、关键问题及解决策略 ##### 问题1:JCR存储...

    jackrabbit-standalone-1.5.6.jar jackrabbit 开发包

    jackrabbit 1.5.6 jar

    Apache Jackrabbit入门

    在本文中,我们将深入探讨Apache Jackrabbit的基础知识,以及如何开始使用它。 一、JCR和Apache Jackrabbit的概念 1. JCR:JCR为存储和检索非结构化信息提供了一个模型和API。它允许开发者创建可以跨各种存储后端...

    Jackrabbit入门实例

    在这个"Jackrabbit入门实例"中,你将找到一系列在Eclipse环境下运行的示例项目,帮助初学者快速理解并掌握Jackrabbit的使用。 首先,让我们深入了解一下JCR。JCR是一个接口规范,定义了如何存储、检索和管理半结构...

    Jackrabbit API

    Apache Jackrabbit API 是一个强大的内容管理系统(CMS)的核心组件,它是Apache Software Foundation 开发的Java Content Repository (JCR) 的实现。JCR 是一个标准,它定义了一个用于存储、管理和检索结构化内容的...

    jackrabbit-standalone

    jackrabbit-standalone-1.6.5.jar是webDav的支持jar包。

    查看jackrabbit仓库的小工具

    标题中的“查看jackrabbit仓库的小工具”指的是一个用于观察和管理Apache Jackrabbit仓库的实用程序。Jackrabbit是Java Content Repository (JCR) API的一个开源实现,它提供了一个内容管理系统(CMS)的基础框架,...

    jackrabbit教程

    Apache Jackrabbit 是一个...对于开发人员来说,理解这些功能以及如何将 Jackrabbit 集成到现有应用中是深入学习的关键部分。通过实践示例代码和探索 Jackrabbit API 文档,你可以逐步掌握这个强大的内容管理系统框架。

    jackrabbit-webdav-2.3.2.src.zip

    在Jackrabbit项目中,"org"下的子文件夹会按照项目模块和类的功能进行划分,例如"org.apache.jackrabbit.webdav"可能是WebDAV相关的主模块,而"org.apache.jackrabbit.webdav.client"可能是客户端实现的代码,"org....

    jackrabbit-webdav-2.1.0.jar

    jackrabbit-webdav-2.1.0.jar 具体用法可以网上查找

    jackrabbit2.6

    Apache Jackrabbit 2.6 是一个开放源代码的、基于Java的内容管理系统(CMS),它实现了JCR(Java Content Repository)规范,提供了一种用于存储、管理和检索非结构化数据的标准接口。Jackrabbit 提供了一个高性能、...

    jackrabbit内容仓库的实例(两个工程)

    这两个项目将帮助我们深入理解和快速入门Jackrabbit的使用。 1. Jackrabbit核心概念: - JCR:JSR 170定义了内容存储的标准接口,使得应用程序可以透明地访问和操作不同类型的存储系统。 - Node:在JCR中,内容被...

    jackrabbit jar包

    jackrabbit开发用jar包,jackrabbit是基于Lucene的一种站内搜索技术,它用xml文件为他的元数据,自动穿件索引,使用xpath或者xquery的查询方法。

    jackrabbit-webdav-2.7.1.zip

    标题中的"jackrabbit-webdav-2.7.1.zip"指的是Apache Jackrabbit的一个特定版本——2.7.1的WebDAV模块的压缩包。Apache Jackrabbit是Java内容存储库(Content Repository)的一个实现,它遵循JCR(Java Content ...

    jackrabbit-api-1.5.0.jar

    jackrabbit-api-1.5.0.jar

    Jackrabbit学习篇

    `jackrabbit-core`是整个Jackrabbit项目的基础,负责处理数据的存储、索引和集群等功能。无论是在单机还是集群环境下运行,该核心包都是必不可少的组件。本文将深入探讨`jackrabbit-core`的关键组成部分,并通过具体...

Global site tag (gtag.js) - Google Analytics