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

全部博文(69)

文章存档

2010年(69)

我的朋友

分类:

2010-09-11 15:23:35

任何一个数据库都离不开一个技术----索引技术,jackrabbit作为内容仓库的开源实现亦不能例外,从前面的文章中我们已经清楚的知道 jackrabbit使用lucene来进行索引任务和查询任务。而查询正是基于索引,所以在本文中,ahuaxuan将和大家一起来学习 jackrabbit中建立索引的方法。

  事实上,jackrabbit中建立索引的流程是比较冗长和复杂的,如同query一样,在本文中ahuaxuan将把注意里放在整体流程上,而流程的每一步可以说都是比较复杂的,这些复杂的实现细节将会放在后面的文章中阐述,本文的主要目的是了解jackrabbit中创建流程的一般过程。

  首先,我们需要寻找一个入口点,一个接口,这个接口是建立索引的入口,任何人需要建立索引的功能必须要调用这个接口,那么这个接口在哪里呢,通过对源代码的类结构分析,ahuaxuan得出来以下的类图,从图中我们可以看出,建立索引的api其实是一个监听器(监听器模式来源于观察者模式,两者并没有什么本质上的不同),任何人需要建立索引只要发事件给对应的监听器,那么监听器就会执行建立索引的流程(同时请注意下图中标注出来创建索引的两个入口,一个是 Update类,还有一个是ClustorNode):

深入浅出 jackrabbit 二 索引概览

  上图即描述了建立索引前的主要逻辑,ObervationDispatcher类,当ClustorNode中发现其他节点有 save,update,delete或者本node中有update操作,那么ObervationDispatcher将会把这种事件分发给对这类事件感兴趣的类,那么显然,对这类event最感兴趣的便是图中的SearchManager,也就是说SearchManager是索引的入口。而且从图中所示的类结构图可知,SearchManager就是一个EventListener。

既然知道了index的入口,不妨让我们来看一下SearchManager中索引的入口方法onEvent()

  Java代码   

public void onEvent(EventIterator events) { 
   // nodes that need to be removed from the index. 
    final Set removedNodes = new HashSet(); 
    // nodes that need to be added to the index. 
    final Map addedNodes = new HashMap(); 
    // property events 
    List propEvents = new ArrayList(); 
 
    while (events.hasNext()) { 
      EventImpl e = (EventImpl) events.nextEvent(); 
      if (!isExcluded(e)) { 
        long type = e.getType(); 
        if (type == Event.NODE_ADDED) { 
          addedNodes.put(e.getChildId(), e); 
          // quick'n dirty fix for JCR-905 
          if (e.isExternal()) { 
            removedNodes.add(e.getChildId()); 
          } 
        } else if (type == Event.NODE_REMOVED) { 
          removedNodes.add(e.getChildId()); 
        } else { 
          propEvents.add(e); 
        } 
      } 
    } 
 
    // sort out property events 
    for (int i = 0; i < propEvents.size(); i++) { 
      EventImpl e = (EventImpl) propEvents.get(i); 
      NodeId nodeId = e.getParentId(); 
      if (e.getType() == Event.PROPERTY_ADDED) { 
        if (addedNodes.put(nodeId, e) == null) { 
          // only property added 
          // need to re-index 
          removedNodes.add(nodeId); 
        } else { 
          // the node where this prop belongs to is also new 
        } 
      } else if (e.getType() == Event.PROPERTY_CHANGED) { 
        // need to re-index 
        addedNodes.put(nodeId, e); 
        removedNodes.add(nodeId); 
      } else { 
        // property removed event is only generated when node still exists 
        addedNodes.put(nodeId, e); 
        removedNodes.add(nodeId); 
      } 
    } 
 
    NodeStateIterator addedStates = new NodeStateIterator() { 
//创建需要add的node iterator 
      ……………. 
    }; 
    NodeIdIterator removedIds = new NodeIdIterator() { 
  …………………… 
    }; 
 
    if (removedNodes.size() > 0 || addedNodes.size() > 0) { 
      try { 
        handler.updateNodes(removedIds, addedStates); 
      } catch (RepositoryException e) { 
        log.error("Error indexing node.", e); 
      } catch (IOException e) { 
        log.error("Error indexing node.", e); 
      } 
    } 
 
    if (log.isDebugEnabled()) { 
      log.debug("onEvent: indexing finished in " 
          + String.valueOf(System.currentTimeMillis() - time) 
          + " ms."); 
    } 
  }

这段代码很简单,就是把传进来的event对象转变成两个iterator,一个是需要remove的node iterator,另外一个是需要add的node iterator。由于lucene不支持索引的更新,所以对于lucene来说,save,update,delete再本质上就是delete和 save。Update被拆成了先delete后save两个环节,也就是说如果update一个节点,那么这个节点的id既会出现在 removedIds里,也会出现在addedStates里。

  Onevent方法的最终走向是handler.updateNodes(),所以我们也需要进入到updateNodes方法中。

  跟随着代码,我们到达了updateNodes方法:

  Java代码  

public void updateNodes(NodeIdIterator remove, NodeStateIterator add) 
      throws RepositoryException, IOException { 
    checkOpen(); 
    final Map aggregateRoots = new HashMap(); 
    final Set removedNodeIds = new HashSet(); 
    final Set addedNodeIds = new HashSet(); 
    index.update(new AbstractIteratorDecorator(remove) { 
      public Object next() { 
        NodeId nodeId = (NodeId) super.next(); 
        removedNodeIds.add(nodeId); 
        return nodeId.getUUID(); 
      } 
    }, new AbstractIteratorDecorator(add) { 
      public Object next() { 
        NodeState state = (NodeState) super.next(); 
        if (state == null) { 
          return null; 
        } 
        addedNodeIds.add(state.getNodeId()); 
        removedNodeIds.remove(state.getNodeId()); 
        Document doc = null; 
        try { 
   //非常重要的一个方法,但是它会在那里执行呢? 
          doc = createDocument(state, getNamespaceMappings(), 
              index.getIndexFormatVersion()); 
          retrieveAggregateRoot(state, aggregateRoots); 
        } catch (RepositoryException e) { 
          log.warn("Exception while creating document for node: " 
              + state.getNodeId() + ": " + e.toString()); 
        } 
        return doc; 
      } 
    }); 
}

这段代码很简单,把原来的两个iterator作了一个decorator,也就是说在原有的iterator的功能上又添加了一些功能,代码虽然很简单,但是却很重要,尤其是在处理那些需要add的node上,在第二个decorator中,有一个next方法,这个方法包含着创建index的主要逻辑:创建属于这个node的document对象。为node创建document是索引过程中第一个最重要的环节,这意味着,我们需要掌握一个 node中,哪些属性被建立索引,建了那些索引,这关乎到查询的范围,如果一个property不能被索引,那么在查询的时候就不能以它作为依据。

  所以请大家记住createDocument这个方法,在后文中,我们会详细分析一个node有哪些field会被索引进文件。

  所以到目前为止,我们已经得知了index流程的前面三个步骤

  Java代码   

1. 调用index流程的两个入口 
2. 监听器解析event,并生成两个iterator 
3. Decorate两个iterator,目的是override其next方法,并将在next方法中创建document。

  接着,我们进入到index.update()方法,index对象是MultiIndex类的实例,对于update方法来说,无论用什么言语来描述它的重要性都不为过,因为在这个方法中,我们会看到jackrabbit建立索引的核心流程,但是我们需要知道的是,它并不包含任何细节,细节隐藏在流程的其他对象中:

  //请注意方法前的synchronized,这个同步非常重要,因为他能控制一个非//常重要的流程的顺序执行。

  Java代码   

synchronized void update(Iterator remove, Iterator add) throws IOException { 
    synchronized (updateMonitor) { 
      updateInProgress = true; 
    } 
    try { 
      long transactionId = nextTransactionId++; 
      executeAndLog(new Start(transactionId)); 
 
      boolean flush = false; 
  //从索引中删除需要删除的node 
      while (remove.hasNext()) { 
        executeAndLog(new DeleteNode(transactionId, (UUID) remove.next())); 
      } 
//将需要增加的node的document加入到索引中去,这里的next 
//方法就是上一个方法中定义的decorator中的next()方法,创建//document就是在此时 
      while (add.hasNext()) { 
        Document doc = (Document) add.next(); 
        if (doc != null) { 
          executeAndLog(new AddNode(transactionId, doc)); 
          // commit volatile index if needed 
          flush |= checkVolatileCommit(); 
        } 
      } 
      executeAndLog(new Commit(transactionId)); 
 
      // flush whole index when volatile index has been commited. 
      if (flush) { 
        flush(); 
      } 
    } finally { 
      synchronized (updateMonitor) { 
        updateInProgress = false; 
        updateMonitor.notifyAll(); 
        if (multiReader != null) { 
          multiReader.close(); 
          multiReader = null; 
        } 
      } 
    } 
  } 

  从代码的注释,我们可以看到这段代码的主要功能,就是把该删的删掉,该加的加进去,然后该flush就flush,最后方法退出。那么代码的主要逻辑在哪里呢?

  在DeleteNode和AddNode类中,还有就是在flush方法中。

  看上去很简单了,但是这只是看上去简单而已,就到目前为止,这个以上流程的主要工作还停留在如何准备索引数据:即document,接着就是短短几行代码来描述如何使用document,

  不过再继续下去之前,我们得回头看看createDocument中究竟有哪些field被创建,究竟node中有哪些信息被放到索引中了呢,同时我们将会看到如何通过修改field的属性来降低高亮功能所带来的时间消耗,to be continue。

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