深入浅出 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方法:

1.public void execute(MultiIndex index) throws IOException {
2.
3.            PersistentIndex idx = index.getOrCreateIndex(indexName);
4.
5.            if (!index.indexNames.contains(indexName)) {
6.
7.                index.indexNames.addName(indexName);
8.
9.                // now that the index is in the active list let the merger know about it
10.
11.                index.merger.indexAdded(indexName, idx.getNumDocuments());
12.
13.            }
14.
15.        }

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

再来看看DeleteIndex的execute方法:

1.public void execute(MultiIndex index) throws IOException {
2.
3.            // get index if it exists
4.
5.            for (Iterator it = index.indexes.iterator(); it.hasNext();) {
6.
7.                PersistentIndex idx = (PersistentIndex) it.next();
8.
9.                if (idx.getName().equals(indexName)) {
10.
11.                    idx.close();
12.
13.                    index.deleteIndex(idx);
14.
15.                    break;
16.
17.                }
18.
19.            }
20.
21.        }

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

1.synchronized void deleteIndex(PersistentIndex index) {
2.
3.        // remove it from the lists if index is registered
4.
5.        indexes.remove(index);
6.
7.        indexNames.removeName(index.getName());
8.
9.        // during recovery it may happen that an index had already been marked
10.
11.        // deleted, so we need to check if it is already marked deleted.
12.
13.        synchronized (deletable) {
14.
15.            if (!deletable.contains(index.getName())) {
16.
17.                deletable.addName(index.getName());
18.
19.            }
20.
21.        }
22.
23.}

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

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

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

1./**
2.     * Names of active persistent index directories.
3.     */
4.private final IndexInfos indexNames = new IndexInfos("indexes");
5.
6.    /**
7.     * Names of index directories that can be deleted.
8.    */
9.private final IndexInfos deletable = new IndexInfos("deletable");
10.
11.

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

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

1.class IndexInfos {
2.
3.    /**
4.     * For new segment names.
5.     */
6.    private int counter = 0;
7.
8.    /**
9.     * Flag that indicates if index infos needs to be written to disk.
10.     */
11.
12.    private boolean dirty = false;
13.
14.    /**
15.     * List of index names
16.     */
17.    private List indexes = new ArrayList();
18.
19.    /**
20.     * Set of names for quick lookup.
21.     */
22.    private Set names = new HashSet();
23.
24.………
25.}

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

1.void addName(String name) {
2.
3.        if (names.contains(name)) {
4.
5.            throw new IllegalArgumentException("already contains: " + name);
6.
7.        }
8.
9.        indexes.add(name);
10.
11.        names.add(name);
12.
13.        dirty = true;
14.
15.    }

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

1.void write(File dir) throws IOException {
2.
3.        // do not write if not dirty
4.
5.        if (!dirty) {
6.
7./*目录没有变化,直接返回*/
8.
9.            return;
10.       }
11.
12.   /*有新的数据添加进来,需要持久化了,创建一个新文件indexs.new */
13.
14.        File nu = new File(dir, name + ".new");
15.
16.
17.
18.        OutputStream out = new FileOutputStream(nu);
19.
20.        try {
21.
22.            DataOutputStream dataOut = new DataOutputStream(out);
23.
24./*前4个byte写入new segment names, 但是我没有看到这个变量再什么地方被使用*/
25.
26.            dataOut.writeInt(counter);
27.
28./*前5-8个byte写入目录总数*/
29.
30.            dataOut.writeInt(indexes.size());
31.
32.            for (int i = 0; i < indexes.size(); i++) {
33.
34./*写入每个indexName*/
35.                dataOut.writeUTF(getName(i));
36.
37.            }
38.        } finally {
39.            out.close();
40.        }
41.
42.        // delete old
43.        File old = new File(dir, name);
44.        if (old.exists() && !old.delete()) {
45.            throw new IOException("Unable to delete file: " + old.getAbsolutePath());
46.        }
47.
48./*删除索引目录中的indexs文件,并将indexs.new改名成indexs*/
49.        if (!nu.renameTo(old)) {
50.            throw new IOException("Unable to rename file: " + nu.getAbsolutePath());
51.        }
52.        dirty = false;
53.
54.    }

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

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

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

1.void read(File dir) throws IOException {
2.
3.        InputStream in = new FileInputStream(new File(dir, name));
4.
5.        try {
6.           DataInputStream di = new DataInputStream(in);
7.            counter = di.readInt();
8.            for (int i = di.readInt(); i > 0; i--) {
9.
10.                String indexName = di.readUTF();
11.
12.                indexes.add(indexName);
13.
14.                names.add(indexName);
15.
16.            }
17.        } finally {
18.            in.close();
19.        }
20.    }

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

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

1.MultiIndex(File indexDir,
2.
3.               SearchIndex handler,
4.
5.               Set excludedIDs,
6.
7.               NamespaceMappings mapping) throws IOException {
8.
9.
10.
11.        this.indexDir = indexDir;
12.        this.handler = handler;
13.        this.cache = new DocNumberCache(handler.getCacheSize());
14.        this.redoLog = new RedoLog(new File(indexDir, REDO_LOG));
15.        this.excludedIDs = new HashSet(excludedIDs);
16.        this.nsMappings = mapping;
17.
18.        if (indexNames.exists(indexDir)) {
19./*读取有效的目录*/
20.            indexNames.read(indexDir);
21.
22.        }
23.
24.        if (deletable.exists(indexDir)) {
25./*读取无效的目录信息*/
26.            deletable.read(indexDir);
27.        }
28.
29.        // try to remove deletable files if there are any
30.
31./*删除之*/
32.        attemptDelete();
33.……….
34.}

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

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

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

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

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

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

时间: 2024-11-08 21:23:48

深入浅出 jackrabbit 九 索引合并(下)的相关文章

深入浅出 jackrabbit 八 索引合并(上)

我们从文本提取的逻辑中走出来,回到主体流程. 在前面的文章中,我们可以看到一次索引创建的操作,可能会产生多个 persistentindex 对象,而这些对象其实代表着一个索引目录.随着创建索引的次数越来越多,那么索引目录也在增多,但是索引目录中的数据却不是很多,所以我们需要把多个目录合并,其实也就是索引的合并.   执行这个操作的类是 IndexMerger ,看其定义为: 1.class IndexMerger extends Thread implements IndexListener

mysql索引合并:一条sql可以使用多个索引

前言 mysql的索引合并并不是什么新特性.早在mysql5.0版本就已经实现.之所以还写这篇博文,是因为好多人还一直保留着一条sql语句只能使用一个索引的错误观念.本文会通过一些示例来说明如何使用索引合并. 什么是索引合并 下面我们看下mysql文档中对索引合并的说明: The Index Merge method is used to retrieve rows with several range scans and to merge their results into one. The

《深入理解ElasticSearch》——3.6 控制索引合并

3.6 控制索引合并 读者知道(我们已经在第1章中讨论过),在ElasticSearch中每个索引都会创建一到多个分片以及零到多个副本,也知道这些分片或副本本质上都是Lucene索引,而Lucene索引又基于多个索引段构建(至少一个索引段).索引文件中绝大部分数据都是只写一次,读多次,而只有用于保存文档删除信息的文件才会被多次更改.在某些时刻,当某种条件满足时,多个索引段会被拷贝合并到一个更大的索引段,而那些旧的索引段会被抛弃并从磁盘中删除,这个操作称为段合并(segment merging).

Mysql学习笔记(九)索引查询优化

原文:Mysql学习笔记(九)索引查询优化 PS:上网再次看了一下数据库关于索引的一些细节...感觉自己学的东西有点少...又再次的啃了啃索引.... 学习内容: 索引查询优化... 上一章说道的索引还不是特别的详细,再补充一些具体的细节... 1.B-Tree索引... B-tree结构被称为平衡多路查找树...其数据结构为:     这就是其数据结构图...我们没必要完全的理解其中的原理..并且我也不会做过多的原理介绍...我们只需要知道数据库是以这种方式进行存储数据的就可以了...   m

SQL Server查询性能优化之创建合理的索引(下)

续上一篇SQLServer查询性能优化之创建合理的索引(上) 数据库索引分为聚集索引和非聚集索引,聚集索引就是物理索引,也就是数据的物理的存储顺序,聚集索引的叶子节点就是数据行本身:非聚集索引是逻辑索引,也可以简单的认为是对聚集索引建立的索引,一般来说聚集索引的键就是非聚集索引的叶子节点(在不使用include时). 关于索引的选择 对于索引类型来说没什么好选的,一般来说聚集索引是必须的(有特殊需要的另说),非聚集索引看实际需要灵活建立.因此对于索引来说主要是决定在那些列上建立索引,尤其是对于聚

合并 y 语句-求教,请大神帮忙 合并下MySQL语句

问题描述 求教,请大神帮忙 合并下MySQL语句 update women_cb m,wptag w set m.status=2 where w.status=1 and find_in_set(w.id_product,m.ids) select w.id_product,m.ids from women_cb m,wptag w where w.status=1 and find_in_set(w.id_product,m.ids) 解决方案 不懂你想做什么,你想更新还是查询?还是想用un

java程序员菜鸟进阶(九)windows下搭建SVN服务器及配置myeclipse SVN客户端

  java程序员菜鸟进阶(九)windows下搭建SVN服务器及配置myeclipse SVN客户端 分类: 学习专区 java Web    1.下载SVN最新版本安装文件,官网下载地址是:http://subversion.tigris.org/servlets/ProjectDocumentList?folderID=8100,选择最新发布的SVN安装文件进行下载.最新版本是Setup-Subversion-1.6.5.msi,大小7.4MB,安装SVN至我的电脑.最好使用中文路径,而且

SQL Server 深入解析索引存储(下)

原文:SQL Server 深入解析索引存储(下)   标签:SQL SERVER/MSSQL SERVER/数据库/DBA/索引体系结构/非聚集索引 概述   非聚集索引与聚集索引具有相同的 B 树结构,它们之间的显著差别在于以下两点: 基础表的数据行不按非聚集键的顺序排序和存储. 非聚集索引的叶层是由索引页而不是由数据页组成. 既可以使用聚集索引来为表或视图定义非聚集索引,也可以根据堆来定义非聚集索引.非聚集索引中的每个索引行都包含非聚集键值和行定位符.此定位符指向聚集索引或堆中包含该键值的

深入浅出jackrabbit之十四 分布式文档提取

/** *author:ahuaxuan *2009-09-24 */ 前言: 本来针对jackrabbit这一系列的文章其实都是有顺序的,比如先讲索引的创建,然后讲索引的查询,等等,但是无奈总是有些横生的枝节,这些横生的枝节又让ahuaxuan有了一些新的想法.所以只能将这篇文章写到后面来了. 切入正题,今天这篇文章其实是对前面文本提取的一个补充.前面讲到文本提取的逻辑,逻辑有点小复杂,但是复杂的逻辑并不是这篇文章的关键,今天的话题是进程模型和线程模型,也许童鞋们会感到非常的奇怪,一个jack