lucene文档之DocumentsWriter

ucene将文档加入DocumentsWriter代码调用层次结构如下:

DocumentsWriter.updateDocument(Document doc, Analyzer analyzer, Term delTerm)
-->(1) DocumentsWriterThreadState state = getThreadState(doc, delTerm);
-->(2) DocWriter perDoc = state.consumer.processDocument();
-->(3) finishDocument(state, perDoc);

DocumentsWriter对象主要包含以下几部分:

* 用于写索引文件
  IndexWriter writer;
  Directory directory;
  Similarity similarity:分词器
  String segment:当前的段名,每当flush的时候,将索引写入以此为名称的段。
  IndexWriter.doFlushInternal()

--> String segment = docWriter.getSegment();//return segment
      --> newSegment = new SegmentInfo(segment,……);
           --> docWriter.createCompoundFile(segment);//根据segment创建cfs文件。

String docStoreSegment:存储域所要写入的目标段。(在索引文件格式一文中已经详细描述)
  int docStoreOffset:存储域在目标段中的偏移量。

int nextDocID:下一篇添加到此索引的文档ID号,对于同一个索引文件夹,此变量唯一,且同步访问。
DocConsumer consumer; 这是整个索引过程的核心,是IndexChain整个索引链的源头。

基本索引链:
对于一篇文档的索引过程,不是由一个对象来完成的,而是用对象组合的方式形成的一个处理链,链上的每个对象仅
理索引过程的一部分,称为索引链,由于后面还有其他的索引链,所以此处的索引链我称为基本索引链。

DocConsumer consumer 类型为DocFieldProcessor,是整个索引链的源头,包含如下部分:
* 对索引域的处理
   DocFieldConsumer consumer 类型为DocInverter,包含如下部分
          InvertedDocConsumer consumer类型为TermsHash,包含如下部分
           TermsHashConsumer consumer类型为FreqProxTermsWriter,负责写freq, pro
            TermsHash nextTermsHash
             TermsHashConsumer consumer类型为TermVectorsTermsWriter,负责tvd, tvf信息
               InvertedDocEndConsumer endConsumer 类型为NormsWriter,负责写nrm信息

* 对存储域的处理
   FieldInfos fieldInfos = new FieldInfos();
   StoredFieldsWriter fieldsWriter负责写fnm, fdt, fdx信息
* 删除文档
  BufferedDeletes deletesInRAM = new BufferedDeletes();
  BufferedDeletes deletesFlushed = new BufferedDeletes();

类BufferedDeletes包含了一下的成员变量:
* HashMap terms = new HashMap();删除的词(Term)
* HashMap queries = new HashMap();删除的查询(Query)
* List docIDs = new ArrayList();删除的文档ID
* long bytesUsed:用于判断是否应该对删除的文档写入索引文件。

由此可见,文档的删除主要有三种方式:
* IndexWriter.deleteDocuments(Term term):所有包含此词的文档都会被删除。
* IndexWriter.deleteDocuments(Query query):所有能满足此查询的文档都会被删除。
* IndexReader.deleteDocument(int docNum):删除此文档ID

删除文档既可以用reader进行删除,也可以用writer进行删除,不同的是,reader进行删除后,此reader马上能够生
而用writer删除后,会被缓存在deletesInRAM及deletesFlushed中,只有写入到索引文件中,当reader再次打开的
才能够看到。

那deletesInRAM和deletesFlushed各有什么用处呢?
此版本的Lucene对文档的删除是支持多线程的,当用IndexWriter删除文档的时候,都是缓存在deletesInRAM中
flush,才将删除的文档写入到索引文件中去,我们知道flush是需要一段时间的,那么在flush的过程中,另一个线
档删除怎么办呢?

一般过程是这个样子的,当flush的时候,首先在同步(synchornized)的方法pushDeletes中,将deletesInRAM全部
deletesFlushed中,然后将deletesInRAM清空,退出同步方法,于是flush的线程就向索引文件写deletesFlushed
删除文档的过程,而与此同时其他线程新删除的文档则添加到新的deletesInRAM中去,直到下次flush才写入索引文件

* 缓存管理
 为了提高索引的速度,Lucene对很多的数据进行了缓存,使一起写入磁盘,然而缓存需要管理,何时分配,何时回收,何时写入磁盘都需要考虑。
 ArrayList freeCharBlocks = new ArrayList();将用于缓存词(Term)信息的空闲块
 ArrayList freeByteBlocks = new ArrayList();将用于缓存文档号(doc id)及词频(freq),位(prox)信息的空闲块。
  ArrayList freeIntBlocks = new ArrayList();将存储某词的词频(freq)和位置(prox)分别在
byteBlocks中的偏移量
  boolean bufferIsFull;用来判断缓存是否满了,如果满了,则应该写入磁盘
  long numBytesAlloc;分配的内存数量
  long numBytesUsed;使用的内存数量
  long freeTrigger;应该开始回收内存时的内存用量。
  long freeLevel;回收内存应该回收到的内存用量。
 long ramBufferSize;用户设定的内存用量。

缓存用量之间的关系如下:
DocumentsWriter.setRAMBufferSizeMB(double mb){
ramBufferSize = (long) (mb*1024*1024);//用户设定的内存用量,当使用内存大于此时,开始写入磁盘
waitQueuePauseBytes = (long) (ramBufferSize*0.1);
waitQueueResumeBytes = (long) (ramBufferSize*0.05);
freeTrigger = (long) (1.05 * ramBufferSize);//当分配的内存到达105%的时候开始释放freeBlocks中的内存
freeLevel = (long) (0.95 * ramBufferSize);//一直释放到95%
}

DocumentsWriter.balanceRAM(){
if (numBytesAlloc+deletesRAMUsed > freeTrigger) {
//当分配的内存加删除文档所占用的内存大于105%的时候,开始释放内存
while(numBytesAlloc+deletesRAMUsed > freeLevel) {
//一直进行释放,直到95%
//释放free blocks
byteBlockAllocator.freeByteBlocks.remove(byteBlockAllocator.freeByteBlocks.size()-1);
numBytesAlloc -= BYTE_BLOCK_SIZE;
freeCharBlocks.remove(freeCharBlocks.size()-1);
numBytesAlloc -= CHAR_BLOCK_SIZE * CHAR_NUM_BYTE;
freeIntBlocks.remove(freeIntBlocks.size()-1);
numBytesAlloc -= INT_BLOCK_SIZE * INT_NUM_BYTE;
}
} else {

if (numBytesUsed+deletesRAMUsed > ramBufferSize){
//当使用的内存加删除文档占有的内存大于用户指定的内存时,可以写入磁盘
bufferIsFull = true;
}
}
}

当判断是否应该写入磁盘时:
* 如果使用的内存大于用户指定内存时,bufferIsFull = true
* 当使用的内存加删除文档所占的内存加正在写入的删除文档所占的内存大于用户指定内存时
deletesInRAM.bytesUsed + deletesFlushed.bytesUsed + numBytesUsed) >= ramBufferSize
* 当删除的文档数目大于maxBufferedDeleteTerms时
DocumentsWriter.timeToFlushDeletes(){
return (bufferIsFull || deletesFull()) && setFlushPending();
}

DocumentsWriter.deletesFull(){
return (ramBufferSize != IndexWriter.DISABLE_AUTO_FLUSH &&
(deletesInRAM.bytesUsed + deletesFlushed.bytesUsed + numBytesUsed) >= ramBufferSize) ||
(maxBufferedDeleteTerms != IndexWriter.DISABLE_AUTO_FLUSH &&
((deletesInRAM.size() + deletesFlushed.size()) >= maxBufferedDeleteTerms));
}

多线程并发索引
  为了支持多线程并发索引,对每一个线程都有一个DocumentsWriterThreadState,其为每一
个线程根据DocConsumer consumer的索引链来创建每个线程的索引链(XXXPerThread),来
进行对文档的并发处理。
 DocumentsWriterThreadState[] threadStates = new DocumentsWriterThreadState[0];
  HashMap threadBindings = new HashMap();
 虽然对文档的处理过程可以并行,但是将文档写入索引文件却必须串行进行,串行写入的代码
在DocumentsWriter.finishDocument中
 WaitQueue waitQueue = new WaitQueue()
  long waitQueuePauseBytes
  long waitQueueResumeBytes

在Lucene中,文档是按添加的顺序编号的,DocumentsWriter中的nextDocID就是记录下一个添加的文档id。 当Lucene支持
持多线程的时候,就必须要有一个synchornized方法来付给文档id并且将nextDocID加一,这些是在DocumentsWriter.getThreadState这个函数里面做的。

虽然给文档付ID没有问题了。但是由Lucene索引文件格式我们知道,文档是要按照ID的顺序从小到大写到索引文件
然而不同的文档处理速度不同,当一个先来的线程一处理一篇需要很长时间的大文档时,另一个后来的线程二可能已
了很多小的文档了,但是这些后来小文档的ID号都大于第一个线程所处理的大文档,因而不能马上写到索引文件中去
而是放到waitQueue中,仅仅当大文档处理完了之后才写入索引文件。

waitQueue中有一个变量nextWriteDocID表示下一个可以写入文件的ID,当付给大文档ID=4时,则nextWriteDocID
为4,虽然后来的小文档5,6,7,8等都已处理结束,但是如下代码,
WaitQueue.add(){
if (doc.docID == nextWriteDocID){
…………
} else {
waiting[loc] = doc;

waitingBytes += doc.sizeInBytes();
}
doPause()
}

但是这存在一个问题:当大文档很大很大,处理的很慢很慢的时候,后来的线程二可能已经处理了很多的小文档了
档都是在waitQueue中,则占有了越来越多的内存,长此以往,有内存不够的危险。
因而在finishDocuments里面,在WaitQueue.add最后调用了doPause()函数
DocumentsWriter.finishDocument(){
doPause = waitQueue.add(docWriter);

if (doPause)
waitForWaitQueue();
notifyAll();//lucene.net采用System.Threading.Monitor.PulseAll(this)通知每个线程
}
WaitQueue.doPause() {
return waitingBytes > waitQueuePauseBytes;
}

当waitingBytes足够大的时候(为用户指定的内存使用量的10%),doPause返回true,于是后来的线程二会进入wait
不再处理另外的文档,而是等待线程一处理大文档结束。

当线程一处理大文档结束的时候,调用notifyAll唤醒等待他的线程。
DocumentsWriter.waitForWaitQueue() {
do {
try {
wait();
} catch (InterruptedException ie) {
throw new ThreadInterruptedException(ie);
}
} while (!waitQueue.doResume());
}

WaitQueue.doResume() {
return waitingBytes <= waitQueueResumeBytes;
}

当waitingBytes足够小的时候,doResume返回true, 则线程二不用再wait了,可以继续处理另外的文档。
• 一些标志位
? int maxFieldLength:一篇文档中,一个域内可索引的最大的词(Term)数。
? int maxBufferedDeleteTerms:可缓存的最大的删除词(Term)数。当大于这个数的时候,就要
写到文件中了。
此过程又包含如下三个子过程:

1、得到当前线程对应的文档集处理对象(DocumentsWriterThreadState)

2、用得到的文档集处理对象(DocumentsWriterThreadState)处理文档

3、用DocumentsWriter.finishDocument结束本次文档添加

时间: 2024-10-19 16:18:42

lucene文档之DocumentsWriter的相关文章

Lucene文档

一.数据概论 我们生活中的数据总体分为两种:结构化数据和非结构化数据. 结构化数据:指具有固定格式或有限长度的数据,如数据库,元数据等. 非结构化数据:指不定长或无固定格式的数据,如邮件,word文档等.非结构化数据又一种叫法叫全文数据. 当然有的地方还会提到第三种,半结构化数据,如XML,HTML等,当根据需要可按结构化数据来处理,也可抽取出纯文本按非结构化数据来处理. 1.1.1.   数据的搜索 对结构化数据的搜索:如对数据库的搜索,用SQL语句.再如对元数据的搜索,如利用windows搜

Solr开发参考文档(转)

Solr开发文档 Solr 是一种可供企业使用的.基于 Lucene 的搜索服务器,它支持层面搜索.命中醒目显示和多种输出格式.在这篇文章中,将介绍 Solr 并展示如何轻松地将其表现优异的全文本搜索功能加入到 Web 应用程序中. 开发环境: System:Windows WebBrowser:IE6+.Firefox3+ JDK:1.6+ JavaEE Server:tomcat5.0.2.8.tomcat6 IDE:eclipse.MyEclipse 8 开发依赖库: JavaEE 5.s

使用Lucene对预处理后的文档进行创建索引(可执行)

时间: 2015/3/18 杨鑫newlife 对于文档的预处理后.就要開始使用Lucene来处理相关的内容了. 这里使用的Lucene的过程例如以下: 首先要为处理对象机那里索引 二是构建查询对象 三是在索引中查找 这里的代码是处理创建索引的部分 代码: package ch2.lucenedemo.process; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import jav

基础:从概念理解Lucene的Index(索引)文档模型

转:http://blog.csdn.net/duck_genuine/article/details/6053430 目录(?)[+] Lucene主要有两种文档模型:Document和Field,一个Document可能包含若干个Field. 每一个Field有不同的策略: 1.被索引 or not,将该字段(Field)经过分析(Analyisi)后,加入索引中,并不是原文 . 2.如果被索引,可选择是否保存“term vector”(向量),用于相似检索. 3.可选择是否存储(store

使用Lucene实现多个文档关键词检索demo(一)

在进行demo前先到http://www.ibm.com/developerworks/cn/java/j-lo-lucene1/了解关于lucene的一些基本概念,忽略其中的代码实例,因为年代久远,而我的这篇文档正是补充其中代码部分. 了解了基本概念后,接下来就可以开始完成demo了. 首先在http://www.apache.org/dyn/closer.cgi/lucene/java/4.10.0下载lucene包,这里我使用的是最新的4.10版,由于最新版与网上其他lucene使用dem

使用Lucene实现多个文档关键词检索demo(二)

上次在使用Lucene建立索引时使用的时自带的StandAnalyzer分词器,而这个分词器在对中文进行分词时只是机械的按字进行划分,因此使用它lucene就不能很好的对中文索引,也就不能实现对中文关键词的检索了,因此其实上次的实践只能对英文进行. 为了解决这个问题,可以使用IKAnalyzer,它是以开源项目Lucene为应用主体的,结合词典分词和文法分析算法的中文分词组件.它支持中英文等分词. 接下来就用它来改善检索功能,首先是下载IKAnalyzer开发包,我将开发包上传到了这里密钥:7j

用lucene.net根据关键字检索本地word文档

目前在做一个winform小软件,其中有一个功能是能根据关键字检索本地保存的word文档.第一次是用com读取word方式(见上一篇文章),先遍历文件夹下的word文档,读取每个文档时循环关键字查找,结果可想而知效率很慢.检索结果是一条接一条显示出来的o(>_<)o ~~.连菜鸟级别的自己看到这效率都觉得很无语.然后想到计算机的本地搜索及google,百度搜索引擎,它们能做到在海量文件中快速搜到匹配某些关键字的文件,应该是运用其它比较先进成熟的技术来实现.于是上网搜了好多资料,发现有一种叫lu

Lucene in action 笔记 term vector——针对特定field建立的词频向量空间,用cos计算针对该field的文档相似度

摘自:http://blog.csdn.net/fxjtoday/article/details/5142661 Leveraging term vectors所谓term vector, 就是对于documents的某一field,如title,body这种文本类型的, 建立词频的多维向量空间.每一个词就是一维, 这维的值就是这个词在这个field中的频率. 如果你要使用term vectors, 就要在indexing的时候对该field打开term vectors的选项: Field op

Lucene是如何理解文档的 & 文档类型(Types)是如何被实现的

Lucene是如何理解文档的 在Lucene中,一份文档(Document)由一系列简单的字段-值(field-value)对组成.一个字段必须有值,同时允许包含多值.同样的,一个单一的字符串在分析处理过程中可能被转换成多个值.Lucene不关心值到底是字符串.数字还是日期--所有的值都以不可理解的比特值(opaque)对待. 当我们在Lucene中索引一份文档时,值和字段在反向索引(inverted index)中被关联起来.可选项的是,是否将原始值存储起来以便今后使用,存储后的值是不可更改的