看solr源代碼的筆記,主要是代碼簡單解析

轉載自:http://blog.csdn.net/duck_genuine/article/details/6962624#t13


配置

solr 對一個搜索請求的的流程

在solrconfig.xml會配置一個handler。配置了前置處理組件preParams,還有後置處理組件filterResult,當然還有默認的組件


[html] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. <requestHandler name="standard" class="solr.SearchHandler" default="true">  
  2.   
  3.      <arr name="first-components">  
  4.         <str>preParams</str>  
  5.      </arr>  
  6.         <lst name="defaults">  
  7.           <str name="echoParams">explicit</str>  
  8.           <int name="rows">10</int>  
  9.           <int name="start">0</int>  
  10.          <str name="q">*:*</str>  
  11.         </lst>      
  12.   
  13.      <arr name="last-components">  
  14.         <str>filterResult</str>  
  15.      </arr>       
  16.   
  17.    </requestHandler>  



http請求控制器

當一個查詢請求過來的時候,先到類SolrDispatchFilter,由這個分發器尋找對應的handler來處理。
 

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. String qt = solrReq.getParams().get( CommonParams.QT );  
  2. handler = core.getRequestHandler( qt );  


---------------------------------------------------------------------------------------------------

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. this.execute( req, handler, solrReq, solrRsp );  
  2. HttpCacheHeaderUtil.checkHttpCachingVeto(solrRsp, resp, reqMethod);  


-----------------------------------------------------------------------------------------------

從上面的代碼裏看出是由solrCore留下的接口來處理請求。從代碼框架上,從此刻開始進入solr的核心代碼。


[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. protected void execute( HttpServletRequest req, SolrRequestHandler handler, SolrQueryRequest sreq, SolrQueryResponse rsp) {  
  2.   sreq.getContext().put( "webapp", req.getContextPath() );  
  3.   sreq.getCore().execute( handler, sreq, rsp );  
  4. }  



看一下solrCore代碼execute的方法 的主要代碼

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public void execute(SolrRequestHandler handler, SolrQueryRequest req, SolrQueryResponse rsp) {  
  2. 。。。。。  
  3.     handler.handleRequest(req,rsp);  
  4.     setResponseHeaderValues(handler,req,rsp);  
  5.  。。。。。。。  
  6.   }  


主要實現對請求的處理,並將請求結果的狀態信息寫到響應的頭部


SolrRequestHandler 處理器


再看一下對請求的處理。。先看定義該請求處理器的接口,可以更好理解。只有兩個方法,一個是初始化信息,主要是配置時的默認參數,另一個就是處理請求的接口。

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public interface SolrRequestHandler extends SolrInfoMBean {  
  2.   public void init(NamedList args);  
  3.   public void handleRequest(SolrQueryRequest req, SolrQueryResponse rsp);  
  4. }  


先看一下實現該接口的類RequestHandlerBase


[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public void handleRequest(SolrQueryRequest req, SolrQueryResponse rsp) {  
  2.     numRequests++;  
  3.     try {  
  4.       SolrPluginUtils.setDefaults(req,defaults,appends,invariants);  
  5.       rsp.setHttpCaching(httpCaching);  
  6.       handleRequestBody( req, rsp );  
  7.       // count timeouts  
  8.       NamedList header = rsp.getResponseHeader();  
  9.       if(header != null) {  
  10.         Object partialResults = header.get("partialResults");  
  11.         boolean timedOut = partialResults == null ? false : (Boolean)partialResults;  
  12.         if( timedOut ) {  
  13.           numTimeouts++;  
  14.           rsp.setHttpCaching(false);  
  15.         }  
  16.       }  
  17.     } catch (Exception e) {  
  18.       SolrException.log(SolrCore.log,e);  
  19.       if (e instanceof ParseException) {  
  20.         e = new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);  
  21.       }  
  22.       rsp.setException(e);  
  23.       numErrors++;  
  24.     }  
  25.     totalTime += rsp.getEndTime() - req.getStartTime();  
  26.   }  


主要記錄該請求處理的狀態與處理時間記錄。真正的實現方法交由各個子類      handleRequestBody( req, rsp );

現在看一下SearchHandler對於搜索處理的實現方法


首先是將solrconfig.xml上配置的各個處理組件按一定順序組裝起來,先是first-Component,默認的component,last-component.這些處理組件會按照它們的順序來執行,以下是searchHandler的實現主體。方法handleRequestBody


 

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. @Override  
  2. public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception, ParseException, InstantiationException, IllegalAccessException  
  3. {  
  4.   // int sleep = req.getParams().getInt("sleep",0);  
  5.   // if (sleep > 0) {log.error("SLEEPING for " + sleep);  Thread.sleep(sleep);}  
  6.   ResponseBuilder rb = new ResponseBuilder();  
  7.   rb.req = req;  
  8.   rb.rsp = rsp;  
  9.   rb.components = components;  
  10.   rb.setDebug(req.getParams().getBool(CommonParams.DEBUG_QUERY, false));  
  11.   
  12.   final RTimer timer = rb.isDebug() ? new RTimer() : null;  
  13.   
  14.   if (timer == null) {  
  15.     // non-debugging prepare phase  
  16.     for( SearchComponent c : components ) {  
  17.       c.prepare(rb);  
  18.     }  
  19.   } else {  
  20.     // debugging prepare phase  
  21.     RTimer subt = timer.sub( "prepare" );  
  22.     for( SearchComponent c : components ) {  
  23.       rb.setTimer( subt.sub( c.getName() ) );  
  24.       c.prepare(rb);  
  25.       rb.getTimer().stop();  
  26.     }  
  27.     subt.stop()<span style="color:#FF0000;">;</span>  
  28.   }  
  29.    //單機版  
  30.   if (rb.shards == null) {  
  31.     // a normal non-distributed request  
  32.   
  33.     // The semantics of debugging vs not debugging are different enough that  
  34.     // it makes sense to have two control loops  
  35.     if(!rb.isDebug()) {  
  36.       // Process  
  37.       for( SearchComponent c : components ) {  
  38.         c.process(rb);  
  39.       }  
  40.     }  
  41.     else {  
  42.       // Process  
  43.       RTimer subt = timer.sub( "process" );  
  44.       for( SearchComponent c : components ) {  
  45.         rb.setTimer( subt.sub( c.getName() ) );  
  46.         c.process(rb);  
  47.         rb.getTimer().stop();  
  48.       }  
  49.       subt.stop();  
  50.       timer.stop();  
  51.   
  52.       // add the timing info  
  53.       if( rb.getDebugInfo() == null ) {  
  54.         rb.setDebugInfo( new SimpleOrderedMap<Object>() );  
  55.       }  
  56.       rb.getDebugInfo().add( "timing", timer.asNamedList() );  
  57.     }  
  58.   
  59.   } else {//分佈式請求  
  60.     // a distributed request  
  61.   
  62.     HttpCommComponent comm = new HttpCommComponent();  
  63.   
  64.     if (rb.outgoing == null) {  
  65.       rb.outgoing = new LinkedList<ShardRequest>();  
  66.     }  
  67.     rb.finished = new ArrayList<ShardRequest>();  
  68.   
  69.     //起始狀態爲0,結束狀態爲整數的最大值  
  70.     int nextStage = 0;  
  71.     do {  
  72.       rb.stage = nextStage;  
  73.       nextStage = ResponseBuilder.STAGE_DONE;  
  74.   
  75.       // call all components  
  76.       for( SearchComponent c : components ) {  
  77.         //得到所有組件運行後返回的下一個狀態,並取最小值  
  78.         nextStage = Math.min(nextStage, c.distributedProcess(rb));  
  79.       }  
  80.   
  81.   
  82.       // 如果有需要向子機發送請求  
  83.       while (rb.outgoing.size() > 0) {  
  84.   
  85.         // submit all current request tasks at once  
  86.         while (rb.outgoing.size() > 0) {  
  87.           ShardRequest sreq = rb.outgoing.remove(0);  
  88.           sreq.actualShards = sreq.shards;  
  89.           if (sreq.actualShards==ShardRequest.ALL_SHARDS) {  
  90.             sreq.actualShards = rb.shards;  
  91.           }  
  92.           sreq.responses = new ArrayList<ShardResponse>();  
  93.   
  94.           // 向各個子機發送請求  
  95.           for (String shard : sreq.actualShards) {  
  96.             ModifiableSolrParams params = new ModifiableSolrParams(sreq.params);  
  97.             params.remove(ShardParams.SHARDS);      // not a top-level request  
  98.             params.remove("indent");  
  99.             params.remove(CommonParams.HEADER_ECHO_PARAMS);  
  100.             params.set(ShardParams.IS_SHARD, true);  // a sub (shard) request  
  101.             String shardHandler = req.getParams().get(ShardParams.SHARDS_QT);  
  102.             if (shardHandler == null) {  
  103.               params.remove(CommonParams.QT);  
  104.             } else {  
  105.               params.set(CommonParams.QT, shardHandler);  
  106.             }  
  107.           //提交子請求  
  108.            comm.submit(sreq, shard, params);  
  109.           }  
  110.         }  
  111.   
  112.   
  113.         // now wait for replies, but if anyone puts more requests on  
  114.         // the outgoing queue, send them out immediately (by exiting  
  115.         // this loop)  
  116.         while (rb.outgoing.size() == 0) {  
  117.           ShardResponse srsp = comm.takeCompletedOrError();  
  118.           if (srsp == nullbreak;  // no more requests to wait for  
  119.   
  120.           // Was there an exception?  If so, abort everything and  
  121.           // rethrow  
  122.           if (srsp.getException() != null) {  
  123.             comm.cancelAll();  
  124.             if (srsp.getException() instanceof SolrException) {  
  125.               throw (SolrException)srsp.getException();  
  126.             } else {  
  127.               throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, srsp.getException());  
  128.             }  
  129.           }  
  130.   
  131.           rb.finished.add(srsp.getShardRequest());  
  132.   
  133.           //每個組件都對於返回的數據處理  
  134.           for(SearchComponent c : components) {  
  135.             c.handleResponses(rb, srsp.getShardRequest());  
  136.           }  
  137.         }  
  138.       }//請求隊列結束  
  139.   
  140.       //再對該輪請求進行收尾工作  
  141.       for(SearchComponent c : components) {  
  142.           c.finishStage(rb);  
  143.        }  
  144.   
  145.       //如果狀態未到結束,則繼續循環  
  146.     } while (nextStage != Integer.MAX_VALUE);  
  147.   }  
  148. }  

首先運行的是各個組件的方法prepare

 

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. for( SearchComponent c : components ) {  
  2.   c.prepare(rb);  
  3. }  


再則如果不是分佈式搜索,則比較簡單的運行

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. for( SearchComponent c : components ) {  
  2.         c.process(rb);  
  3.       }  


就結束!

如果是分佈式搜索,過程會比較複雜些,對於每個組件處理都會返回一個狀態,對於以下幾個方法循環執行,直到狀態結束 。  

在類ResponseBuilder定義了幾個狀態。

  

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public static int STAGE_START           = 0;  
  2. public static int STAGE_PARSE_QUERY     = 1000;  
  3. public static int STAGE_EXECUTE_QUERY   = 2000;  
  4. public static int STAGE_GET_FIELDS      = 3000;  
  5. public static int STAGE_DONE            = Integer.MAX_VALUE;  

從STAGE_START---->STAGE_PARSE_QUERY------>STAGE_EXECUTE_QUERY--------------->STAGE_GET_FIELDS------------>STAGE_DONE

從這些狀態名稱可以猜得出整個對應的過程。

每個組件先調用方法distributeProcess,並返回下一個狀態

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. for( SearchComponent c : components ) {  
  2.      // the next stage is the minimum of what all components report  
  3.      nextStage = Math.min(nextStage, c.distributedProcess(rb));  
  4.    }  

而方法handleResponse主要處理返回來的數據

     

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. for(SearchComponent c : components) {  
  2.         c.handleResponses(rb, srsp.getShardRequest());  
  3.       }  

然後交由finishStage方法來對每一個狀態的過程作結束動作。

------------------------------

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. for(SearchComponent c : components) {  
  2.           c.finishStage(rb);  
  3.        }  


-----------------------------

瞭解這個流程有助於擴展solr。比如有個業務是要我對搜索的自然結果排序進行干預,而這個干預只針對前幾頁結果,所以我不得不做個組件來對其中結果進行處理。

所以我想可以添加一個組件放在最後-------------》

1)如果是分佈式搜索:

       這個組件可以在重寫finsihStage做處理。算是對最終結果的排序處理即可。

2)如果只是單機:

      這個組件可以在重寫process做處理



組件

現在看一下其中一個主要的組件QueryComponent

prepare

對於QueryComponent主要解析用戶傳送的語法解析參數defType,以及過濾查詢fq,返回字段集fl.排序字段Sort


單機處理

process


分佈式搜索過程中的某一步,這裏應該是主機要合併文檔,取出對應的文檔的過程,

主機發出指定的solr主鍵ids來取文檔集,首先取出對應的lucene的內部id集。如果某些文檔已不在則棄掉。


[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. String ids = params.get(ShardParams.IDS);  
  2.     if (ids != null) {//將傳過來的ids,放進結果集中,並在後面取出對應的結果文檔  
  3.      SchemaField idField = req.getSchema().getUniqueKeyField();  
  4.       List<String> idArr = StrUtils.splitSmart(ids, ","true);  
  5.       int[] luceneIds = new int[idArr.size()];  
  6.       int docs = 0;  
  7.       for (int i=0; i<idArr.size(); i++) {  
  8.       //solr主鍵id對應的文檔lucene內部的id  
  9.        int id = req.getSearcher().getFirstMatch(  
  10.                 new Term(idField.getName(), idField.getType().toInternal(idArr.get(i))));  
  11.         if (id >= 0)  
  12.           luceneIds[docs++] = id;  
  13.       }  
  14.        
  15.       DocListAndSet res = new DocListAndSet();  
  16.   
  17.       //這裏並沒有傳入scores[]  
  18.   
  19.   res.docList = new DocSlice(0, docs, luceneIds, null, docs, 0);  
  20. //需要另一種doc集合處理。  
  21.  if (rb.isNeedDocSet()) {  
  22.  List<Query> queries = new ArrayList<Query>();  
  23.   queries.add(rb.getQuery());  
  24. List<Query> filters = rb.getFilters();   
  25. if (filters != null)  
  26.  queries.addAll(filters);  
  27.   res.docSet = searcher.getDocSet(queries);  
  28.  }   
  29. rb.setResults(res);  
  30.  rsp.add("response",rb.getResults().docList);  
  31.  return;   
  32. }  



[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. //封裝搜索值對象與封裝結果值對象   
  2.   SolrIndexSearcher.QueryCommand cmd = rb.getQueryCommand();  
  3.   //設置超時最大值  
  4.    cmd.setTimeAllowed(timeAllowed);  
  5.    SolrIndexSearcher.QueryResult result = new SolrIndexSearcher.QueryResult();  
  6.    //搜索  
  7.    searcher.search(result,cmd);  
  8.    //設置搜索結果  
  9.    rb.setResult( result );  
  10.    rsp.add("response",rb.getResults().docList);  
  11.    rsp.getToLog().add("hits", rb.getResults().docList.matches());  
  12.    //對含有字段排序處理  
  13.    doFieldSortValues(rb, searcher);  
  14.   //非分佈查詢過程,且搜索結果數小於50,進行緩存  
  15.    doPrefetch(rb);  



目前看到真實獲取文檔內容的是在
QueryResponseWriter
例如xml的輸出格式類XMLWriter




分佈式處理

1)distributedProcess

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1.   @Override    
  2.   public int distributedProcess(ResponseBuilder rb) throws IOException {  
  3.     if (rb.stage < ResponseBuilder.STAGE_PARSE_QUERY)  
  4.       return ResponseBuilder.STAGE_PARSE_QUERY;  
  5.     if (rb.stage == ResponseBuilder.STAGE_PARSE_QUERY) {  
  6.       createDistributedIdf(rb);  
  7.       return ResponseBuilder.STAGE_EXECUTE_QUERY;  
  8.     }  
  9.     if (rb.stage < ResponseBuilder.STAGE_EXECUTE_QUERY) return ResponseBuilder.STAGE_EXECUTE_QUERY;  
  10.     if (rb.stage == ResponseBuilder.STAGE_EXECUTE_QUERY) {  
  11. //分佈式查詢  
  12.      createMainQuery(rb);  
  13.       return ResponseBuilder.STAGE_GET_FIELDS;  
  14.     }  
  15.     if (rb.stage < ResponseBuilder.STAGE_GET_FIELDS) return ResponseBuilder.STAGE_GET_FIELDS;  
  16.     if (rb.stage == ResponseBuilder.STAGE_GET_FIELDS) {  
  17.    
  18.     //這裏就會去對應的主機拿取需要的字段,封裝請求字段的參數,放進請求隊列裏,可以由外部的searchHandler提交該請求,最後結果放在ShardResponse類裏。  
  19.      createRetrieveDocs(rb);  
  20.       return ResponseBuilder.STAGE_DONE;  
  21.     }  
  22.     return ResponseBuilder.STAGE_DONE;  
  23.   }  


   


 2) handleResponses

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. public void handleResponses(ResponseBuilder rb, ShardRequest sreq) {    
  2.   
  3.         if ((sreq.purpose & ShardRequest.PURPOSE_GET_TOP_IDS) != 0) {  
  4.   
  5.                      //合併ids   
  6.   
  7.               mergeIds(rb, sreq);  
  8.   
  9.              //合併groupCount     
  10.   
  11.            mergeGroupCounts(rb, sreq);   
  12.   
  13.           }      
  14.   
  15.       if ((sreq.purpose & ShardRequest.PURPOSE_GET_FIELDS) != 0) {  
  16.   
  17.               //獲取文檔的字段,並將結題組裝起來放到最終結果列表對應的位置裏      
  18.   
  19.             returnFields(rb, sreq);      
  20.   
  21.            return;    
  22.   
  23.       }  
  24.   
  25.  }  


   3)  finishStage


 

[java] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. @Override  
  2.  public void finishStage(ResponseBuilder rb) {  
  3.   //這裏說是==獲取文檔內容的值,在  
  4.   if (rb.stage == ResponseBuilder.STAGE_GET_FIELDS) {  
  5.       //有些文檔可能已不存在了,則忽略掉  
  6.      for (Iterator<SolrDocument> iter = rb._responseDocs.iterator(); iter.hasNext();) {  
  7.        if (iter.next() == null) {  
  8.          iter.remove();  
  9.          rb._responseDocs.setNumFound(rb._responseDocs.getNumFound()-1);  
  10.        }          
  11.      }  
  12.   
  13.      rb.rsp.add("response", rb._responseDocs);  
  14.    }  
  15.  }  


同樣最後的結果是保存在

ResponseBuilder 

     ResponseBuilder 
         NamedList values = new SimpleOrderedMap();

這個字段裏,以鍵爲"response",單機存儲的是lucene 的內部id列表
如果是分佈式,則存儲的是SolrDocumentList,不用再去索引拿出對應的存儲字段,
這個在QueryResponseWriter裏有對應的處理



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章