Solr高亮
原理
做搜索時,高亮是很常見的需求,那么Solr肯定也為高亮提供了支持。先解釋下Solr高亮的原理,在我們設置了需要高亮顯示的Field之后,查詢得到的返回結果會多出來下面的內容:
"highlighting": { "519": { "Artist_Name": [ "<em>周傑倫</em>" ] } }
其實就是多了highlighting的字段,並沒有改變原來返回的字段內容。
Json串是使用 Unique Field :{高亮顯示的內容}的形式。
SolrJ有三種高亮類型:
如果要對某field做高亮顯示,必須對該field設置stored=true
Standard Highlighter,根據查詢的docIdSet,獲取Documents,並獲取當前document的需要高亮的field的value,根據query的term和該field的value做匹配算法
FastVector Highlighter,效率比普通的高亮顯示要高;需要定義termvector(占用空間和IO),包括position和offset,根據query term的termvector到field value中做快速的定位標記,進而實現快速的高亮顯示
Postings Highlighter,由於高亮顯示需要對field設置為store=true,所有對於單節點數據量比較大並且該字段比較大的話,會消耗大量的IO操作,那么可以把該字段存儲在另外的地方,比如Hbase,在外部做高亮顯示的匹配。
其中推薦使用的是Standard Highlighter,下面也是針對Standard Highlighter,
配置
下面介紹兩種配置方式:
1.SolrJ配置:
//設置高亮 songQuery.setHighlight(true); // 開啟高亮組件或用query.setParam("hl", "true"); songQuery.addHighlightField("Song_Name,Song_SingerName");// 高亮字段 songQuery.set("hl.highlightMultiTerm","true");//啟用多字段高亮 songQuery.setHighlightSimplePre("<font style=\"color:#A7D043;font-weight:bold;\">"); //標記,高亮關鍵字前綴 songQuery.setHighlightSimplePost("</font>");//后綴
2.solrConfig.xml配置:
<requestHandler name="search" class="solr.SearchHandler" default="true"> <!-- default values for query parameters can be specified, these will be overridden by parameters in the request --> <lst name="defaults"> <str name="echoParams">explicit</str> <int name="rows">10</int> <str name="df">text</str> <str name="hl">true</str> <str name="hl.fl">content</str> <str name="f.name.hl.fragsize">50</str> <str name="hl.simple.pre"><font color="red></str> <str name="hl.simple.post"></font></str> </lst> </requestHandler>
詳細的RequestHandler配置請參看博客:五、SolrJ、Request Handler
其實這兩種配置並沒有本質上的區別。我個人習慣使用SolrJ配置。
解析
獲取highlighting是非常簡單的,一條語句搞定:
Map<String,Map<String,List<String>>> tempMap = response.getHighlighting();
相信大家也注意到了,雖然接受結果簡單,但是如果想遍歷就比較復雜了,因為接受到的結果是嵌套了很多層的類型Map<String,Map<String,List<String>>>
那么我這邊把我解析的方法分享下:
Map<String,Map<String,List<String>>> tempMap = songHighlight.getHighlighting(); for(Map.Entry<String, Map<String,List<String>>> entry : tempMap.entrySet()) { if(Integer.parseInt(entry.getKey()) == song.getSong_SongID()) { for(Map.Entry<String, List<String>> entryLayer2 : entry.getValue().entrySet()) { if(null != entryLayer2.getKey() && "Song_Name".equals(entryLayer2.getKey())) { //your Operation } if(null != entryLayer2.getKey() && "Song_SingerName".equals(entryLayer2.getKey())) { //your Operation } } } }
這個方法比Iterator和foreach效率稍高。
我設置了兩個字段需要高亮,所以在循環中判斷了高亮是屬於哪個字段,之后進行相應的操作。
因為我做的時候,一首歌可能有幾個Song_SingerName,在數據庫中用"/"分隔,所以這種情況更加復雜,我首先是把后綴中的/換成了出現概率很小的@
songQuery.setHighlightSimplePost("<@font>");//后綴
然后再用split("@")分隔出不同的Song_SingerName,但是這樣就會有一個問題,就是我不知道高亮的歌手到底是哪一個歌手,所以這個時候,我還需要從分割后的String[]中提取所有的中文字符,比對后,存入另一個變量,最后再用"/"替換掉"@"。
Solr權重
概念
Solr底層依然用的是Lucene的權重算法,也就是通過一個公式計算每個Documents的得分,然后按得分高低排序,公式如下:
簡單解釋下這個公式中包含的一些因子:
Tf:Term frequency,就是條目出現的次數。
Idf: Inverse document frequency,就是用來描述在一個搜索關鍵字中,不同字詞的稀有程度。比如搜索The Cat in the Hat,那么很明顯The和in遠沒有Cat和Hat重要。
Boosting:這個使我們設置權重的重點,比如搜索歌手名,那么在一個document中還有歌手的ID、歌曲的清晰度、歌曲上傳時間,而boosting是不同的Filed有不同權重,之后根據公式計算得分。所以可以看到,我們並不能直接影響solr搜索結果的排序,需要改變權重,進而改變不同Document的得分,從而影響排序。
其中還有很多因子和公式的解釋,有興趣的同學可以參考Solr in action這本書,里面有比較詳細的解釋。
因為我們只需簡單的根據某一Filed的權重影響結果的排序,所以我們需要改變Document的Boosting,那么就需要用到Dismax,Dismax是一個查詢解析器(Query parser),查詢解析器的概念就是提供了一系列查詢的參數,一旦我們在查詢url中設置了相應的參數,那么查詢解析器將會解析查詢信息,從而得到搜索結果,其實完全也可以把查詢解析器理解為一個Api,就是提供了相應的方法,我們設置,之后Solr根據我們設置的參數進行查詢,只不過不同的Query Parser提供了不同的參數而已。
一共提供了三種Query Parser
Standard:最常用的,並且是默認
Dismax:
Extended Dismax
功能從上至下是逐漸遞增的,在大部分情況下,Standard已經可以完全滿足需求,但是因為要使用權重排序,那么需要用到Dismax,具體提供的參數請查看wiki:
https://cwiki.apache.org/confluence/display/solr/The+Standard+Query+Parser
那么首先需要設置Query Parser為Dismax:
songQuery.set("defType","dismax");
之后設置需要查詢的Field:
songQuery.set("qf","Song_Name^2 Song_SingerName^0.2");
比如我這里就需要根據用戶輸入的關鍵字查詢歌手名和歌曲名,之后返回這兩個Field命中的結果、多個Query Filed中可以設置不同的權重,比如Song_Name的權重就為2,必須注意,在Solr權重的設置中,所有權重標准為1,意思是當權重設置大於1時,代表這個字段的權重變大,如果權重設置小於1並且大於0的時候,代表這個字段權重變小。
之后設置其它Field的權重:
songQuery.set("bf", "sum(div(Song_Quality,0.01),if(exists(Song_FileMV),20000,0),recip(ms(NOW,Song_CreateTimeForNew),1,10000,1))");
這里面用到了很多Function Query,比如div,代表相除、exists代表如果Song_FileMV如果不為空那么設置它的權重為20000,為空則為0。記住最后要sum起來,因為從上面的公式可以看出來,boosting是一個變量,所以最好要有一個和值。相關的函數請參考wiki:
http://wiki.apache.org/solr/FunctionQuery
這樣搜索結果就會按照有MV的優先顯示、最近上傳的優先顯示、清晰度高的優先顯示。