在 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; }