閱讀266 返回首頁    go 阿裏雲 go 技術社區[雲棲]


solr進階: 如何定製搜索服務,擴展搜索邏輯

前兩天發了篇"剖析solr實用性",主要黑了solr建索引讓我覺得不舒服,還黑了solr隻是個通用的丟入了servlet容器裏的簡單服務,事實證明我錯了。這篇博客我要糾正並回答那篇博客裏自己總結的問題:如何定製solr實現自己的搜索服務。

solr是一個可擴展的服務,我們可以添加自己的包和類,在solr已經實現的default處理邏輯之上,再添加我們自己的搜索邏輯。實現手段就是繼承solr的基礎類,重寫或者改寫新的FilterSearch Component以及Request Handler類,來取代solr默認的處理類或者與之並存。我也是讀了源碼,參考了默認類的實現過程,才找到了定製的方法,下麵一一說明。


Request Handler

solrconfig.xml裏,對/select設置的默認處理類是solr.SearchHandler

  <requestHandler name="/select" >
     <lst name="defaults">
       <str name="echoParams">explicit</str>
       <int name="rows">10</int>
       <str name="df">usergoods_mix</str>
     </lst>
  </requestHandler>
源碼中,SearchHandler類在org.apache.solr.handler.component包下,繼承了RequestHandlerBase類,他最主要的邏輯在handleRequestBody函數中,

  @Override
  public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception, ParseException, InstantiationException, IllegalAccessException
  {}
SearchHandler還有一個主要的成員變量

protected List<SearchComponent> components = null;
SearchHandler主要職責是借助solrconfig.xml配置文件裏設置的默認或者新增的requestHandler參數以及search component類,構建一個ResponseBuilder

ResponseBuilder rb = new ResponseBuilder(req, rsp, components);

根據配置把各個search component該做的事都分配好,search component是真正讀取處理SolrQueryRequest內的查詢參數,往SolrQueryResponse裏填寫數據的地方。下麵會再介紹search component的兩個prepare()和process()函數。


定製Request Handler

默認的request handler直接得到你http裏url帶來的傳參,然後就開始根據配置分發任務,讓各自的component去處理查詢請求了。定製request handler的好處是,在這個步驟之中,你可以再額外進行一些別的邏輯處理,比如你可以寫寫log,在得到SolrQueryRequest後,傳入前查看處理一下裏麵的query,對SolrQueryResponse裏得到的數據集再做些處理等等。定製方式是自己繼承StandardRequestHandler類(該類繼承了SearchHandler),

package myplugin.handler;

import org.apache.solr.handler.StandardRequestHandler;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;

public class MySearchHandler extends StandardRequestHandler {
	public void handleRequestBody(SolrQueryRequest request,
			SolrQueryResponse response) throws Exception {
		super.handleRequestBody(request, response);
		//TODO: whatever you want
	}
}
並在solrconfig.xml裏配置即可起效。
  <requestHandler name="/test" >
    <lst name="defaults">
      <str name="echoParams">explicit</str>
      <int name="rows">10</int>
      <str name="df">usergoods_mix</str>
    </lst>
  </requestHandler>
別忘了把自己的jar包放到webapp/的WEB-INF的lib目錄下。solr也為我們定製了別的handler,比如DisMaxRequestHandler,LukeRequestHandler,MoreLikeThisHandler

和SpellCheckerRequestHandler等等。大家可以自己看看源碼,知道他們分別做什麼。都在org.apache.solr.handler內。


Search Component

search component的意義遠比handler重要的多。solr已經在solrconfig.xml給我們定製了幾個默認的component

       <searchComponent name="query"      />
       <searchComponent name="facet"      />
       <searchComponent name="mlt"        />
       <searchComponent name="highlight"  />
       <searchComponent name="stats"      />
       <searchComponent name="debug"      />
一般處理我們查詢請求的一定避免不了第一個query component。閱讀源碼發現,所有這些類都繼承SearchComponent。所以我們定製的時候也要繼承SearchComponent。

拿QueryComponent舉例子說明search component的重要性,最重要的兩個函數是

public class QueryComponent extends SearchComponent
{
  public static final String COMPONENT_NAME = "query";
  public void prepare(ResponseBuilder rb) throws IOException{}
  public void process(ResponseBuilder rb) throws IOException {}
  // ...
}
下麵截取一段prepare裏的代碼說明QueryComponent是怎麼讀取SolrQueryRequest(res)裏的內容,並最後把結果寫進SolrQueryResponse(rsp)的。

public void process(ResponseBuilder rb) throws IOException
  {
    SolrQueryRequest req = rb.req;
    SolrQueryResponse rsp = rb.rsp;
    SolrParams params = req.getParams();
    if (!params.getBool(COMPONENT_NAME, true)) {
      return;
    }
    SolrIndexSearcher searcher = req.getSearcher();
    // ...

      DocListAndSet res = new DocListAndSet();
      res.docList = new DocSlice(0, docs, luceneIds, null, docs, 0);
      if (rb.isNeedDocSet()) {
        // TODO: create a cache for this!
        List<Query> queries = new ArrayList<Query>();
        queries.add(rb.getQuery());
        List<Query> filters = rb.getFilters();
        if (filters != null) queries.addAll(filters);
        res.docSet = searcher.getDocSet(queries);
      }
      rb.setResults(res);
      rsp.add("response",rb.getResults().docList);

對同一個request handler,可以按順序配置多個search component,這些component會在handler類裏各自得到自己的任務,把SolrQueryRequest和SolrQueryResponse傳承下去,在這個過程中,我們可以加入自己的component,定製我們想要的搜索結果和搜索邏輯


定製search component

我簡單把實現代碼帖一下,主要還是通過繼承基礎類,最後配置到solrconfig.xml內。

package myplugin.component;

import java.io.IOException;

import org.apache.solr.handler.component.ResponseBuilder;
import org.apache.solr.handler.component.SearchComponent;


public class MySearchComponent extends SearchComponent {
	String query = null;
	@Override
	public void prepare(ResponseBuilder rb) throws IOException {
		query = rb.req.getParams().get("q", ""); 
		System.out.println("prepare: " + query);
	}

	@Override
	public void process(ResponseBuilder rb) throws IOException {
		if (query != null) {		
			rb.rsp.add("mytest", "zbf"); // <str name="mytest">zbf</str>
			//SimpleOrderedMap map = (SimpleOrderedMap) builder.rsp.getValues();
			//DocList doclist = (DocList) map.get("response");			
//			System.out.println("process: " + map.get("response").toString());
//			System.out.println("process: " + map.get("mytest").toString());
		}
	}

	@Override
	public String getDescription() {
		return "MySearchComponent";
	}

	@Override
	public String getSource() {
		return "";
	}

	@Override
	public String getSourceId() {
		return "";
	}

	@Override
	public String getVersion() {
		return "0.1";
	}
}
主要就是在prepare()裏獲取到SolrQueryRequest裏的查詢query,在process()裏自己處理,並且獲取到前一次component處理得到的SolrQueryResponse,裏麵可能會有已經排好序的查詢數據集,你也可以做一些二次處理,簡單過濾,重新排序等事情

  <requestHandler name="/test" >
    <lst name="defaults">
      <str name="echoParams">explicit</str>
      <int name="rows">10</int>
      <str name="df">usergoods_mix</str>
    </lst>
    <arr name="components">
      <str>query</str>
      <str>myComponent</str>
    </arr>
  </requestHandler>
  <searchComponent name="myComponent" >
  </searchComponent>

先聲明自己的searchComponent,然後放入handler裏使用起來,注意配置順序,因為component是按順序串接起來的。


定製Filter

最後簡單說下Filter,他的作用就是如果你自己定製了Filter,你就可以按自己的方式處理字符串。比如你的查詢query裏傳來的是“field:如何 定製 搜索服務”,如果你直接交給solr的api去做那麼"如何"使用的是field字段,但是空格之後的word都會歸結到default field裏,這是solr包裝了lucene的接口之後的結果,可能剛開始在組裝自己的查詢url的時候會不太適應,所以如果你定製一個自己的filter,就可以解決這樣的問題。

定製Filter要繼承兩個類。其實在配置IKAnalyzer的時候大家可以看到

    <fieldType name="text_ik" >
        <analyzer type="index">
          <tokenizer   isMaxWordLength="true"/>
          <filter  ignoreCase="true" words="stopwords.txt" enablePositionIncrements="true" />
          <filter />
        </analyzer>
        <analyzer type="query">
           <!-- 同上 -->
        </analyzer>  
    </fieldType>
在tokenizer處使用一個類,在filter處使用一個Factory類。下麵我舉solr 3.1 cookbook上的例子說明一下。先是自己的filter類,

package pl.solr;
import java.io.IOException;
import org.apache.lucene.analysis.TokenFilter;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.TermAttribute;
public class ExampleFilter extends TokenFilter {
    private final TermAttribute termAttr = (TermAttribute) 
addAttribute(TermAttribute.class);     
    public ExampleFilter(TokenStream stream) {
        super(stream);
    }     
    @Override
    public boolean incrementToken() throws IOException {
        if (input.incrementToken()) {
            String term = termAttr.term();
            if (term.length() <= 1) {
                return true;
            }
            StringBuffer buffer = new StringBuffer();
             buffer.append(term.charAt(1)).append(term.charAt(0)).
append(term.substring(2));             
            termAttr.setTermBuffer(buffer.toString());
            termAttr.setTermLength(buffer.length());
            return true;
        }
        return false;
    }
}
然後是工廠類,

package pl.solr;
import org.apache.lucene.analysis.TokenStream;
import org.apache.solr.analysis.BaseTokenFilterFactory;
public class ExampleFilterFactory extends BaseTokenFilterFactory {
    @Override
    public TokenStream create(TokenStream stream) {
        return new ExampleFilter(stream);
    }
}
最後是配置文件:
<fieldtype name="exampleType" stored="true" indexed="true" 
 >
 <analyzer>
  <tokenizer  />
  <filter  />
 </analyzer>
</fieldtype>


總結

前幾天我也不知道solr的這幾個玩法,把solr給小看了。其實solr是一個高可擴展的東西,你要實現自己的搜索服務,就繼承solr的基本類,增加自己的擴展類到solr的配置文件裏,可以取代solr的默認處理類,也可以和solr的類共同處理。所以以上說的request handler,search component以及filter給我們實現了很好的擴展方式,用起來比較像SOA的架構,像OSGi裏的各部分組件。

之後如果對solr有更深的理解或者解讀,會再做分享。


(全文完)

最後更新:2017-04-04 07:03:34

  上一篇:go Valve創始人再噴Win 8:它傷害了PC行業的每一個人
  下一篇:go HDU3579 一元線性同餘方程