最新更新包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组件结构
组件整体结构
Index Context内部结构
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请求参数如下表:
2. 索引操作接口返回结果
返回结果使用了原生的HTTP response协议。如果索引操作接口提交的请求正常执行,则接口返回HTTP 200应答。如果提交出现错误,则接口返回HTTP 500错误,并将异常信息带着response message中返回。
HTTP response头部信息如下:
HTTP/1.0 200 OK
HTTP/1.0 500
<异常描述字窜>
3. 索引查询接口
索引查询接口是指对索引数据进行搜索操作的接口。接口使用标准的HTTP POST请求,
HTTP请求参数如下表:
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 下载