在 solr 3.5 配置及應用(一) 講過一了 solr 3.5的詳細配置,本節我們講利用solr 的客戶端調用solr的應用了!
一、利用SolrJ操作solr API
使用SolrJ操作Solr會比利用httpClient來操作Solr要簡單。SolrJ是封裝了httpClient方法,來操作solr的API的。SolrJ底層還是通過使用httpClient中的方法來完成Solr的操作。
需要的包如下:
1、 apache-solr-solrj-3.5.0.jar
2、 commons-httpclient-3.1.jar
3、slf4j-api-1.6.0.jar
4、commons-logging-1.1.jar
在solr 3.5的解壓包 apache-solr-3.5.0\apache-solr-3.5.0\dist\apache-solr-solrj-3.5.0.jar 就有這個包。
二、Solr理論
1、 solr基礎
因為 Solr 包裝并擴展了 Lucene,所以它們使用很多相同的術語。更重要的是,Solr 創建的索引與 Lucene 搜索引擎庫完全兼容。通過對 Solr 進行適當的配置,某些情況下可能需要進行編碼,Solr 可以閱讀和使用構建到其他 Lucene 應用程序中的索引。
在 Solr 和 Lucene 中,使用一個或多個 Document 來構建索引。Document 包括一個或多個 Field。Field 包括名稱、內容以及告訴 Solr 如何處理內容的元數據。例如,Field 可以包含字符串、數字、布爾值或者日期,也可以包含你想添加的任何類型,只需用在solr的配置文件中進行相應的配置即可。Field 可以使用大量的選項來描述,這些選項告訴 Solr 在索引和搜索期間如何處理內容。現在,查看一下表 1 中列出的重要屬性的子集:
屬性名稱 |
描述 |
Indexed |
Indexed Field 可以進行搜索和排序。你還可以在 indexed Field 上運行 Solr 分析過程,此過程可修改內容以改進或更改結果。 |
Stored |
stored Field 內容保存在索引中。這對于檢索和醒目顯示內容很有用,但對于實際搜索則不是必需的。例如,很多應用程序存儲指向內容位置的指針而不是存儲實際的文件內容。 |
2、 solr索引操作
在 Solr 中,通過向部署在 servlet 容器中的 Solr Web 應用程序發送 HTTP 請求來啟動索引和搜索。Solr 接受請求,確定要使用的適當 SolrRequestHandler,然后處理請求。通過 HTTP 以同樣的方式返回響應。默認配置返回 Solr 的標準 XML 響應。你也可以配置 Solr 的備用響應格式,如json、csv格式的文本。
索引就是接受輸入元數據(數據格式在schema.xml中進行配置)并將它們傳遞給 Solr,從而在 HTTP Post XML 消息中進行索引的過程。你可以向 Solr 索引 servlet 傳遞四個不同的索引請求:
add/update 允許您向 Solr 添加文檔或更新文檔。直到提交后才能搜索到這些添加和更新。
commit 告訴 Solr,應該使上次提交以來所做的所有更改都可以搜索到。
optimize 重構 Lucene 的文件以改進搜索性能。索引完成后執行一下優化通常比較好。如果更新比較頻繁,則應該在使用率較低的時候安排優化。一個索引無需優化也可以正常地運行。優化是一個耗時較多的過程。
delete 可以通過 id 或查詢來指定。按 id 刪除將刪除具有指定 id 的文檔;按查詢刪除將刪除查詢返回的所有文檔。
Lucene中操作索引也有這幾個步驟,但是沒有更新。Lucene更新是先刪除,然后添加索引。因為更新索引在一定情況下,效率沒有先刪除后添加的效率好。
3、 搜索
添加文檔后,就可以搜索這些文檔了。Solr 接受 HTTP GET 和 HTTP POST 查詢消息。收到的查詢由相應的 SolrRequestHandler 進行處理。
solr查詢參數描述:
參數 |
描述 |
示例 |
q |
Solr 中用來搜索的查詢。有關該語法的完整描述,請參閱 參考資料。可以通過追加一個分號和已索引且未進行斷詞的字段(下面會進行解釋)的名稱來包含排序信息。默認的排序是 score desc,指按記分降序排序。 |
q=myField:Java AND otherField:developerWorks; date asc此查詢搜索指定的兩個字段,并根據一個日期字段對結果進行排序。 |
start |
將初始偏移量指定到結果集中。可用于對結果進行分頁。默認值為 0。 |
start=15 返回從第 15 個結果開始的結果。 |
rows |
返回文檔的最大數目。默認值為 10。 |
rows=25,返回25個結果集 |
fq |
提供一個可選的篩選器查詢。查詢結果被限制為僅搜索篩選器查詢返回的結果。篩選過的查詢由 Solr 進行緩存。它們對提高復雜查詢的速度非常有用。 |
任何可以用 q 參數傳遞的有效查詢,排序信息除外。 |
hl |
當 hl=true 時,在查詢響應中醒目顯示片段。默認為 false。參看醒目顯示參數(見 參考資料)。 |
hl=true |
fl |
作為逗號分隔的列表指定文檔結果中應返回的 Field 集。默認為 “*”,指所有的字段。“score” 指還應返回記分。 |
*,score |
sort |
排序,對查詢結果進行排序,參考 |
sort=date asc,price desc |
4、 solr模式
上面有提到schema.xml這個配置,這個配置可以在你下載solr包的安裝解壓目錄的apache-solr-3.4.0\example\solr\conf中找到,它就是solr模式關聯的文件。打開這個配置文件,你會發現有詳細的注釋。
模式組織主要分為三個重要配置
types 部分是一些常見的可重用定義,定義了 Solr(和 Lucene)如何處理 Field。也就是添加到索引中的xml文件屬性中的類型,如int、text、date等
fileds是你添加到索引文件中出現的屬性名稱,而聲明類型就需要用到上面的types
其他配置有
uniqueKey 唯一鍵,這里配置的是上面出現的fileds,一般是id、url等不重復的。在更新、刪除的時候可以用到。
defaultSearchField默認搜索屬性,如q=solr就是默認的搜索那個字段
solrQueryParser查詢轉換模式,是并且還是或者(and/or)
5、 索引配置
Solr 性能因素,來了解與各種更改相關的性能權衡。
表 1 概括了可控制 Solr 索引處理的各種因素:
因素 |
描述 |
useCompoundFile |
通過將很多 Lucene 內部文件整合到單一一個文件來減少使用中的文件的數量。這可有助于減少 Solr 使用的文件句柄數目,代價是降低了性能。除非是應用程序用完了文件句柄,否則 false 的默認值應該就已經足夠。 |
mergeFactor |
決定低水平的 Lucene 段被合并的頻率。較小的值(最小為 2)使用的內存較少但導致的索引時間也更慢。較大的值可使索引時間變快但會犧牲較多的內存。 |
maxBufferedDocs |
在合并內存中文檔和創建新段之前,定義所需索引的最小文檔數。段 是用來存儲索引信息的 Lucene 文件。較大的值可使索引時間變快但會犧牲較多的內存。 |
maxMergeDocs |
控制可由 Solr 合并的 Document 的最大數。較小的值 (< 10,000) 最適合于具有大量更新的應用程序。 |
maxFieldLength |
對于給定的 Document,控制可添加到 Field 的最大條目數,進而截斷該文檔。如果文檔可能會很大,就需要增加這個數值。然而,若將這個值設置得過高會導致內存不足錯誤。 |
unlockOnStartup |
unlockOnStartup 告知 Solr 忽略在多線程環境中用來保護索引的鎖定機制。在某些情況下,索引可能會由于不正確的關機或其他錯誤而一直處于鎖定,這就妨礙了添加和更新。將其設置為 true 可以禁用啟動鎖定,進而允許進行添加和更新。 |
6、 查詢處理配置
<maxBooleanClauses> 標記定義了可組合在一起形成一個查詢的子句數量的上限。對于大多數應用程序而言,默認的 1024 就應該已經足夠;然而,如果應用程序大量使用了通配符或范圍查詢,增加這個限值將能避免當值超出時,拋出 TooManyClausesException。
若應用程序預期只會檢索 Document 上少數幾個 Field,那么可以將 <enableLazyFieldLoading> 屬性設置為 true。懶散加載的一個常見場景大都發生在應用程序返回和顯示一系列搜索結果的時候,用戶常常會單擊其中的一個來查看存儲在此索引中的原始文檔。初始的顯示常常只需要顯示很短的一段信息。若考慮到檢索大型 Document 的代價,除非必需,否則就應該避免加載整個文檔。
<query> 部分負責定義與在 Solr 中發生的事件相關的幾個選項。Searcher 的 Java 類來處理 Query 實例。要改進這一設計和顯著提高性能,把這些新的 Searcher 聯機以便為現場用戶提供查詢服務之前,先對它們進行 “熱身”。<query> 部分中的 <listener> 選項定義 newSearcher 和 firstSearcher 事件,您可以使用這些事件來指定實例化新搜索程序或第一個搜索程序時應該執行哪些查詢。如果應用程序期望請求某些特定的查詢,那么在創建新搜索程序或第一個搜索程序時就應該反注釋這些部分并執行適當的查詢。
solrconfig.xml 文件的剩余部分,除 <admin> 之外,涵蓋了與 緩存、復制 和 擴展或定制 Solr 有關的項目。admin 部分讓您可以定制管理界面。有關配置 admin 節的更多信息,請參看solrconfig.xml 文件中的注釋。
7、 監視、記錄和統計數據
用于監視、記錄和統計數據的 Solr 管理選項
8、 智能緩存
智能緩存是讓 Solr 得以成為引人矚目的搜索服務器的一個關鍵性能特征。Solr 提供了四種不同的緩存類型,所有四種類型都可在 solrconfig.xml 的 <query> 部分中配置。solrconfig.xml 文件中所用的標記名列出了這些緩存類型:
緩存標記名 |
描述 |
能否自熱 |
filterCache |
通過存儲一個匹配給定查詢的文檔 id 的無序集,過濾器讓 Solr 能夠有效提高查詢的性能。緩存這些過濾器意味著對 Solr 的重復調用可以導致結果集的快速查找。更常見的場景是緩存一個過濾器,然后再發起后續的精煉查詢,這種查詢能使用過濾器來限制要搜索的文檔數。 |
可以 |
queryResultCache |
為查詢、排序條件和所請求文檔的數量緩存文檔 id 的有序 集合。 |
可以 |
documentCache |
緩存 Lucene Document,使用內部 Lucene 文檔 id(以便不與 Solr 惟一 id 相混淆)。由于 Lucene 的內部 Document id 可以因索引操作而更改,這種緩存不能自熱。 |
不可以 |
Named caches |
命名緩存是用戶定義的緩存,可被 Solr 定制插件 所使用。 |
可以,如果實現了 org.apache.solr.search.CacheRegenerator 的話。 |
每個緩存聲明都接受最多四個屬性:
class 是緩存實現的 Java 名。
size 是最大的條目數。
initialSize 是緩存的初始大小。
autoWarmCount 是取自舊緩存以預熱新緩存的條目數。如果條目很多,就意味著緩存的hit 會更多,只不過需要花更長的預熱時間。
三、實例
1、獲取SolrServer 用單例的形式寫了個類;
package com.stu.commons;
import java.net.MalformedURLException;
import org.apache.solr.client.solrj.impl.CommonsHttpSolrServer;
/**
* Description:
* @author LiChunming
* @version V1.0
* @createDateTime:2012-2-27 下午03:49:04
* @Company: MSD.
* @Copyright: Copyright (c) 2011
**/
public class SolrServer {
private static SolrServer solrServer = null;
private static CommonsHttpSolrServer server=null;
private static String url="http://localhost:8080/solr";
public static synchronized SolrServer getInstance() {
if (solrServer==null){
solrServer=new SolrServer();
}
return solrServer;
}
public static CommonsHttpSolrServer getServer(){
try {
if(server==null){
server = new CommonsHttpSolrServer(url);
server.setSoTimeout(1000); // socket read timeout
server.setConnectionTimeout(1000);
server.setDefaultMaxConnectionsPerHost(100);
server.setMaxTotalConnections(100);
server.setFollowRedirects(false); // defaults to false
//allowCompression defaults to false.
//Server side must support gzip or deflate for this to have any effect.
server.setAllowCompression(true);
server.setMaxRetries(1); // defaults to 0. > 1 not recommended.
}
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return server;
}
}
2、打開目錄tomcat-7.0.14\solr\conf\schema.xml文件在<fields>下增加下字段。用于增加到搜索引擎的字段
<field name="blogId" type="string" indexed="true" stored="true" required="true"/>
<field name="content" type="text" indexed="true" stored="true" omitNorms="true" />
<field name="bTypeId" type="string" indexed="true" stored="true" />
<field name="bTypeName" type="string" indexed="true" stored="true" />
<field name="nickName" type="string" indexed="true" stored="true" />
<field name="createTime" type="date" indexed="true" stored="true" omitNorms="true" />
3、增加信息到引擎文件中

public void writerBlog(BlogsDO blog)
{
// TODO Auto-generated method stub

try
{
blog.setId(SerialNumberUtil.getRandomNum(4));
//獲取連接服務
CommonsHttpSolrServer solrServer= SolrServer.getInstance().getServer();
SolrInputDocument doc1 = new SolrInputDocument();
doc1.addField("id", SerialNumberUtil.getRandomNum(4) );
doc1.addField("blogId", blog.getBlogsId());
doc1.addField("title",blog.getTitle() );
doc1.addField("bTypeId", blog.getbTypeId());
doc1.addField("bTypeName", blog.getbTypeName());
doc1.addField("content", blog.getContent());
String createTime=DateUtils.formatDate(blog.getGmtCreate(), "yyyyMMddHHmmss");
doc1.addField("createTime",createTime);
doc1.addField("nickName",blog.getNickName());
solrServer.add(doc1);
solrServer.commit();

} catch (SolrServerException e)
{
// TODO Auto-generated catch block
e.printStackTrace();

} catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
} 實體類 BlogsDO
package com.stu.entity;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import org.apache.solr.client.solrj.beans.Field;
import com.stu.commons.util.DateUtils;
/**
* Description:
* @author LiChunming
* @version V1.0
* @createDateTime:2011-5-17 下午04:38:11
* @Company: MSD.
* @Copyright: Copyright (c) 2011
**/
@Entity
@Table(name="blogs")
public class BlogsDO implements Serializable{
/**
*
*/
private static final long serialVersionUID = -4721368786493126226L;
@Field
private String id;
@Field("blogId")
private Integer blogsId;
@Field
private String title;
@Field
private String content="";
@Field("createTime")
private Date gmtCreate;
@Field
private String nickName;
@Field
private String bTypeId;
@Field
private String bTypeName;
private Date gmtModified;
private String revDate;
private String sDate="";
private String eDate="";
@Transient
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer getBlogsId() {
return blogsId;
}
public void setBlogsId(Integer blogsId) {
this.blogsId = blogsId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public String getbTypeId() {
return bTypeId;
}
public void setbTypeId(String bTypeId) {
this.bTypeId = bTypeId;
}
@Column(name="gmt_create")
public Date getGmtCreate() {
return gmtCreate;
}
public void setGmtCreate(Date gmtCreate) {
this.gmtCreate = gmtCreate;
}
@Column(name="gmt_modified")
public Date getGmtModified() {
return gmtModified;
}
public void setGmtModified(Date gmtModified) {
this.gmtModified = gmtModified;
}
@Transient
public String getRevDate() {
if (this.gmtCreate == null) {
return null;
}
return DateUtils.formatDate(gmtCreate, "yyyy-MM-dd HH:mm:ss");
}
public void setRevDate(String revDate) {
this.revDate = revDate;
}
@Transient
public String getbTypeName() {
return bTypeName;
}
public void setbTypeName(String bTypeName) {
this.bTypeName = bTypeName;
}
@Transient
public String getsDate() {
return sDate;
}
public void setsDate(String sDate) {
this.sDate = sDate;
}
@Transient
public String geteDate() {
return eDate;
}
public void seteDate(String eDate) {
this.eDate = eDate;
}
@Override
public String toString() {
return this.id + "#" + this.blogsId + "#" + this.title + "#" + this.content + "#" + this.bTypeId + "#" + this.bTypeName + "#" + this.nickName+"#" + this.gmtCreate;
}
}
5、文檔查詢(注意查詢出來的文檔轉化為List<object>比較麻煩,下次我們將使用
DocumentObjectBinder對象將SolrInputDocument 和 BlogsDO對象相互轉換)public List<BlogsDO> searchBlogsList(String content, String bTypeId,
String sDate, String eDate, Page page) throws IOException,
ParseException {
List<BlogsDO> blogList=new ArrayList<BlogsDO>();
BlogsDO blogsDO=null;
CommonsHttpSolrServer solrServer= SolrServer.getInstance().getServer();
SolrQuery sQuery = new SolrQuery();
String para="";
//OR 或者 OR 一定要大寫
if(StringUtils.isNotEmpty(content)){
para=para+"(title:"+content+" OR content:"+content+")";
//空格 等同于 OR
// para=para+"(title:"+content+" content:"+content+")";
}
//AND 并且 AND一定要大寫
if(!bTypeId.equals("-1")){
if(StringUtils.isNotEmpty(para)){
para=para+" AND bTypeId:"+bTypeId;
}else{
para=para+" bTypeId:"+bTypeId;
}
}
if(StringUtils.isNotEmpty(sDate) && StringUtils.isNotEmpty(eDate)){
if(StringUtils.isNotEmpty(para)){
para=para+" AND createTime:["+sDate+" TO "+eDate+"]";
}else{
para=para+" createTime:["+sDate+" TO "+eDate+"]";
}
}
//查詢name包含solr apple
//sQuery.setQuery("name:solr,apple");
//manu不包含inc
//sQuery.setQuery("name:solr,apple NOT manu:inc");
//50 <= price <= 200
//sQuery.setQuery("price:[50 TO 200]");
//sQuery.setQuery("popularity:[5 TO 6]");
//params.setQuery("price:[50 TO 200] - popularity:[5 TO 6]");
//params.setQuery("price:[50 TO 200] + popularity:[5 TO 6]");
//50 <= price <= 200 AND 5 <= popularity <= 6
//sQuery.setQuery("price:[50 TO 200] AND popularity:[5 TO 6]");
//sQuery.setQuery("price:[50 TO 200] OR popularity:[5 TO 6]");
// 查詢關鍵詞,*:*代表所有屬性、所有值,即所有index
if(!StringUtils.isNotEmpty(para)){
para="*:*";
}
logger.info("para:"+para);
sQuery.setQuery(para);
//設置分頁 start=0就是從0開始,,rows=5當前返回5條記錄,第二頁就是變化start這個值為5就可以了。
sQuery.setStart((page.getCurrentPage()-1)*page.getPerPageSize());
sQuery.setRows(page.getPerPageSize());
//排序 如果按照blogId 排序,,那么將blogId desc(or asc) 改成 id desc(or asc)
sQuery.addSortField("blogId", ORDER.asc);
//設置高亮
sQuery.setHighlight(true); // 開啟高亮組件
sQuery.addHighlightField("content");// 高亮字段
sQuery.addHighlightField("title");// 高亮字段
sQuery.setHighlightSimplePre("<font color='red'>");//標記,高亮關鍵字前綴
sQuery.setHighlightSimplePost("</font>");//后綴
sQuery.setHighlightSnippets(2);//結果分片數,默認為1
sQuery.setHighlightFragsize(1000);//每個分片的最大長度,默認為100
//分片信息
sQuery.setFacet(true)
.setFacetMinCount(1)
.setFacetLimit(5)//段
.addFacetField("content");//分片字段
try {
QueryResponse response = solrServer.query(sQuery);
SolrDocumentList list = response.getResults();
Integer counts=(int) list.getNumFound();
logger.info("counts:"+counts);
page.setCounts(counts);
//獲取所有高亮的字段
Map<String,Map<String,List<String>>> highlightMap=response.getHighlighting();
String blogId="";
for (SolrDocument solrDocument : list) {
blogsDO=new BlogsDO();
blogId=solrDocument.getFieldValue("blogId").toString();
blogsDO.setBlogsId(Integer.valueOf(blogId));
blogsDO.setbTypeId(solrDocument.getFieldValue("bTypeId").toString());
blogsDO.setbTypeName(solrDocument.getFieldValue("bTypeName").toString());
blogsDO.setNickName(solrDocument.getFieldValue("nickName").toString());
List<String> titleList=highlightMap.get(blogId).get("title");
List<String> contentList=highlightMap.get(blogId).get("content");
if(titleList!=null && titleList.size()>0){
blogsDO.setTitle(titleList.get(0));
}else{
//獲取并設置高亮的字段title
blogsDO.setTitle(solrDocument.getFieldValue("title").toString());
}
if(contentList!=null && contentList.size()>0){
blogsDO.setContent(contentList.get(0));
}else{
//獲取并設置高亮的字段content
blogsDO.setContent(solrDocument.getFieldValue("content").toString());
}
blogsDO.setRevDate(solrDocument.getFieldValue("createTime").toString());
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
try {
blogsDO.setGmtCreate(sdf.parse(solrDocument.getFieldValue("createTime").toString()));
} catch (java.text.ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
blogList.add(blogsDO);
}
} catch (SolrServerException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return blogList;
}