Lucene/Solr开发经验

来源:互联网 发布:印尼酒店网络怎么样 编辑:程序博客网 时间:2024/05/14 15:04

1、开篇语

2、概述

3、渊源

4、初识Solr

5、Solr的安装

6、Solr分词顺序

7、Solr中文应用的一个实例

8、Solr的检索运算符

[[size=x-large]开篇语]按照惯例应该写一篇技术文章了,这次结合Lucene/Solr来分享一下开发经验。

Lucene是一个使用Java语言写的全文检索开发包(API),利用它可以实现强大的检索功能,它的详细介绍大家可以去Google上搜索一下,本文重点放在Solr相关的讨论上。

[概述]目前国内研究Solr的人不多,而且大多是因为项目开发需要。Solr师承Lucene,为Apache基金会下的一个项目,具体的说它还是Lucene下的一个子项目。Solr出身豪门,而且具有自己的技术特点,填补了以往Lucene仅仅作为开发工具包的遗憾,它是一个完完整整地应用。换句话说,它是一个全文检索服务器,它开箱即用,让我们能立马体会到Lucene的强大功能,为Lucene产品化走出了一大步。

Solr分词原理演示界面

[渊源]最初,CNET Networks使用Lucene API来开发了一些应用,并在这个基础上产生了Solr的雏形,后来Apache Software Foundation在Lucene顶级项目的支持下得到了Solr,这已经是2006年1月份的事了。2006年1月17日,Solr正是加入Apache基金会的孵化项目,在整个项目孵化期间,Solr 稳步地积累各种特性并吸引了一个稳定的user群体、developer群体和Committer群体,并于1年之后的17日正式酝酿成熟,在这之前已经成功发布了1.1.0版。目前的稳定版本是1.2,Solr在9月份的2007Apache年会上大放异彩,在今年11月底将来到香港参加2007亚洲开源软件峰会,遗憾的是为什么不来北京:-(

[初识Solr]Solr服务器不同于普通的关系型数据库,不仅仅在于它核心本质的不同(面向结构化和非结构化数据的不同),很大的不同还在于它的体系架构上。Solr服务器一般情况下需要部署于应用服务器/Java容器上(如果是本机通信不涉及RPC可以不使用Java容器,如采用嵌入方式使用Solr),无法独立工作于JVM上。

Solr架构图

Solr服务器可以存储数据并通过索引对其进行快速高效检索。对外提供HTTP/XML和Json API接口,这使得它能够在多语言环境下集成,比如针对它的客户端的开发。Solr目前的客户端面向的有Java、PHP、Python、C#、Json和Ruby等,遗憾的是没有面向C/C++(这也是本人目前在研究的),研究音乐搜索分类的Brian Whitman曾在苹果平台上使用JNI技术在C代码中嵌入Solr实现检索,不过是一个Cocoa工程。有了这些客户端,使用者能很方便地将Solr集成到具体运用中。目前最完善的当属Java客户端Solrj,以及加入到Solr trunk,并将在1.3版本中正式发布。

如果不研究开发Solr,只是使用Solr,只需要关注Solr的以下几个方面:

1、Solr服务器的配置在solrconfig.xml中完成,包括对缓存,servlet的个性化配置等等,即系统全局的配置;

2、索引方法、索引域(字段)等等在schema.xml中完成,这个配置是针对Solr实例的;

3、索引数据文件默认放在Solr文档根目录下的data/index目录下,这个路径可以通过第1点配置,同时可以将这个目录下的文件进行复制粘贴,即可完成索引的复用;

4、建立索引的时间相当长,我采用按词无字典索引方式对2G110万条中文记录进行索引,花了将近2个半小时的时间(当然这个时间和很多因素有关,有兴趣的话大家可以留言和我讨论),相对而言,在linux下建索引时间要比windows下快很多,可以使用commit操作使新增索引生效,同时注意索引的优化,索引优化也是很费资源和时间的,但是优化索引也是提高检索速度的重要方法,因此需要好好权衡这一点;

5、安装完后的Solr目录下有这么几个文件夹:bin文件夹里主要是用于建立镜像和完成远程同步的脚本;conf文件夹下主要是1、2点中提到的配置文件;admin文件夹下是的主要是提供web管理界面的文件;

6、目前Solr1.2不具备安全性设计,没有用户组及权限设置,在进行具体应用时需要注意安全,目前最有效的方法是通过应用服务器上的授权实现。

本文永久链接:http://www.jinsehupan.com/blog/?p=25

[Solr的安装]Solr发行版中已经有一个使用Jetty为servlet容器的小例子,可以使用这个例子来体验,那正在在自己想部署的平台和应用服务器上该怎么一个步骤呢?

要开始使用 Solr,需安装以下软件:

1、Java 1.5 或更高版本;

2、Ant 1.6.x 或更高版本(用于编译管理Solr工程,个人推荐,当然可以使用eclipse);

3、Web 浏览器,用来查看管理页面(官方建议使用Firefox,但实际没有发现和IE有什么差别);

4、servlet 容器,如Tomcat 5.5(不建议使用6版本)。本文以Tomcat 在 8080 端口上运行为例。如果运行的是其他 servlet 容器或在其他的端口上运行,则可能要修改代码中的URL才能访问示例应用程序和 Solr。

下面开始安装配置:

1、使用Ant编译工程或下载示例应用程序,将Solr WAR 文件复制到 servlet 容器的webapps目录中;

2、得到Solr文件夹,以备随后将其复制到当前目录,可以使用ant build得到,也可以在下载的压缩包中找到,以它为模板以备之后的修改;

3、可以通过以下三种方式之一设置 Solr 的主位置:

设置 java 系统属性 solr.solr.home (没错,就是 solr.solr.home,一般在嵌入式集成中用得多);

配置 java:comp/env/solr/home 的一个 JNDI 查找指向 solr 目录,建立/tomcat55/conf/Catalina/localhost/solr.xml文件,注意这个xml文件名将是Solr实例名称,2中的当前目录被指定为下面中的f:/solrhome,文件内容如下:

Xml代码 Lucene/Solr开发经验 - hbluojiahui - hbluojiahui的博客

  1. <context docBase="f:/solr.war" debug="0" crossContext="true" >    
  2.    <environment name="solr/home" type="java.lang.String" value="f:/solrhome" override="true" />    
  3. </context>   

<context docBase="f:/solr.war" debug="0" crossContext="true" > <environment name="solr/home" type="java.lang.String" value="f:/solrhome" override="true" /> </context>

在包含 solr 目录的目录中启动 servlet 容器(默认的 Solr 主目录是当前工作目录下的 solr);

4、最后一点就是如果有CJK(中日韩文字)应用,出现乱码问题,采用如下方法解决(其实已经不算是solr配置问题,而是应用服务器配置问题),修改Tomcat的conf/server.xml文件中对于端口(本文为8080)的连接器统一资源编码为UTF-8,因为Solr1.2内核支持UTF-8编码:

Xml代码 Lucene/Solr开发经验 - hbluojiahui - hbluojiahui的博客

  1. <server ...>    
  2.  <service ...>    
  3.    <connector ... URIEncoding="UTF-8"/>    
  4.      ...    
  5.  </service>    
  6. </server>   

<server ...> <service ...> <connector ... URIEncoding="UTF-8"/> ... </service> </server>

[Solr分词顺序]Solr建立索引和对关键词进行查询都得对字串进行分词,在向索引库中添加全文检索类型的索引的时候,Solr会首先用空格进行分词,然后把分词结果依次使用指定的过滤器进行过滤,最后剩下的结果才会加入到索引库中以备查询。分词的顺序如下:

索引

1:空格whitespaceTokenize

2:过滤词StopFilter

3:拆字WordDelimiterFilter

4:小写过滤LowerCaseFilter

5:英文相近词EnglishPorterFilter

6:去除重复词RemoveDuplicatesTokenFilter

查询

1:查询相近词

2:过滤词

3:拆字

4:小写过滤

5:英文相近词

6:去除重复词

以上是针对英文,中文的除了空格,其他都类似

[Solr中文应用的一个实例]

1、首先配置schema.xml,这个相当于数据表配置文件,它定义了加入索引的数据的数据类型的。1.2版本的schema.xml主要包括types、fields和其他的一些缺省设置。

A、首先需要在types结点内定义一个FieldType子结点,包括name,class,positionIncrementGap等等一些参数,name就是这个FieldType的名称,class指向org.apache.solr.analysis包里面对应的class名称,用来定义这个类型的行为。在FieldType定义的时候最重要的就是定义这个类型的数据在建立索引和进行查询的时候要使用的分析器analyzer,包括分词和过滤。在例子中text这个FieldType在定义的时候,在index的analyzer中使用solr.WhitespaceTokenizerFactory这个分词包,就是空格分词,然后使用solr.StopFilterFactory,solr.WordDelimiterFilterFactory,solr.LowerCaseFilterFactory,solr.EnglishPorterFilterFactory,solr.RemoveDuplicatesTokenFilterFactory这几个过滤器。在向索引库中添加text类型的索引的时候,Solr会首先用空格进行分词,然后把分词结果依次使用指定的过滤器进行过滤,最后剩下的结果才会加入到索引库中以备查询。Solr的analysis包并没有带支持中文的包,在这里我们采用lucene里的语言包(在下载后的solr压缩包内,lib目录下有一个lucene-analyzers-2.2.0.jar包,里面含有中文处理的cn和cjk类),有cn和cjk两个类可以支持中文。我们采用cjk类,并在schema.xml中加入如下配置:

Xml代码 Lucene/Solr开发经验 - hbluojiahui - hbluojiahui的博客

  1. <fieldtype name="text_cjk" class="solr.TextField">    
  2.       <analyzer class="org.apache.lucene.analysis.cjk.CJKAnalyzer"/>    
  3.     </fieldtype>   

<fieldtype name="text_cjk" class="solr.TextField"> <analyzer class="org.apache.lucene.analysis.cjk.CJKAnalyzer"/> </fieldtype>

支持类型定义完成了。

B、接下来的工作就是在fields结点内定义具体的字段(类似数据库中的字段),就是filed,filed定义包括name,type(为之前定义过的各种FieldType),indexed(是否被索引),stored(是否被储存),multiValued(是否有多个值)等等。例如定义如下:

Xml代码 Lucene/Solr开发经验 - hbluojiahui - hbluojiahui的博客

  1. <field name="记录号" type="slong" indexed="true" stored="true" required="true" />    
  2.    <field name="文件名" type="string" indexed="true" stored="true" />    
  3.    <field name="日期" type="date" indexed="true" stored="true" />    
  4.    <field name="版次" type="string" indexed="true" stored="true" multiValued="true"/>    
  5.    <field name="栏目" type="string" indexed="true" stored="true" multiValued="true"/>    
  6.    <field name="标题" type="text_cjk" indexed="true" stored="true" multiValued="true"/>    
  7.    <field name="作者" type="text_cjk" indexed="true" stored="true" multiValued="true"/>    
  8.    <field name="正文" type="text_cjk" indexed="true" stored="true" multiValued="true"/>    
  9.    <field name="标记" type="text_cjk" indexed="true" stored="true" multiValued="true"/>   

<field name="记录号" type="slong" indexed="true" stored="true" required="true" /> <field name="文件名" type="string" indexed="true" stored="true" /> <field name="日期" type="date" indexed="true" stored="true" /> <field name="版次" type="string" indexed="true" stored="true" multiValued="true"/> <field name="栏目" type="string" indexed="true" stored="true" multiValued="true"/> <field name="标题" type="text_cjk" indexed="true" stored="true" multiValued="true"/> <field name="作者" type="text_cjk" indexed="true" stored="true" multiValued="true"/> <field name="正文" type="text_cjk" indexed="true" stored="true" multiValued="true"/> <field name="标记" type="text_cjk" indexed="true" stored="true" multiValued="true"/>

field的定义相当重要,有几个技巧需注意一下,对可能存在多值得字段尽量设置multiValued属性为true,避免建索引是抛出错误;如果不需要存储相应字段值,尽量将stored属性设为false。

C、建议建立了一个拷贝字段,将所有的全文字段复制到一个字段中,以便进行统一的检索:

Xml代码 Lucene/Solr开发经验 - hbluojiahui - hbluojiahui的博客

  1. <field name="text_com" type="text_cjk" indexed="true" stored="false" multiValued="true"/>   

<field name="text_com" type="text_cjk" indexed="true" stored="false" multiValued="true"/>

并在拷贝字段结点处完成拷贝设置:

Xml代码 Lucene/Solr开发经验 - hbluojiahui - hbluojiahui的博客

  1. <copyfield source="标题" dest="text_com"/>    
  2.    <copyfield source="正文" dest="text_com"/>   

<copyfield source="标题" dest="text_com"/> <copyfield source="正文" dest="text_com"/>

D、除此之外,还可以定义动态字段,所谓动态字段就是不用指定具体的名称,只要定义字段名称的规则,例如定义一个dynamicField,name为*_i,定义它的type为text,那么在使用这个字段的时候,任何以_i结尾的字段都被认为是符合这个定义的,例如name_i,gender_i,school_i等。

2、配置solrconfig.xml,用来配置Solr的一些系统属性,比较重要的一个就是可以通过更改其中的dataDir属性来指定索引文件的存放位置,对于有大数据量的情况下还要进行自动commit操作配置,以下设置为当内存索引量达到20W条时自动进行往磁盘写操作,以免堆溢出,这也是解决单个入库xml文件最好不要超过30M的有效方法:

Xml代码 Lucene/Solr开发经验 - hbluojiahui - hbluojiahui的博客

  1. <autocommit>    
  2.     <maxdocs>200000</maxdocs>    
  3.   </autocommit>   

<autocommit> <maxdocs>200000</maxdocs> </autocommit>

3、配置好这些后,需要重新启动Solr服务器使配置生效,然后向其中添加数据。

4、添加数据是通过向服务器的update Servlet POST xml格式的数据来实现的,xml结构是这样的add中间有很多个doc,每个doc中有很多个field。添加到索引库中的每条记录都必须指定唯一的数字id来唯一标识这条索引。建立好xml文件(例如solr.xml)之后,在exampledocs目录下执行:java -jar post.jar solr.xml来添加索引数据。对于post的jar包,如果重新配置了应用服务器,如使用了comcat,端口改为8080,实例名称改为solrx了需要重新生成相应的post.jar包进行操作。

另附ronghao实现中文分词的案例供大家参考:

对全文检索而言,中文分词非常的重要,这里采用了qieqie庖丁分词(非常不错:))。集成非常的容易,我下载的是2.0.4-alpha2版本,其中它支持最多切分和按最大切分。创建自己的一个中文TokenizerFactory继承自solr的BaseTokenizerFactory。

**

* Created by IntelliJ IDEA.

* User: ronghao

* Date: 2007-11-3

* Time: 14:40:59

* 中文切词 对庖丁切词的封装

*/

public class ChineseTokenizerFactory extends BaseTokenizerFactory {

/**

* 最多切分 默认模式

*/

public static final String MOST_WORDS_MODE = “most-words”;

/**

* 按最大切分

*/

public static final String MAX_WORD_LENGTH_MODE = “max-word-length”;

private String mode = null;

public void setMode(String mode) {

if (mode==null||MOST_WORDS_MODE.equalsIgnoreCase(mode)

|| “default”.equalsIgnoreCase(mode)) {

this.mode=MOST_WORDS_MODE;

} else if (MAX_WORD_LENGTH_MODE.equalsIgnoreCase(mode)) {

this.mode=MAX_WORD_LENGTH_MODE;

}

else {

throw new IllegalArgumentException(”不合法的分析器Mode参数设置:” + mode);

}

}

@Override

public void init(Map args) {

super.init(args);

setMode(args.get(”mode”));

}

public TokenStream create(Reader input) {

return new PaodingTokenizer(input, PaodingMaker.make(),

createTokenCollector());

}

private TokenCollector createTokenCollector() {

if( MOST_WORDS_MODE.equals(mode))

return new MostWordsTokenCollector();

if( MAX_WORD_LENGTH_MODE.equals(mode))

return new MaxWordLengthTokenCollector();

throw new Error(”never happened”);

}

}

在schema.xml的字段text配置里加入该分词器。

Xml代码 Lucene/Solr开发经验 - hbluojiahui - hbluojiahui的博客

  1. <fieldtype name="text" class="solr.TextField" positionIncrementGap="100">    
  2.   
  3.     
  4.   
  5.             <analyzer type="index">    
  6.   
  7.     
  8.   
  9.                <tokenizer class="com.ronghao.fulltextsearch.analyzer.ChineseTokenizerFactory" mode="most-words"/>    
  10.   
  11.   
  12.   
  13.   
  14.   
  15.                 <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>    
  16.   
  17.     
  18.   
  19.                     <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0"/>    
  20.   
  21.         
  22.   
  23.                     <filter class="solr.LowerCaseFilterFactory"/>    
  24.   
  25.        
  26.   
  27.         
  28.   
  29.                     <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>    
  30.   
  31.        
  32.   
  33.               </analyzer>    
  34.   
  35.        
  36.   
  37.                 <analyzer type="query">    
  38.   
  39.        
  40.   
  41.                     <tokenizer class="com.ronghao.fulltextsearch.analyzer.ChineseTokenizerFactory" mode="most-words"/>                   
  42.   
  43.        
  44.   
  45.                     <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>    
  46.   
  47.         
  48.   
  49.                     <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>    
  50.   
  51.         
  52.   
  53.                     <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0"/>    
  54.   
  55.         
  56.   
  57.                    <filter class="solr.LowerCaseFilterFactory"/>    
  58.   
  59.         
  60.   
  61.                     <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>    
  62.   
  63.       
  64.   
  65.                 </analyzer>    
  66.   
  67.         
  68.   
  69.             </fieldtype>  

<fieldtype name="text" class="solr.TextField" positionIncrementGap="100"> <analyzer type="index"> <tokenizer class="com.ronghao.fulltextsearch.analyzer.ChineseTokenizerFactory" mode="most-words"/> <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/> <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0"/> <filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.RemoveDuplicatesTokenFilterFactory"/> </analyzer> <analyzer type="query"> <tokenizer class="com.ronghao.fulltextsearch.analyzer.ChineseTokenizerFactory" mode="most-words"/> <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/> <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/> <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0"/> <filter class="solr.LowerCaseFilterFactory"/> <filter class="solr.RemoveDuplicatesTokenFilterFactory"/> </analyzer> </fieldtype>

完成后重启tomcat,即可在http://localhost:8080/solr/admin/analysis.jsp

体验到庖丁的中文分词。注意要将paoding-analysis.jar复制到solr的lib下,注意修改jar包里字典的home。

[Solr的检索运算符]

“:” 指定字段查指定值,如返回所有值*:*²

“?”²表示单个任意字符的通配

“*” 表示多个任意字符的通配(不能在检索的项开始使用*或者?符号)²

“~”²表示模糊检索,如检索拼写类似于”roam”的项这样写:roam~将找到形如foam和roams的单词;roam~0.8,检索返回相似度在0.8以上的记录。

²邻近检索,如检索相隔10个单词的”apache”和”jakarta”,”jakarta apache”~10

“^”²控制相关度检索,如检索jakarta apache,同时希望去让”jakarta”的相关度更加好,那么在其后加上”^”符号和增量值,即jakarta^4 apache

布尔操作符AND、||²

布尔操作符OR、²&&

布尔操作符NOT、!、-²(排除操作符不能单独与项使用构成查询)

“+” 存在操作符,要求符号”+”后的项必须在文档相应的域中存在²

( ) 用于构成子查询²

² [] 包含范围检索,如检索某时间段记录,包含头尾,date:[200707 TO 200710]

{}²不包含范围检索,如检索某时间段记录,不包含头尾

date:{200707 TO 200710}[/size]

" 转义操作符,特殊字符包括+ -² && || ! ( ) { } [ ] ^ ” ~ * ? : "[/size]

转自:http://stephenjqj.javaeye.com/blog/413356