Lucene -- 实时索引
lucene的实时搜索可以分成:实时和近实时的搜索。
实时只能依靠内存了。
近实时可以用lucene中提供org.apache.lucene.index.DirectoryReader.open(IndexWriter writer, boolean applyAllDeletes) throws IOException,可以在不十分影响性能的前提下,实现近实时的效果(比如每1s打开一次搜索,这类似于solr中的实现)。
一、实时搜索
lucene一般有ramdirectory和fsddirectory两种方式存储索引
第一个是内存方式,非常快,但没有持久化; 第二个是硬盘方式,慢,但有持久化。
Lucene 的事务性,使得Lucene 可以增量的添加一个段,我们知道,倒排索引是有一定的格式的,而这个格式一旦写入是非常难以改变的,那么如何能够增量建索引呢?
Lucene 使用段这个概念解决了这个问题,对于每个已经生成的段,其倒排索引结构不会再改变,而增量添加的文档添加到新的段中,段之间在一定的时刻进行合并,从而形成新的倒排索引结构。
Lucene 的事务性,使得Lucene 的索引不够实时,如果想Lucene 实时,则必须新添加的文档后IndexWriter 需要commit,在搜索的时候IndexReader 需要重新的打开,然而当索引在硬盘上的时候,尤其是索引非常大的时候,IndexWriter 的commit 操作和IndexReader 的open 操作都是非常慢的,根本达不到实时性的需要。
其实一般的应用,如果可以允许有1、2分钟的延时,那么用fsddirectory就足够了,每1分钟增加索引并commit即可。
但是如果有需求,要实时搜索的话,那么就需要用ram和fsd两种方式来组合使用了。
大致原理是用multireader组合多个索引的searcher即可。
(multireader可以为实时搜索服务,也可用于分布式索引啊)
实时步骤是:
1、先打开fsdindex,用于搜索;如果新增文档,则加入ramindex,并重打开ramsearcher。ram的重打开是很快的。
然后定时把ramindex写入磁盘。
2、在写入的时候,fsd需要commit并重新打开一个reader,这个时候需要新开一个ramindex。
在此时的搜索需要打开3个searcher,原ramsearcher,原fsdsearcher,新ramsearcher。
这个时候原ramindex写入磁盘的时候,只要不commit就不会出现重复结果。
3、ramindex写入磁盘结束,那么需要新打开一个fsdsearcher,这个过程是比较慢的。所以我们保持第2步的3个searcher先不变,继续服务。
4、当心得fsdsearcher打开完毕,那么丢弃原fsdsearcher和原ramseacher。使用新的fsdsearcher和ramsearcher 这4步中的操作大多是原子性的,如果做了(2)但没有做(3),如果来一个搜索,则将少看到一部分数据,如果做了(3)没有做(2)则,多看到一部分数据。所以需要加一个同步锁,以防数据异常。
二、近实时搜索
实现原理:
Near real time search的原理记录在LUCENE-1313和LUCENE-1516里。
LUCENE-1313,在Index Writer内部维护了一个ram directory,在内存够用前,flush和merge操作只是把数据更新到ram directory,只有Index Writer上的optimize和commit操作才会导致ram directory上的数据完全同步到文件。
LUCENE-1516,Index Writer提供了实时获得reader的API,这个调用将导致flush操作,生成新的segment,但不会commit(fsync),从而减少 了IO。新的segment被加入到新生成的reader里。从返回的reader里,可以看到更新。所以,只要每次新的搜索都从Index Writer获得一个新的reader,就可以搜索到最新的内容。这一操作的开销仅仅是flush,相对commit来说,开销很小。
Lucene的index组织方式为一个index目录下的多个segment。新的doc会加入新的segment里,这些新的小segment每隔一段时间就合并起来。因为合并,总的segment数量保持的较小,总体search速度仍然很快。为了防止读写冲突,lucene只创建新的 segment,并在任何active的reader不在使用后删除掉老的segment。
flush是把数据写入到操作系统的缓冲区,只要缓冲区不满,就不会有硬盘操作。
commit是把所有内存缓冲区的数据写入到硬盘,是完全的硬盘操作。
optimize是对多个segment进行合并,这个过程涉及到老segment的重新读入和新segment的合并,属于CPU和IO-bound的
重量级操作。这是因为,Lucene索引中最主要的结构posting通过VINT和delta的格式存储并紧密排列。合并时要对同一个term的posting进行归并排序,是一个读出,合并再生成的过程。
代码解读:
在IndexWriter获得reader的方法中,主要调用了两个方法doflush()和maybeMerge()。doflush() 将调用DocumentsWriter的flush方法,生成新的segment,返回的reader将能访问到新的segment。 DocumentsWriter接收多个document添加,并写入到同一个segment里。每一个加入的doc会经过多个DocConsumer组 成的流水线,他们包括StoredFieldsWriter(内部调用 FieldsWriter),TermVectorsTermsWriter,FreqProxTermsWriter,NormsWriter等。在外 界没有主动调用flush的情况下,RAM buffer全用完了或者加入的doc数足够大后,才会创建新的segment并flush到目录中。
FreqProxTermsWriter调用TermHashPerField负责term的索引过程,当索引某字段词项时,使用对应 TermsHashPerField的add()函数完成(一个)词项索引过程,并将索引内容(词项字符串/指针信息/位置信息等)存储于内存缓冲中。中 间的过程使用了CharBlockPool,IntBlockPool,ByteBlockPool,只要内存够用,可以不断往后添加。
特性试验:
设计一个文档检索程序,进程管理一个index writer和两个线程,线程A负责新文档的索引,线程B负责处理搜索请求,其中搜索时使用IndexWriter的新API获取新的reader。通过交替的生成index和search的请求,观察search的结果和索引目录的变化。实验结果如下:
1 打开indexwriter时,会生成一个lock文件
2 每次调用reader时,如果发生了更新,会先进行一次flush,把上次积攒在内存中的更新数据写成新的segment,多出一个.cfs。
3 从新的reader中,可以读到之前新加入的doc信息。
4 当新生成的segment达到十次后,会发生一次optimize,生成8个文件,为.fdt, .fdx, .frq, .fnm,
.nrm, .prx, .tii, .tis。
5 当然,外界也可以主动触发optimize,结果是一样的。optimize前的多个segment的文件以及此前optimize的文件不再有用。
6 因为optimize生成cfs要消耗双倍磁盘空间,并增加额外的处理时间,当optimize的index大小较大,超过了index总大小的10或者一个规定大小时,即使index writer指定了CFS格式,optimize仍然会保留为多个文件的格式(LUCENE-2773)。
7 调用indexwriter的close方法,lock文件会被释放,但除了optimize的结果文件外,此前生成的文件并不会被删除。只到下次打开此index目录时,不需要的文件才会被删除。
8 当三种情况下,indexwriter会试图删除不需要的文件,on open,on flushing a new segment,On finishing a merge。但如果当前打开的reader正在使用文件,则不会删除。
9 因此,reader使用完后,一定要调用close方法,释放不需要的文件。
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.Version; public class NRT1 { /**
* 用IndexReader.open(writer, false);实现近实时效果
*
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
Directory dir = new RAMDirectory();
IndexWriterConfig iwc = new IndexWriterConfig(Version.LUCENE_41,
new StandardAnalyzer(Version.LUCENE_41));
IndexWriter w = new IndexWriter(dir, iwc);
w.commit(); FieldType doctype = new FieldType();
doctype.setIndexed(true);
doctype.setStored(true); Document doc = new Document();
doc.add(new Field("title", "haha", doctype)); DirectoryReader r = DirectoryReader.open(dir);
for (int i = 0; i < 3; i++) {
w.addDocument(doc);
r = DirectoryReader.open(w, false);
// true:使得删除可见(并不是写入磁盘);false:删除操作不可见,这会使得性能比true要高一些。
System.out.println(r.numDocs());
}
}
}
Lucene -- 实时索引的更多相关文章
- Solr4.8.0源码分析(10)之Lucene的索引文件(3)
Solr4.8.0源码分析(10)之Lucene的索引文件(3) 1. .si文件 .si文件存储了段的元数据,主要涉及SegmentInfoFormat.java和Segmentinfo.java这 ...
- lucene写索引出现锁文件的原因之一
lucene正常情况目录下的文件 有三个文件. segments.gen segments_a08, 还有一个类似 _uw.cfs名字的东西. 当然,不一定都一样, 但肯定是这三个. 如果出现了很多文 ...
- Sphinx 实时索引
index rt { type = rt rt_mem_limit = 512M path = /usr/local/sphinx/data/rt rt_field = title rt_field ...
- lucene学习笔记:三,Lucene的索引文件格式
Lucene的索引里面存了些什么,如何存放的,也即Lucene的索引文件格式,是读懂Lucene源代码的一把钥匙. 当我们真正进入到Lucene源代码之中的时候,我们会发现: Lucene的索引过程, ...
- lucene创建索引简单示例
利用空闲时间写了一个使用lucene创建索引简单示例, 1.使用maven创建的项目 2.需要用到的jar如下: 废话不多说,直接贴代码如下: 1.创建索引的类(HelloLucene): packa ...
- 如何提高Lucene构建索引的速度
如何提高Lucene构建索引的速度 hans(汉斯) 2013-01-27 10:12 对于Lucene>=2.3:IndexWriter可以自行根据内存使用来释放缓存.调用writer.set ...
- Solr4.8.0源码分析(12)之Lucene的索引文件(5)
Solr4.8.0源码分析(12)之Lucene的索引文件(5) 1. 存储域数据文件(.fdt和.fdx) Solr4.8.0里面使用的fdt和fdx的格式是lucene4.1的.为了提升压缩比,S ...
- Solr4.8.0源码分析(11)之Lucene的索引文件(4)
Solr4.8.0源码分析(11)之Lucene的索引文件(4) 1. .dvd和.dvm文件 .dvm是存放了DocValue域的元数据,比如DocValue偏移量. .dvd则存放了DocValu ...
- Solr4.8.0源码分析(9)之Lucene的索引文件(2)
Solr4.8.0源码分析(9)之Lucene的索引文件(2) 一. Segments_N文件 一个索引对应一个目录,索引文件都存放在目录里面.Solr的索引文件存放在Solr/Home下的core/ ...
随机推荐
- 边工作边刷题:70天一遍leetcode: day 85
Find the Celebrity 要点: 这题从solution反过来想比较好:loop through n同时maintain一个candidate:如果cand认识某个i,那么modify c ...
- 给Testerhome测试小道消息做个硬广告
测试小道消息在荔枝FM上的粉丝马上就要超过1k了.还差17个我们就能够开通社区了.欢迎大家都来收听测试小道消息哈--更多精彩内容还希望大家下载荔枝fm.关注FM245329. 如果你还不了解测试小道消 ...
- Codeforces Zepto Code Rush 2014 -C - Dungeons and Candies
这题给的一个教训:Codeforces没有超时这个概念.本来以为1000*(1000+1)/2*10*10要超时的.结果我想多了. 这题由于k层都可能有关系,所以建一个图,每两个点之间连边,边权为n* ...
- RabbitMQ 一二事(2) - 工作队列使用
上篇文章讲了简单队列的使用,这其实就是RMQ给的demo,实际并没有什么用 本篇讲讲工作模式队列,也称之为任务队列 一个生产者发布了多条消息,消费者A可以接受消息,接受消息后该消息就消除,消费者B可以 ...
- 第11章 Windows线程池(2)_Win2008及以上的新线程池
11.2 Win2008以上的新线程池 (1)传统线程池的优缺点: ①传统Windows线程池调用简单,使用方便(有时只需调用一个API即可) ②这种简单也带来负面问题,如接口过于简单,无法更多去控制 ...
- 移动App崩溃测试用例设计
我们的日常生活中对移动设备越来越多的使用意味着移动App测试这个主题已成为需要考虑的一个无法避免的问题.根据最近的调查研究,用户难以容忍有bug的移动App. 移动App Bug的影响是用户体验差.A ...
- MongoDB学习(一)简介
本篇主要简单介绍一下MongoDB数据库. 一.简介 mongoDB是一个开源的,基于分布式的,面向文档存储的非关系型数据库.是非关系型数据库当中功能最丰富.最像关系数据库的. mongoDB由C++ ...
- js中的return,return true,return false小结
return 函数执行到这句时会终结,并返回调用函数,而且把表达式的值作为函数的结果返回 return false 可以防止默认的事件行为.例如,默认情况下点击一个<a>元素,页面会跳转 ...
- poj 1046 Color Me Less
Color Me Less Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 33007 Accepted: 16050 D ...
- SQL2008安装后激活方式以及提示评估期已过解决方法(转)
第一步:进入SQL2008配置工具中的安装中心第二步:再进入维护界面,选择版本升级第三步:进入产品密钥,输入密钥第四步:一直点下一步,直到升级完毕.SQL Server 2008 Developer: ...