索引过程分析

Lucene3 中的索引过程设计很多对象,分别负责索引文档的不同部分的信息,同时也注重复用一些对象集。索引过程中涉及3种索引链:基本索引链、线程索引链、域索引链。

IndexWriter

IndexWriter 对象主要包括几方面的信息:
1.用于索引文档

成员 描述
Directory directory; 指向索引文件夹
Analyzer analyzer; 分析器
Similarity similarity = Similarity.getDefault(); 相似度计算,默认是tf-idf模型。
SegmentInfos segmentInfos = new SegmentInfos(); 保存段信息 segments_N。
IndexFileDeleter deleter; 用来管理索引文件。
Lock writeLock; 文件锁,每个索引文件夹只能打开一个 IndexWriter。
Set segmentsToOptimize = new HashSet(); 保存正在optimize的段信息,当调用optimize()时当前所有的段信息加入到此set,之后新生成的段不参与此次优化。

2.用于合并段

成员 描述
HashSet< SegmentInfo> mergingSegments = new HashSet< SegmentInfo>();
MergePolicy mergePolicy = new LogByteSizeMergePolicy(this);
MergeScheduler mergeScheduler = new ConcurrentMergeScheduler();
LinkedList< MergePolicy.OneMerge> pendingMerges = new LinkedList< MergePolicy.OneMerge>();
Set< MergePolicy.OneMerge> runningMerges = new HashSet< MergePolicy.OneMerge>();
List< MergePolicy.OneMerge> mergeExceptions = new ArrayList< MergePolicy.OneMerge>();
long mergeGen;

3.保持索引完整性、一致性和事务性

成员 描述
SegmentInfos rollbackSegmentInfos; 当IndexWriter对索引进行更改操作后,可以commit提交到文件中去,也可以rollback取消上次commit到此时的修改。
SegmentInfos localRollbackSegmentInfos; 主要用于将其他的索引文件合并到此文件夹的时候,为防止出错可回滚所保存的原来的段信息。

4.配置信息

成员 描述
long writeLockTimeout; 获得锁的超时时间
int termIndexInterval; 同 tii 和 tis 文件中的 indexInterval

创建IndexWriter对象

IndexWriter 构造函数中调用 init() 方法进行初始化。

  1. 构造索引链 IndexingChain。
    对文档的索引过程,不是由一个对象完成的,而是对象组合成一个处理链,链上的对象仅完成处理的一部分。
  2. 创建文件锁。
  3. 设置 rollbackSegmentInfos,用于失败回滚。
  4. 构造 DocumentsWriter 对象。
  5. 构造 IndexFileDeleter 对象。

索引链 IndexingChain

  1. 索引链的源头 DocConsumer
    • DocConsumer 其子类 DocFieldProcessor
  2. 索引域的处理
    • DocFieldConsumer 其子类 DocInverter
    • InvertedDocConsumer 其子类 TermsHash
    • TermsHashConsumer 其子类 TermVectorsTermsWriterFreqProxTermsWriter,分别负责写tvx、tvd、tvf信息和freq、prox信息。
    • InvertedDocEndConsumer 其子类 NormsWriter,负责写nrm信息。
  3. 存储域的处理
    • StoredFieldsWriter 负责写 fnm、fdt、fdx信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
DocConsumer / DocConsumerPerThread
--> code: DocFieldProcessor / DocFieldProcessorPerThread
--> DocFieldConsumer / DocFieldConsumerPerThread / DocFieldConsumerPerField
--> code: DocFieldConsumers / DocFieldConsumersPerThread / DocFieldConsumersPerField
--> code: DocInverter / DocInverterPerThread / DocInverterPerField
--> InvertedDocConsumer / InvertedDocConsumerPerThread / InvertedDocConsumerPerField
--> code: TermsHash / TermsHashPerThread / TermsHashPerField
--> TermsHashConsumer / TermsHashConsumerPerThread / TermsHashConsumerPerField
--> code: FreqProxTermsWriter / FreqProxTermsWriterPerThread / FreqProxTermsWriterPerField
--> code: TermVectorsTermsWriter / TermVectorsTermsWriterPerThread / TermVectorsTermsWriterPerField
--> InvertedDocEndConsumer / InvertedDocConsumerPerThread / InvertedDocConsumerPerField
--> code: NormsWriter / NormsWriterPerThread / NormsWriterPerField
--> code: StoredFieldsWriter / StoredFieldsWriterPerThread / StoredFieldsWriterPerField

DocumentsWriter

DocumentsWriter 主要包括以下几部分:

用于写索引文件

成员 描述
IndexWriter writer; 在IndexWriter初始化时,构造DocumentsWriter对象时传入
Directory directory;
Similarity similarity;
String segment; 当前的段名,每当flush的时候,将索引写入以此为名的段。
String docStoreSegment; 存储域索要写入的目标段。
int docStoreOffset; 存储域在目标端中的偏移量。
int nextDocID; 下一篇添加到此索引的文档ID,对于同一个索引文件夹,此变量唯一且同步访问。
DocConsumer consumer; 索引链的源头。

删除文档

成员 描述
BufferedDeletes deletesInRAM = new BufferedDeletes(); 用于一直缓存删除文档,flush的时候同步的push到deletesFlushed中去,之后再次新增的删除文档则继续停留在deletesInRAM中直到下次flush。
BufferedDeletes deletesFlushed = new BufferedDeletes(); 缓存删除文档,当flush的时候,将删除的文档写入索引文件。

缓存管理

为了提供索引速度,Lucene对很多数据进行了缓存,包括缓存分配和回收。

成员 描述
ArrayList freeCharBlocks = new ArrayList(); 用于缓存Term信息的空闲块。
ByteBlockAllocator byteBlockAllocator = new ByteBlockAllocator(); 通过ArrayList freeByteBlocks缓存文档号(doc id)、词频(freq)、位置(prox)信息的空闲块。
ArrayList freeIntBlocks = new ArrayList(); 用于存储某词的词频(freq)和位置(prox)分别在 byteBlocks 中的偏移量。
boolean bufferIsFull; 缓存是否满了,需要写入磁盘。
long numBytesAlloc; 分配的内存数量。
long numBytesUsed; 使用的内存数量。
long freeTrigge; 应该回收内存时的内存用量。
long freeLevel; 回收内存直到该内存用量。free down to
long ramBufferSize; 设定的内存用量。

多线程并发索引

为了支持多线程并发索引,每个线程都有一个 DocumentsWriterThreadState,根据 DocConsumer 的索引链来创建每个线程的索引链(xxPerThread)。
并发处理文档,串行写入索引

成员
DocumentsWriterThreadState[] threadStates;
HashMap threadBindings;
WaitQueue waitQueue;
long waitQueuePauseBytes;
long waitQueueResumeBytes;
  • Lucene中文档是添加的顺序编号的,多线程通过 synchronized 方法来讲 nextDocID 加一,这些在 DocumentsWriter.getThreadState()里面完成的。
  • Lucene中是按照文档的ID顺序从小到大的写到索引文件中去的,然而文档的处理速度不同,需要将后面已经处理完的文档放到 waitQueue 中,根据 nextWriteDocID 写入下个文件。
  • waitQueue 的内存问题,往里添加时内存不足则调用 doPause() 使得线程暂时不再处理文档。

标志位

成员 描述
int maxFieldLength 一篇文档中,一个域内可索引的最大 term 数。
int maxBufferedDeleteTerms 可缓存的最大的删除 term 数,超过则写到文件中。

DocumentsWriterThreadState

问题

  • 问题
    Lucene中只能有一个 IndexWriter 打开索引文件夹,同时生成文件锁write.lock
    而且IndexWriter.addDocument()是同步的(synchronized),这就导致多线程并不能起到提高性能的效果。
  • 解决
    为了支持索引中多线程处理文档,不使IndexWriter成为瓶颈,对每个线程都有一个相应的文档集处理对象DocumentsWriterThreadState,这就使得可以多线程并行处理文档。

IndexWriter中通过同步方法getThreadState获取各线程对应的DocumentsWriterThreadState,同时维护一个成员变量threadBindings(HashMap),键为线程对象,值为线程对应的 DocumentsWriterThreadState 对象。

DocumentsWriter
1
synchronized DocumentsWriterThreadState getThreadState(doc, delTerm);

处理文档(线程索引链)

每个DocumentsWriterThreadState对象都有一个文档及域处理对象DocFieldProcessorPerThread,它的成员函数processDocument()被调用来对文档及域进行处理。

1
DocWriter perDoc = state.consumer.processDocument();

DocFieldProcessorPerThread线程索引链的源头。

开始处理

DocFieldProcessorPerThread.processDocument()中会调用相关的对象对文档进行处理(DocFieldConsumerPerThreadStoredFieldWriterPerThread等)。
几乎各部分的处理对象都有startDocument()finishDocument()两个方法,一般在startDocument()方法中清理上篇被处理文档遗留的数据,在finishDocument中收集本次处理的结果数据并返回。

处理文档的每个域(域索引链)

一个线程可以处理多个文档,而几乎每个文档的域大致相同,因而复用域处理对象DocFieldProcessorPerField,对于每个域都有一个此对象。DocFieldProcessorPerField对象是域索引链的源头,和基本索引链、线程索引链不同的是,域索引链仅仅负责处理索引域而不负责存储域

DocFieldProcessorPerThread中维护着一个链式哈希表DocFieldProcessorPerField[] fieldHash方便更快查找域对应的处理对象,一个数组DocFieldProcessorPerField[] fields方便按域名字典顺序进行查找。DocFieldProcessorPerField并非每个域都有一个处理对象,而是对每一组相同名字的域有相同的处理对象。

  1. 不分词的域处理
    • 构造Token及其属性
    • 将Token加入倒排表
  2. 分词的域处理
    • 构建域的TokenStream
    • 循环构造Token及其属性,并加入倒排表

结束处理

存储域返回一个二进制的存储域缓存DocumentsWriter.DocWriter,索引域返回结果 null。最终返回到DocumentsWriter.finishDocument(),然后通过DocumentsWriter.updateDocument()条件判断是否要写到磁盘中。

1
2
StoredFieldsWriterPerThread.finishDocument();
DocInverterPerThread.finishDocument();


感谢:
http://www.cnblogs.com/forfuture1978/archive/2010/02/02/1661439.html