Solr的查询过程

Solr的查询过程
2009-03-11 16:28
从这一节开始,我们将要分析一下Solr1.3中的查询过程是如何进行的。我们还是从那个古老的位置(Solrcore::execute)开始。
    在SolrCore中有两个execute方法:
    1. execute(SolrQueryRequest req, SolrQueryResponse rsp)
    2.execute(SolrRequestHandler handler, SolrQueryRequest req, SolrQueryResponse rsp)
    对于第一个方法,没有像第二个方法那样的handler参数,但是其实其内部通过这样一个方法来获得handler 的:SolrRequestHandler handler = getRequestHandler(req.getQueryType())。也就是说我们通过在req中指定qt参数的值就可以获得我们想要的处理 器,当然这些处理器需要在solrconfig.xml的
requestHandler元素中定义(配置文件中有大量requestHandler)。这样服务器在建立时才能建立相应的处理器实例。
    这里我发现在获取处理器时,参数为"",null还是standard都能得到StandardRequestHandler的实例,为什么这三个都可以呢?真是奇怪哦。通过获得时的跟踪与调试始终无法获得任何信息,所以我决定查看这些处理器建立的过程。
    请求处理器建立过程
    SolrCore中有一个名为reqHandlers的属性,其类型为RequestHandlers,它是Solr1.3中搜索的请求处理器的载体,它 具有一个方法get(String)可以通过名字来获得相应的请求处理器。所以我们下面关系的就是reqHandlers的建立过程。
    在SolrCore的构造函数里面有以下代码:
     reqHandlers = new RequestHandlers(this);
     reqHandlers.initHandlersFromConfig( solrConfig );
    第一行就是将SolrCore对象作为参数传递给RequestHandlers,使得它具有SolrCore信息。关键是第二行代码,我们进入该代码去看个究竟吧。
    1.创建一个RequestHandlers 对象handlers指向当前,它是final的,这就保证了其内部SolrCore属性引用的不可变性。
    2.AbstractPluginLoader<SolrRequestHandler> loader =
      new AbstractPluginLoader<SolrRequestHandler>(.....
     这里是一个匿名内部类的用法,loader在这里的类型是抽象插件加载器。它内部有三个方法被重载create,register,init。这个匿名类是用来加载Solr请求处理器的。暂且别管它。
    3.在最后部分的代码中我们连续看到了register(RequestHandlers的方法)被调用了三次,这三次的参数正好对应 了"",null,standard。这个注册方法处理获得请求处理器外,还将请求处理器的名字与对象的映射信息添加到了响应的SolrCore对象中。
    4.SolrRequestHandler defaultHandler = loader.load( config.getResourceLoader(), nodes );正是这句代码完成了所有在配置文件中显示指定的请求处理器的建立与记载。完成这里过成以后检查默认的请求处理器是否获得(与standard对应), 如果没有那么会以standard为名建立一个处理器。不管如何到最后都会获得默认处理器,我们名为""和名为null的请求处理器也与之管理起来。
     这样所有需要和不需要的处理器都建立好了。他们的信息保存在每核对应的信息注册器(infoRegistry : Map<String, SolrInfoMBean>)中,同时也都存在于请求处理器的载体reqHandlers : RequestHandlers中。

   SolrCore::execute的流程
    1.进行handler合法性的检查,不能为null,否则抛出错误。
    2.final NamedList<Object> responseHeader = new SimpleOrderedMap<Object>();
       rsp.add("responseHeader", responseHeader);
       建立一个单序映射表并将其作为响应头加入到rsp中。
    3.NamedList toLog = rsp.getToLog();
      获得rsp的ToLog对象,并从请求中获得一些相关信息加入,这些信息有webapp,path,params。
    4.handler.handleRequest(req,rsp);
    5.StringBuilder sb = new StringBuilder();....
       从rsp的ToLog中获得信息并将其加入到sb中,然后在日志中输出。
我们已经分析了SolrCore中的execute方法的流程,而且可以看到具体执行查询过程的语句是:handler.handleRequest(req,rsp)。请注意查询时候,这里的变量的类型是:
    handler:StandardRequestHandler
    req:SolrRequestParsers类中的一个匿名类。
    rsp:SolrQueryResponse
   
    我们现在采用跟踪的方法来看handleRequest里面所做的事情。请看StandardRequestHandler的类谱系图。
    
    由于以上继承关系,我们并不能再StandardRequestHandler.java中找到这个方法,而是在他“爷爷”类那里找到了这个方法。
    这个方法做了如下的工作(忽略掉一些记录信息以及出错处理的代码)
    1.SolrPluginUtils.setDefaults(req,defaults,appends,invariants);
      将一些默认的参数插入到req中。
    2.rsp.setHttpCaching(httpCaching);
      这里的httpCaching是一个boolean类型,设置这个相应是否需要缓存功能。
    3. handleRequestBody( req, rsp )。这里实际上调用SearcherHandler类的方法。

    下面转到SearcherHandler:: handleRequestBody( SolrQueryRequest req, SolrQueryResponse rsp)看个究竟。
A 设置rb中的req,rsp,components,debug等属性
   ResponseBuilder rb = new ResponseBuilder();
   rb.req = req;
   rb.rsp = rsp;
   rb.components = components;
   rb.setDebug(req.getParams().getBool(CommonParams.DEBUG_QUERY, false));
   这一部分代码就是创建一个新的响应“构建器”,然后设置其属性。这里的components属性比较陌生。它是什么呢?通过查看输出的日志,可以看到对应有这样几行代码是与之相关的。
   2008-12-29 16:45:14 org.apache.solr.handler.component.SearchHandler inform
   信息: Adding component:org.apache.solr.handler.component.QueryComponent@2585e
   2008-12-29 16:45:14 org.apache.solr.handler.component.SearchHandler inform
   信息: Adding component:org.apache.solr.handler.component.FacetComponent@1f796d0
   2008-12-29 16:45:14 org.apache.solr.handler.component.SearchHandler inform
   信息: Adding component:org.apache.solr.handler.component.MoreLikeThisComponent@1ce64f6
   2008-12-29 16:45:14 org.apache.solr.handler.component.SearchHandler inform
   信息: Adding component:org.apache.solr.handler.component.HighlightComponent@1465ca2
   2008-12-29 16:45:14 org.apache.solr.handler.component.SearchHandler inform
   信息: Adding debug component:org.apache.solr.handler.component.DebugComponent@6bba64   
   这些代码就是创建组件时输出的日志,那么这些组件是什么什么时候,如果创建的呢?通过代码跟踪发现这些日志输出来自 SearchHandler::getDefaultConponent()。而最终又是由SolrCore的初始函数中调用inform(this)来 间接达到的。
   
   在SolrResourceLoader的inform方法中,有一个for循环:
   for( SolrCoreAware aware : waitingForCore ) {
      aware.inform( core );
    }
   这个循环里面的SolrCoreAware 是一些组件和查询处理器的父类。其类谱系图如下:

    第一次循环处理的是 StandardRequestHandler的实例,因此是调用StandardRequestHandler的直接父类的inform方法,这个方法 里面有这样一句代码:list = getDefaultComponents()。这个调用使得SearchHandler的components属性具有了几个查询组件。很容易就看出来 了,这是要获得默认的组件。我们当前并没有配置组件,所以显然是默认组件了。这里的默认组件正是上面日志中的那几个: QueryComponent、FacetComponent、MoreLikeThisComponent、HighlightComponent、 DebugComponent。他们都是SearcheComponent的子类。类谱系图如下:
   
   B 设置时间工具
   final RTimer timer = rb.isDebug() ? new RTimer() : null;
   这句代码是根据rb的debug属性来设置time是否获得一个实例。可见RTimer是与Debug相关的。
   RTime是什么东东?原来这一个计时器,当创建实例时,自动开始计时,所以构造函数中有一个获得系统当前时间的方法调用。
   C在循环中对每个组件进行prepare方法调用。
不过这里要分timer为空或不为空的情况。

当timer为空的时候,循环对每一个搜索组件执行prepare(rb)。
   for( SearchComponent c : components ) {
        c.prepare(rb);
      }
   我们来看看SearchComponent的prepare方法。方法说明是这样的:准备response,确保在调用任何组件的process方法之前调用该方法,为每一个到来的请求调用该方法,这里实际上是作依赖于请求的初始化工作。
   我们随便看一个组件类的prepare方法。我们来看看QueryComponent类的prepare方法吧。改方法原型为public void prepare(ResponseBuilder rb)。这里的参数就是包含req,rsp,components属性的rb。
   这个方法做了哪些事情呢?

       a   设置rb中的返回域标志。

       b   设置rb中的查询字符串。

       c   QParser parser = QParser.getParser(rb.getQueryString(), defType, req),这里的rb中的查询字符串来自查询参数,defType也来自查询参数,通过这三个参数获得一个“查询分析器”(QParser)。有了这个查 询分析器,就可以通过其获得与查询参数对应的Query对象,Sort对象( parser.getSort(true)获得的是SortSpec对象,它是对Sort进行了包装的一个类),并将其传递给rb作为属性。

      rb.setQuery( parser.getQuery() );
      rb.setSortSpec( parser.getSort(true) );
      rb.setQparser(parser);

      d   String[] fqs = req.getParams().getParams(CommonParams.FQ);这里是从查询参数中获得fq参数(字符串数组),fq参数的作 用是什么呢?Lucene query string(s) for filtering the results without affecting scoring,原来是fq是在影响评分情况下过滤结果的查询字符串。

      e   下面所作的事情就是根据这些查询过滤字符串来构建查询对象,并将其加入到rb的过滤中。

    QParser fqp = QParser.getParser(fq, null, req);
     filters.add(fqp.getQuery());(这里的filters已经和rb中的过滤进行了关联)

   f   后面的处理只作分布式的一些处理。

当timer不为空的时候,就需要为每个组件设置一个时间。所以它只是在上面叙述过程之外加了一点时间处理的功能而已。

   D主要是在一个循环中执行每个组件的process方法。

      我们先来看看非分布式的情况(即rb.shard==null)。

      这里又分为需要debug和不需要debug的情形,如果不需要debug,那么直接用一个循环来执行每个组件的process方法即可。如果需要 debug,那么除了执行process方法外,还需要将时间管理器与每个组件相关联。再执行完所有的组件的方法后,将时间信息(或叫debug信息,反 正他们是同一回事情)添加到rb中去。

说明了了SearchHandler类handleRequestBody(SolrQueryRequest, SolrQueryResponse)方法的实质。那就是先执行每个组件( QueryComponent、FacetComponent、MoreLikeThisComponent、HighlightComponent、 DebugComponent等)的prepare方法,然后再执行每个组件的的process方法。这些组件方法的操作都是围绕着 ResponseBuilder的实例来进行的,因为这个实例包含了全部的请求以及响应的信息。虽然还没有分析process方法,我们有理由相信,我们 的查询过程就是在这个方法中进行的。
    这一节,我们将来看看组件的process方法。先给出组件类的谱系图以及SearchComponent接口的概况。
    
    我们这里先来分析QueryComponent的process方法分析。
   一、获取req,rsp,searcher,params,timeAllowed,ids,并判断ids是否为空来决定是否进行分布式处理
   SolrQueryRequest req = rb.req;
    SolrQueryResponse rsp = rb.rsp;
    SolrIndexSearcher searcher = req.getSearcher();
    SolrParams params = req.getParams();
    long timeAllowed = (long)params.getInt( CommonParams.TIME_ALLOWED, -1 );
    String ids = params.get(ShardParams.IDS);
    二、对于非分布式情形
    SolrIndexSearcher.QueryCommand cmd = rb.getQueryCommand();
    cmd.setTimeAllowed(timeAllowed);
    SolrIndexSearcher.QueryResult result = new SolrIndexSearcher.QueryResult();
    searcher.search(result,cmd);
    rb.setResult( result );

    rsp.add("response",rb.getResults().docList);
    rsp.getToLog().add("hits", rb.getResults().docList.matches())
    这段代码的功能。从rb中获得cmd,设置cmd超时时间,实例化一个result,调用searcher.search(result,cmd)进行查 询处理,然后将result加入rb,这样rb就具备了结果信息。所以这里最重要的是searcher.search方法。最后设置rsp的返回结果部分 以及日志部分。
    三、根据fsv的参数值决定是否进行一些(...)操作。
    四、判断条件,决定是否进行文档预获取工作。

你可能感兴趣的:(apache,配置管理,Solr,Lucene,qt)