分享一个Lucene索引公用组件--LuciMint

最新更新包V1.1.5已经发布

前言
关于LuciMint的诞生, 笔者觉得有必要说明一下。首先它不是一个开源项目,而只是笔者在工作过程中,总结出的一个工具包,因此,第一,它暂时不开源(代码还处于优化修改期),第二,它的功能还是比较有限的。与Lucene相对比,LuciMint就如同一部廉价的卡片机,Lucene则是一部单反相机了,哈哈~~ 但谁说卡片机不遭人喜爱呢,这也是笔者想把它与大家分享的原因。对于一般性的企业知识库应用,网站BBS搜索等,大数量零散数据(如,本地文件系统,邮件系统等等)的辅助索引,还是好用的。

现在言归正传,说说为啥会有LuciMint。笔者从5年前开始参与与Lucene相关的项目开发,发现有一些问题,几乎是所有系统都必须面对和解决的。如:
  • 1.Lucene存在索引写入同步问题(虽然Lucene自身提供了文件级的同步写入锁,但面对时不时抛出的同步异常,还是很不爽的)。尤其,在写入请求并发量大的时候,从性能到稳定性上,问题都十分突出。
  • 2.用过Lucene的用户会发现,Lucene的磁盘索引不适合于实时索引,既“实时写入,实时查询”。首先,实时的写入会产生大量零散的索引碎片,不利率Lucene的搜索效率;其次,Lucene的Reader对新进的文档是无法读取到的,必须重新打开,这个也影响效率。
  • 3.Lucene的索引是以文件形式存在的,对于分布式的web系统而言,你不可能为每台APP服务器搭建一个Lucene索引,因此Lucene缺乏一个统一远程的索引/搜索服务。
  • 4.对于不同的表结构(Java Bean实体),要创建不同的Document的结构,读取时也要自己解析,这个在hibernate /ibatis 流行的当今java界,也显得麻烦了。
  • 5.默认的Lucene自带的QueryParser对用户,特别是中文用户的查询习惯不很吻合,笔者常常会遇到有人问,为什么我输入“Java加密”却搜不到“JavaMD5加密”。

为解决上述问题,笔者需要在不同的项目间复用相关的代码,并不断的优化,重构之。于是乎,便有了现在的LuciMint。为啥叫这个名字,因为取名的时候,笔者正在吃一颗味道不错的薄荷糖,哈哈哈~


1.LuciMint介绍
    LuciMint(Lucene Index薄荷糖)是一个基于Lucene的全文索引小组件,它封装了Lucene的底层对Document对象的操作,提供了一个面向用户数据格式的,轻量级的索引操作接口。

    采用异步请求方式,LuciMint实现了 “高并发的实时索引”,“实时搜索”,“多套索引配置”等核心功能。专门的,针对Java语言客户端,LuciMint设计了Annotation标签,通过Java Bean属性的简单的标注,用户可以直接将Bean生成Lucene索引.
同时,LuciMint结合了IKAnalyzer3.2.8版本的了“简易的搜索表达式”功能,实现了统一的HTTP搜索服务接口。

    LuciMint适用于基于Lucene的,一般性的,大中型企业知识库、文档中心,或者中小型的互联网论坛,地图POI信息搜索等项目。

1.1 LuciMint组件结构
组件整体结构

分享一个Lucene索引公用组件--LuciMint_第1张图片

Index Context内部结构

分享一个Lucene索引公用组件--LuciMint_第2张图片


1.2 LuciMint特性
  • 1.通过异步任务队列处理方式,LuciMint解决了,在高并发索引请求下的索引同步操作问题。
  • 2.实现了Lucene索引信息的“实时写入,实时搜索”。
  • 3.提供了HTTP + XML格式的web访问接口,实现了Lucene在分布式群集系统中的接入使用。
  • 4.对于Java用户而言,LuciMint提供了简单的Annotation来辅助对Bean实现的索引创建。通过在Java Bean上标注Annotation,并传入相应的API,即可完成索引的创建。对于非Java用户而言,LuciMint也提供了介于HTTP和XML的web服务接口。
  • 5.LuciMint为一般性用户提供了一个基于HTTP的搜索接口,用户通过“简易搜索表达式”,可以方便的进行搜索查询操作。有特殊搜索需求的用户,也可以尝试在LuciMint的基础上,构建专用的搜索接口,来实现高级搜索功能。


2.使用指南
2.1安装部署
1.LuciMint要引入的依赖包及配置文件
 以本地jar包形式集成LuciMint,需要以下jar包
  • LuciMint.jar
  • IKAnalyzer3.2.8.jar
  • Lucene Core 3.X.jar
  • Jackson-core-asl-1.6.0.jar
  • Jackson-mapper-asl-1.6.0.jar
  • Spring 2.5.6.jar
  • Spring.xml配置文件
  • log4j-1.2.15jar (这个是Spring需要的依赖包)
  • common-logging-1.1.1.jar((这个是Spring需要的依赖包)


 以HTTP服务方式集成LuciMint,需要以下jar包

服务端:与“以本地jar包形式集成LuciMint”方式相同,此外还要在web.xml中配置一个HTTP服务(servlet)。(祥见部署部分说明)
客户端:客户端部署只需要
  • LuciMint.jar
  • Jackson-core-asl-1.6.0.jar
  • Jackson-mapper-asl-1.6.0.jar

LuciMint的Spring.xml配置
不论你使用本地jar包方式或者HTTP服务方式,你都需要配置Spring.xml。用户需要靠它来指定索引的目录,及索引名称等自定义的信息。下面我们给出一个Spring.xml的配置实例来进行讲述。 值得说明的是,一个LuciMint是可以同时支持创建多个Luene索引实例的,这很适用于在一个物理服务器上构建不同内容,不容需求的索引服务。
以下是Spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<!-- 索引分词器配置-->
	<bean id="defaultAnalzyer" class="org.wltea.analyzer.lucene.IKAnalyzer"></bean>
     -- 该属性指定LuciMint使用的分词器实例,目前及支持IKAnalzer3.2.8,所以你不用改这行配置。

<!-- 索引控制器配置-1 -->
	<bean id="bbsIndexConfig" class="org.wltea.luci.index.IndexConfig">
		<property name="indexName" value="BBS" /> --索引名称,该属性唯一指定一个索引控制器
		<property name="keyFieldName" value="id" /> --索引数据的主键名称,要跟Bean的主键字段对应
		<property name="rootDir" value="i:/sohufz/index/bbs/" />--索引数据存放的目录
		<property name="luceneAnalyzer" ref="defaultAnalzyer" />--索引使用的分词器,引用先前的defaultAnalzyer
	</bean>
	
<!-- 索引控制器配置-2 配置同上 ,前面说过LuciMint支持多个索引-->
	<bean id="commentIndexConfig" class="org.wltea.luci.index.IndexConfig">
		<property name="indexName" value="COMMENT" />
		<property name="keyFieldName" value="commentId" />
		<property name="rootDir" value="D:/comment/index/" />
		<property name="luceneAnalyzer" ref="defaultAnalzyer" />
	</bean>	

<!-- 索引控制器配置-3 配置同上-->
	<bean id="sampleIndexConfig" class="org.wltea.luci.index.IndexConfig">
		<property name="indexName" value="SAMPLE" />
		<property name="keyFieldName" value="uuid" />
		<property name="rootDir" value="E:/index/sample/" />
		<property name="luceneAnalyzer" ref="defaultAnalzyer" />
	</bean>	

	<!-- 索引控制器的容器  -->
	<bean id="LuciMint.IndexContextContainer" class="org.wltea.luci.index.IndexContextContainer" init-method="getInstance" >
		<property name="indexConfigs">
			<list>
				<ref bean="bbsIndexConfig" />  -- 在这里引用上述的索引控制器配置
				<ref bean="commentIndexConfig" /> -- 在这里引用上述的索引控制器配置
				<ref bean="sampleIndexConfig" /> -- 在这里引用上述的索引控制器配置
			</list>	
		</property>
	</bean>
</beans>

特别值得提醒的是,将不同的索引目录放置在不同的物理磁盘上,有助于提高索引的读写效率!!!

LuciMint在HTTP服务方式下的web.xml配置
前面文档中提到过,如果你使用的是LuciMint的HTTP服务方式部署,那么你需要在web.xml文件中配置一个HTTP Servlet。如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee   http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

<servlet>
  <display-name>Luci索引RPC服务端HTTP实现</display-name>
  <servlet-name>LuciIndexService</servlet-name>
  <servlet-class>org.wltea.luci.rpc.http.LuciIndexServlet</servlet-class> 
 </servlet>
 
 
  <servlet-mapping>
  <servlet-name>LuciIndexService</servlet-name>
  <url-pattern>/web/indexservice</url-pattern> --这里可以按你自己的需求定义url(啰嗦一句哈哈)
 </servlet-mapping>
 
</web-app>


2.2快速入门
Java客户端使用
1.使用LuciMint的Annotation注释JavaBean
LuciMint的Annotation非常简单,一共就3个:
org.wltea.luci.annotation.PKey -- 主键字段标识。
使用@Pkey注释,表示该字段采用索引、存储、不切分的方式。注意Bean中的主键字段必须和Spring.xml索引配置中的索引主键域名字一致。
org.wltea.luci.annotation.FieldStore --非主键字段的存储标识。
使用@FieldStore表示这个字段要在索引中存储,不使用表示不存储。
org.wltea.luci.annotation.FieldIndex -- 非主键字段的索引表示。
使用@FieldIndex等同于@FieldIndex("NO")表示不索引
使用@FieldIndex等同于@FieldIndex("NOT_ANALYZED")表示采用索引不分词策略。
使用@FieldIndex("ANALYZED")表示采用索引且分词策略。

要说明的是,如果Bean的属性不使用任何Luci Annotation的标识,则Luci在索引中将忽略这个属性。
以下是JavaBean的Annotation列子:
/**
 * 
 */
package org.wltea.luci.sample;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.wltea.luci.annotation.FieldIndex;
import org.wltea.luci.annotation.FieldStore;
import org.wltea.luci.annotation.PKey;

/**
 * @author linliangyi
 *
 */
public class SampleJavaBean implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 7153417317917298956L;
	
	@PKey
	private int uuid;

	@FieldStore
	@FieldIndex("ANALYZED")
	private String userName;

	@FieldStore
	private boolean checkFlag;
	
	@FieldIndex
	private String url;
	
	@FieldStore
	@FieldIndex
	private Date registTime;

	public int getUuid() {
		return uuid;
	}

	public void setUuid(int uuid) {
		this.uuid = uuid;
	}

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public boolean isCheckFlag() {
		return checkFlag;
	}

	public void setCheckFlag(boolean checkFlag) {
		this.checkFlag = checkFlag;
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public Date getRegistTime() {
		return registTime;
	}

	public void setRegistTime(Date registTime) {
		this.registTime = registTime;
	}
	
}

2.以本地jar包形式调用java client
/**
 * 
 */
package org.wltea.luci.sample;

import java.util.Date;
import java.util.List;

import org.wltea.luci.client.IndexService;
import org.wltea.luci.client.IndexServiceFactory;
import org.wltea.luci.client.QueryResults;

/**
 * 
 * 简易索引例子
 * @author linliangyi
 *
 */
public class LuciMintSample {

	
	/**
	 * 本地索引HelloWorld例子
	 * @param args
	 */
	public static void main(String[] args){
		
		//以本地客户端方式,根据命名,获取Luci索引服务实例,
//这里的名字要跟Spring文件配置的索引名称一致,注意大小写敏感哦
		IndexService indexService = IndexServiceFactory.getLocalIndexService("BBS");
		//使用带注释的Bean,创建索引
		for(int i = 0 ; i <  20 ; i++){
			//一个需要建索引的JavaBean
			SampleJavaBean bean = new SampleJavaBean();
			bean.setCheckFlag(true);
			bean.setRegistTime(new Date());
			bean.setUrl("http://sample.lucimint.org");
			bean.setUserName("LuciMint" + i);
			bean.setUuid(20000 + i);
            //新增索引实际上就一句
			indexService.add(bean);
		}
		
		try {
			Thread.sleep(1000);
			System.out.println("*****************************");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}	
		
		//索引查询部分代码
		//编写查询逻辑
		String queryString = "url='http://sample.lucimint.org'";
         //查询索引也只要一句, 
		QueryResults queryResults = indexService.query(queryString, 1, 20, true);
		System.out.println("PageNo :" + queryResults.getPageNo());
		System.out.println("PageSize :" + queryResults.getPageSize());
		System.out.println("TotalHit :" + queryResults.getTotalHit());
		System.out.println("TotalPage :" + queryResults.getTotalPage());
		//读取具体的数据列表 , 传入你的bean类型,Luci将帮你封装好结果集
		List<SampleJavaBean> beanList = queryResults.getResultBeans(SampleJavaBean.class);
		
		
		for(SampleJavaBean bean : beanList){
			System.out.println(bean.getUuid() + " | " +bean.getUserName());
		}
		
	}
}

3.以远程服务信息调用java client
远程调用Luci服务和本地调用的代码几乎是一样的,只有一句不同
/**
 * 
 */
package org.wltea.luci.sample;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import java.util.List;

import org.wltea.luci.client.IndexService;
import org.wltea.luci.client.IndexServiceFactory;
import org.wltea.luci.client.QueryResults;

/**
 * 远程调用例子
 * @author linliangyi
 *
 */
public class LuciMintRPCSample {
	
	/**
	 * 远程索引HelloWorld例子
	 * @param args
	 */
	public static void main(String[] args){		
	    //要声明远程服务的HTTP地址,如下
		URL remoteHttpURL = null;
		try {
			remoteHttpURL = new URL("http://10.5.21.86/sc/web/indexservice");
		} catch (MalformedURLException e1) {
			e1.printStackTrace();
		}
		//唯一不一样的就是这里,根据索引命名,获取Luci远程索引器实例
		IndexService indexService = IndexServiceFactory.getRemoteIndexService("SAMPLE", remoteHttpURL);
		//使用带注释的Bean,创建索引
		for(int i = 0 ; i <  20 ; i++){
			//一个需要建索引的JavaBean
			SampleJavaBean bean = new SampleJavaBean();
			bean.setCheckFlag(true);
			bean.setRegistTime(new Date());
			bean.setUrl("http://sample.lucimint.org");
			bean.setUserName("LuciMint" + i);
			bean.setUuid(20000 + i);
             //新增索引
			indexService.add(bean);
		}
		
		try {
			Thread.sleep(3000);
			System.out.println("*****************************");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}	
		
		//**********************************查询部分
		//编写查询逻辑
		String queryString = "url='http://sample.lucimint.org'";
         //查询索引也只要一句, 
		QueryResults queryResults = indexService.query(queryString, 1, 20, true);
		System.out.println("PageNo :" + queryResults.getPageNo());
		System.out.println("PageSize :" + queryResults.getPageSize());
		System.out.println("TotalHit :" + queryResults.getTotalHit());
		System.out.println("TotalPage :" + queryResults.getTotalPage());
		//读取具体的数据列表 , 传入你的bean类型,Luci将帮你封装好结果集
		List<SampleJavaBean> beanList = queryResults.getResultBeans(SampleJavaBean.class);
		
		
		for(SampleJavaBean bean : beanList){
			System.out.println(bean.getUuid() + " | " +bean.getUserName());
		}
	}
	
}


通用(非java客户端)HTTP 索引服务接口使用
如果你有PHP,或者其他的客户端,那么你可以使用这个通用接口
1. 索引操作接口
索引操作接口是指对索引数据进行 新增、删除、修改、优化等数据变更操作接口。接口使用标准的HTTP POST请求,
HTTP请求参数如下表:
分享一个Lucene索引公用组件--LuciMint_第3张图片
2. 索引操作接口返回结果
返回结果使用了原生的HTTP response协议。如果索引操作接口提交的请求正常执行,则接口返回HTTP 200应答。如果提交出现错误,则接口返回HTTP 500错误,并将异常信息带着response message中返回。
HTTP response头部信息如下:
HTTP/1.0 200 OK
HTTP/1.0 500 <异常描述字窜>

3. 索引查询接口
索引查询接口是指对索引数据进行搜索操作的接口。接口使用标准的HTTP POST请求,
HTTP请求参数如下表:
分享一个Lucene索引公用组件--LuciMint_第4张图片

4. 索引查询接口返回结果
查询结果使用JSON格式返回。格式如下:
{
"pageNo":2,   //页号
"pageSize":20,//单页记录数
"totalHit":33,//总记录数
"totalPage":2,//总页数
"results":[    //结果数据集。内部是记录的具体属性
		{
		"userName":"林良益",
		"uuid":"10000",
		"registTime":"20110225180130",
		"url":"http://www.sohu.com"
		},
		{
		"userName":"蔡剑锋",
		"uuid":"10001",
		"registTime":"20110225180131",
		"url":"http://www.sohu.com"
		}
		......		
		]
}

XML-DATA数据格式说明
<?xml version="1.0" encoding="UTF-8"?>
<index-data>
	<document>
		<field name="uuid" pkey="true" >10000</field>
		<field name="url" store="true"><![CDATA[http://www.sohu.com]]></field>
		<field name="check" >false</field>
		<field name="userName"  store="true" index="ANALYZED">林良益</field>
		<field name="registTime" store="true" index="NO_ANALYZED" >20110225180130</field>
	</document>
	<document>
		<field name="uuid" pkey="true" >10001</field>
		<field name="url" store="true"><![CDATA[http://www.sohu.com]]></field>
		<field name="check" >false</field>
		<field name="userName"  store="true" index="ANALYZED">蔡剑锋</field>
		<field name="registTime" store="true" index="NO_ANALYZED" >20110225180131</field>
	</document>	
</index-data>

  • 1. XML文件必须是UTF-8编码
  • 2. 根元素<index-data>表示数据集合的开始,一个<index-data>元素中可以含有多条<document>记录。
  • 3. 元素<document>表示一条数据记录数据,一个<document>元素可以含有多个<field>字段属性。
  • 4. 元素<field>表示一条数据记录中的一个字段
  • 5. field的name属性表示字段名称, field中的文本表示字段值,复杂文本请使用CDATA标签。注意:文本中不能含有非法的XML字符[\\x00-\\x08|\\x0b-\\x0c|\\x0e-\\x1f]。
  • 6. pkey 属性指定数据记录的主键,每条document必须有且只能有一个字段是主键。主键的字段要和索引服务端配置的主键名称相一致。
  • 7. store 属性表示字段是否要在Lucene索引中保存。store="true"表示索引要保存该字段值,store="false"或者没有定义store属性,则不保存
  • 8. index 属性表示字段是否要被索引,index有三个值:NO, NO_ANALYZED,  ANALYZED,分别表示,不索引,索引但不分词,分词后索引。不定义index属性默认不索引。


简易搜索表达式说明
搜索表达式样例:
id='1231' && (title:'文档标题'|| content:'内容') – author='helloworld'

  • 1. 表达式使用 属性名<->属性值一一对应的形式,属性值使用 单引号 标识。
  • 2. 在属性名-属性值中使用 “=”等号,表示对该属性的精确搜索(不分词);使用“:”冒号表示对属性的模糊搜索(分词搜索)。
  • 3. 表达式支持“&&”与 “||” 或“-”非的逻辑操作,以及“( )”括号优先级定义。注意“-”非逻辑不能单独使用。


关于索引的初始化
笔者专门提出一个章节,提醒用户关于导入历史数据的注意事项 (除非你是全新的系统,没有历史数据).
  • 1. 请使用IndexService.build(Object)或者IndexService.build(List<?> objs) 方法批量导入历史数据,不要使用IndexService.add(Object)。前者为数据批量迁移定制,后者为动态增量数据定制。
  • 2. 在完成历史数据的索引初始化后,务必调用IndexService.optimize()方法,在正式使用前,对索引进行优化操作。否则,你的索引搜索性能有可能大幅度下降!!!


2.3 Java API说明
    详细请参阅使用《LuciMint 索引组件 1.1 使用手册》和Java API DOC


相关下载:

IKAnalyzer3.2.8 下载

你可能感兴趣的:(spring,bean,xml,应用服务器,Lucene)