Chinaunix首页 | 论坛 | 博客
  • 博客访问: 128292
  • 博文数量: 69
  • 博客积分: 2895
  • 博客等级: 少校
  • 技术积分: 710
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-03 18:05
文章分类

全部博文(69)

文章存档

2010年(69)

我的朋友

分类:

2010-09-11 15:41:08

我们从文本提取的逻辑中走出来,回到主体流程。

  在前面的文章中,我们可以看到一次索引创建的操作,可能会产生多个 persistentindex 对象,而这些对象其实代表着一个索引目录。随着创建索引的次数越来越多,那么索引目录也在增多,但是索引目录中的数据却不是很多,所以我们需要把多个目录合并,其实也就是索引的合并。

  执行这个操作的类是 IndexMerger ,看其定义为:

  Java代码   

class IndexMerger extends Thread implements IndexListener 
 
/*由此可见它是一个线程,并且同时充当着listener的角色,看看它的构造方法: 
*/ 
IndexMerger(MultiIndex multiIndex) { 
 
    this.multiIndex = multiIndex; 
    setName("IndexMerger"); 
    setDaemon(true); 
    try { 
      mergerIdle.acquire(); 
 
    } catch (InterruptedException e) { 
      // will never happen, lock is free upon construction 
      throw new InternalError("Unable to acquire mutex after construction"); 
    } 
}

  还是一个 deamon 线程。而且一构造就来了一个 mergerIdle.acquire(); 真是迫不及待啊。啥意思啊?得到一把锁,一把非阻塞的锁。

  在创建完 IndexMerger ,那么就有可能把 PersistentIndex 加进来了,因为 Merger 类必须知道哪些 PersistentIndex 是需要 Merger 的,那么我们看看负责这段逻辑的代码:这段代码主要负责 3 个功能,一个是初始化 indexBuckets ,这个一个 ArrayList ,其中放的是需要 Merger 的 PersistentIndex 的列表,也就是我们可以认为 indexBucket 里放的还是 list ,这里有一个非常奇怪的设计,就是在初始化的时候将 PersistentIndex 按照 docnums 的范围分组了,一组就是一个 indexBucket 。

第二个是把需要加入的 PersistentIndex 加入到对应的分组中。

  第三个是判断是否需要合并,如果需要就加到一个队列中,等待被合并。

  先看第一段代码:

  Java代码  

synchronized (lock) { 
 
      // initially create buckets 
      if (indexBuckets.size() == 0) { 
        long lower = 0; 
// default minMergeDocs is 100 
        long upper = minMergeDocs; 
 
//default maxMergeDocs is 2147483647 
// IndexBucket实际上就是一个ArrayList 
 
         while (upper < maxMergeDocs) { 
 
          indexBuckets.add(new IndexBucket(lower, upper, true)); 
          lower = upper + 1; 
//default mergeFactor is 10 
          upper *= mergeFactor; 
 
        } 
 
        // one with upper = maxMergeDocs 
 
        indexBuckets.add(new IndexBucket(lower, maxMergeDocs, false)); 
 
        // and another one as overflow, just in case... 
 
        indexBuckets.add(new IndexBucket(maxMergeDocs + 1, Long.MAX_VALUE, false)); 
      } 
 ············

  仔细阅读代码,我们发现,在初始化 indexBuckets 的代码中,其实按照范围来初始化的,比如当添加第一 IndexBucket 的时候 lower=0 , upper=100

即 new IndexBucket(0 100 , rue )

  第二个则为: new IndexBucket(101, 100*10, t rue )

  第三个则为: new IndexBucket(1001, 100*10*10, t rue )

  第四个则为: new IndexBucket(10001, 100*10*10*10, t rue )

  第五个则为: new IndexBucket(100001, 100*10*10*10*10, t rue )

  ````````````

  一直持续下去直到 upper 小于 2147483647 ,且是 10 的最大幂。那么就是说 10 亿,当一个目录中有 10 亿个 document 的 index 数据时,这个目录将不再参与 merge 过程, indexBuckets 中总共有 8 个 IndexBucket, 不过在循环外面还有两个创建 IndexBucket 的语句,不过这两个都是不允许参加合并的,所以第 3 个参数是 false ,也就是说一共有 10 个,第九个是:

  Java代码  

new IndexBucket(1000000001, 2147483647, false)

  那么第十个是:

  Java代码

new IndexBucket(2147483648, 0x7fffffffffffffffL, false)

  搞清楚 indexBuckets 的 初始化之后,我们再来看看第二个步骤,把根据 docNums 把对应的persistentindex 加入到 IndexBucket 中 :

  Java代码   

// put index in bucket 
 
      IndexBucket bucket = (IndexBucket) indexBuckets.get(indexBuckets.size() - 1); 
 
      for (int i = 0; i < indexBuckets.size(); i++) { 
 
        bucket = (IndexBucket) indexBuckets.get(i); 
 
        if (bucket.fits(numDocs)) { 
          break; 
        } 
      } 
 
/*如果indexBuckets 没有值,那么就把Index 添加到第10个IndexBucket中,否则就从indexBuckets 的第一IndexBucket开始匹配,根据numDocs的值放到对应的IndexBucket中。*/ 
 
      bucket.add(new Index(name, numDocs)); 
 
      if (log.isDebugEnabled()) { 
        log.debug("index added: name=" + name + ", numDocs=" + numDocs); 
 
      } 
 
      // if bucket does not allow merge, we don't have to continue 
 
//如果是最后两个IndexBucket,那么即刻退出 
      if (!bucket.allowsMerge()) { 
        return; 
      } 
 
 
 
/*这段代码没有什么难的,接着看第3个步骤: 
*/      // check if we need a merge 
//超过indexbucket中超过10个元素<其实就是10个目录>则开始合并 
 
      if (bucket.size() >= mergeFactor) { 
 
        long targetMergeDocs = bucket.upper; 
        targetMergeDocs = Math.min(targetMergeDocs * mergeFactor, maxMergeDocs); 
 
        // sum up docs in bucket 
 
        List indexesToMerge = new ArrayList(); 
 
        int mergeDocs = 0; 
 
        for (Iterator it = bucket.iterator(); it.hasNext() && mergeDocs <= targetMergeDocs;) { 
 
          indexesToMerge.add(it.next()); 
 
        } 
/* 结合上下文,indexesToMerge.size()这值会小于2吗?????*/ 
        if (indexesToMerge.size() > 2) { 
 
          // found merge 
 
          Index[] idxs = (Index[]) indexesToMerge.toArray(new Index[indexesToMerge.size()]); 
 
          bucket.removeAll(indexesToMerge); 
 
          if (log.isDebugEnabled()) { 
 
            log.debug("requesting merge for " + indexesToMerge); 
 
          } 
 
          mergeTasks.add(new Merge(idxs)); 
 
          log.debug("merge queue now contains " + mergeTasks.size() + " tasks."); 
        } 
      }

 这段代码的主要功能是把 indexbucket 里的 persistentindex 信息拿出来,而且量超过 2 的话就把他们加入到一个队列中,并将它们从该 indexbucket 里删除。通过这个步骤,那么 mergeTasks 队列中就存在一些需要合并的 index 了。

  中场总结:

  通过上面的方法和前面的索引提交的文章我们得到一些重要信息:当用户把 ramdirectory 中超过 100 的 docs 的 index data 刷到 fsdirectory 中时,新建一个目录,作为这个新 fsdirectory 的目录,接着把这个 fsdirectory 对应的 PersistentIndex 加到 IndexMerger 类的某个 IndexBucket 中,接着当某个 IndexBucket 中的 PersistentIndex 数量(即这些目录的数量)超过 10 ( mergefactor )的时候,就会执行合并的操作。

  那么下面的问题是,合并之后,这 10 个目录将会何去何从,它们是把另外 9 个合并到其中一个中去呢还是怎么滴?接着看吧。

  显然,这里又用到生产消费模型,任何调用 indexAdded 方法的都属性生产者,生产者根据一些条件,有选择的把需要合并的 persistentindex 放到 mergeTasks 的队列中,有了生产者肯定存在消费者,文章开头提过, IndexMerger 类是一个 deamon 线程,看看它的 run 方法,那么就发现,其实它就是消费者。它主要完成以下几个功能:

  1 判断消费者是否空闲

  2 判断队列中是否有退出命令

  3 如果空闲则进入 wait 状态

  4 根据 persistentindex 的名字取到所有的 persistentindex 的 IndexReader 对象

  5 再创建一个新的 PersistentIndex, , 原来的 index 文件合并到这个新的目录中

  6 将前面的 IndexReader 对象添加到 PersistentIndex 的 indexwriter 方法中,并执行optimize。

 7 关闭这些 readers

  8 根据名字删除已经被合并的 PersistentIndex 的索引文件和目录等。

  我们再来看看代码,代码中已经加入了 ahuaxuan 的注释:

  Java代码  

public void run() { 
 
    for (;;) { 
      boolean isIdle = false; 
 
   //队列长度为0,表示消费者处于空闲状态,那么会进入wait状态 
      if (mergeTasks.size() == 0) { 
 
        mergerIdle.release(); 
 
        isIdle = true; 
 
      } 
 
/*2判断队列中是否有退出命令 
 
*/ 
 
      Merge task = (Merge) mergeTasks.remove(); 
 
      if (task == QUIT) { 
        mergerIdle.release(); 
        break; 
      } 
 
      if (isIdle) { 
 
        try { 
          mergerIdle.acquire(); 
        } catch (InterruptedException e) { 
 
          Thread.interrupted(); 
          log.warn("Unable to acquire mergerIdle sync"); 
        } 
      } 
 
       log.debug("accepted merge request"); 
 
       // reset deleted documents 
      deletedDocuments.clear(); 
 
      // get readers 
 
/*4 根据persistentindex的名字取到所有的persistentindex 
 
  的IndexReader对象 
 
*/ 
 
      String[] names = new String[task.indexes.length]; 
 
      for (int i = 0; i < task.indexes.length; i++) { 
        names[i] = task.indexes[i].name; 
      } 
 
      try { 
 
        log.debug("create new index"); 
 
/*再创建一个新的PersistentIndex,原来的index文件合并到这个新的目录中 
 
*/ 
        PersistentIndex index = multiIndex.getOrCreateIndex(null); 
 
        boolean success = false; 
 
        try { 
 
          log.debug("get index readers from MultiIndex"); 
 
          IndexReader[] readers = multiIndex.getIndexReaders(names, this); 
 
          try { 
 
            // do the merge 
 
            long time = System.currentTimeMillis(); 
 
/*6 将前面的IndexReader对象添加到PersistentIndex的indexwriter方法中,并执行optimize。 
 
*/ 
            index.addIndexes(readers); 
 
            time = System.currentTimeMillis() - time; 
 
            int docCount = 0; 
 
            for (int i = 0; i < readers.length; i++) { 
              docCount += readers[i].numDocs(); 
            } 
 
            log.info("merged " + docCount + " documents in " + time + " ms into " + index.getName() + "."); 
 
          } finally { 
            for (int i = 0; i < readers.length; i++) { 
 
/*7 关闭这些readers 
 
*/ 
 
              try { 
                readers[i].close(); 
              } catch (IOException e) { 
                log.warn("Unable to close IndexReader: " + e); 
              } 
            } 
          } 
 
          // inform multi index 
 
          // if we cannot get the sync immediately we have to quit 
 
          if (!indexReplacement.attempt(0)) { 
            log.debug("index merging canceled"); 
            break; 
          } 
 
          try { 
            log.debug("replace indexes"); 
            multiIndex.replaceIndexes(names, index, deletedDocuments); 
          } finally { 
            indexReplacement.release(); 
          } 
           success = true; 
         } finally { 
 
          if (!success) { 
 
            // delete index 
 
            log.debug("deleting index " + index.getName()); 
 
/*8 根据名字删除已经被合并的PersistentIndex的索引文件和目录等。 
 
*/ 
            multiIndex.deleteIndex(index); 
          } 
        } 
      } catch (Throwable e) { 
        log.error("Error while merging indexes: " + e); 
      } 
    } 
    log.info("IndexMerger terminated"); 
  } 

 看到这里爱思考的同学们一定会意识到这里还漏了什么,是什么呢?前面讲到,一个 bucket 中超过 10 个目录,会被合并一个新的目录,那么也就是说这个新目录中至少有 1000 个 document 的索引数据,这样下来,如果我有 100000 个节点,而且恰好每个目录中之后 1000 个 document 的数据,那么就得用 100 个目录来存储数据了。这样带来的问题是,每做一次查询,都需要把 100 个 indexReader 传给 search ,即使使用多线程并行搜索,那目录数也还是太多了,而且如果是 100w 个节点,那就更不得了了,所以 jackrabbit 中一定还有机制会把这些目录合并成更大目录的逻辑。为什么这么说,因为之前在创建 indexbucket 中的时候,分了 8 个允许合并的段,而上面的逻辑只会用到前面一个 bucket ,后面的几个肯定是有用处的,那么是谁来触发它们的,它们在哪里呢?

  我们看到在上面的 run 方法中,我们有一个方法没有讲到: multiIndex .replaceIndexes(names, index, deletedDocuments );

  我们将会在这个方法中寻找到真相,同样, ahuaxuan 在代码中加入了自己的注释

  /* obsoleteIndexes 是需要被删除的 dir ,因为他们的数据已经被合并到新的目录里, index 参数则表示那个对应那个新目录的 PersistentIndex , deleted 表示需要被删除的类 */

  Java代码   

void replaceIndexes(String[] obsoleteIndexes, 
            PersistentIndex index, 
            Collection deleted) 
 
      throws IOException { 
 
/*在multiIndex中,到处都是synchronized ,而且都是锁定multiindex对象,为啥呢? 详见后文*/ 
 
    synchronized (this) { 
 
/*这段代码在multiIndex#update方法中也出现过,你知道它的用途吗,其实可以猜出来*/ 
 
      synchronized (updateMonitor) { 
        updateInProgress = true; 
      } 
      try { 
 
        // if we are reindexing there is already an active transaction 
 
        if (!reindexing) { 
 
          executeAndLog(new Start(Action.INTERNAL_TRANS_REPL_INDEXES)); 
 
        } 
 
        // delete obsolete indexes 
 
/*10个目录已经合并成一个了,那这个10个目录该删的就删,不需要犹豫*/ 
 
        Set names = new HashSet(Arrays.asList(obsoleteIndexes)); 
 
        for (Iterator it = names.iterator(); it.hasNext();) { 
 
          // do not try to delete indexes that are already gone 
 
          String indexName = (String) it.next(); 
 
          if (indexNames.contains(indexName)) { 
            executeAndLog(new DeleteIndex(getTransactionId(), indexName)); 
          } 
 
        } 
 
         // Index merger does not log an action when it creates the target 
 
        // index of the merge. We have to do this here. 
 
/*还记得CreateIndex的作用吗?复习一下:根据名字获取PersistentIndex对象,如果名字不存在或者为null,则新建一个PersistentIndex对象,罗嗦一句,一个PersistentIndex代表一个目录*/ 
 
        executeAndLog(new CreateIndex(getTransactionId(), index.getName())); 
  
/*又来了AddIndex对象,还记得它的作用吗,将这个persistentIndex加入到*/ 
 
        executeAndLog(new AddIndex(getTransactionId(), index.getName())); 
 
        // delete documents in index 
 
        for (Iterator it = deleted.iterator(); it.hasNext();) { 
          Term id = (Term) it.next(); 
          index.removeDocument(id); 
        } 
 
        index.commit(); 
 
        if (!reindexing) { 
          // only commit if we are not reindexing 
          // when reindexing the final commit is done at the very end 
          executeAndLog(new Commit(getTransactionId())); 
        } 
 
      } finally { 
        synchronized (updateMonitor) { 
          updateInProgress = false; 
          updateMonitor.notifyAll(); 
          releaseMultiReader(); 
        } 
      } 
    } 
 
    if (reindexing) { 
      // do some cleanup right away when reindexing 
      attemptDelete(); 
    } 
  }

看完这段方法,我们发现,小的目录合并成大目录之后,这个大目录又被加到 indexbucket 等待下一次被合并,如此递归,一直当一个目录的 document 的 index 数据超过 10 亿,那么就不会再合并了, ahuaxuan 画了一张图:

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

  图中的0-100表示最基层的目录级别,这些目录只包含0-100个document的index数据,而默认参数情况下这些目录根本用不着,因为在前面的流程中,我们看到,ramdirectory中的数据只有满100才会加入到fsdirectory中,这意味着一开始用到的目录就是101-1000 级别的目录(101-1000的目录表示这些目录中的document的index数据也只有101-1000个这个范围。)。这种目录超过10个就会合并成一个新目录。依次类推高层目录。见图中ahuaxuan的注释

  说到这里,大部分人都知道了,很多参数可以控制合并的调优,这些参数在前文已经讲过了,不再赘述。

  到这里,IndexMerger的主体流程基本上完成了,其实就是一个生产-消费模型+小目录生产大目录,大目录生成更大目录的算法,这样做的好处是什么?当然是尽量少改动索引文件,应该说是便于分布式的查询架构。但是在后文中,我们会详细分析jackrabbit还没有为分布式查询准备好的原因,它的这块设计还有待改进,人无完人,框架亦是如此,不用过于苛求,也不必抱怨,用的不爽,那么就---改它,再不行---重新实现(某个模块或者全部)。

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

  本文分成两部分内容

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

  2  jackrabbit是如何解决这些问题的。

  而这个细节将会直接影响我们对query module的改造,这个细节虽然不难,但是却很重要,是jackrabbit中一个比较重要的设计。下面让我们一起来看看这个是什么样的细节。

  回顾上文,我们知道一个目录合并的主要逻辑是10个以上同层次(一共10个层次,还记得否)的小目录会合并成上一个层次的目录,我们再来看看这幅图:

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

  那么现在的问题是

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

  2.       并且要通知程序产生了一个新的目录。

  但是这个时候程序突然挂掉,怎么办呢。那么就必须有一个恢复机制。

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

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

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

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

  我们先来看看AddIndex类的execute方法:

  Java代码   

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方法:

  Java代码   

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方法:

  Java代码  

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到底是什么对象:

  Java代码    

/** 
   * 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这个类具有持久化的功能,它需要把自身包含的数据持久化到磁盘上。

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

  Java代码   

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加入这个类中:

  Java代码   

void addName(String name) { 
 
    if (names.contains(name)) { 
 
      throw new IllegalArgumentException("already contains: " + name); 
 
    } 
 
    indexes.add(name); 
 
    names.add(name); 
 
    dirty = true; 
 
  }

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

  Java代码   

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方法:

  Java代码   

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的构造方法里确实用到了:

  Java代码   

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的关系,很重要,是保证数据完整性的重要一步。

  总结

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

阅读(814) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~