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 |
保存正在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() 方法进行初始化。
- 构造索引链 IndexingChain。
对文档的索引过程,不是由一个对象完成的,而是对象组合成一个处理链,链上的对象仅完成处理的一部分。 - 创建文件锁。
- 设置 rollbackSegmentInfos,用于失败回滚。
- 构造 DocumentsWriter 对象。
- 构造 IndexFileDeleter 对象。
索引链 IndexingChain
- 索引链的源头
DocConsumer
DocConsumer
其子类DocFieldProcessor
- 索引域的处理
DocFieldConsumer
其子类DocInverter
InvertedDocConsumer
其子类TermsHash
TermsHashConsumer
其子类TermVectorsTermsWriter
和FreqProxTermsWriter
,分别负责写tvx、tvd、tvf信息和freq、prox信息。InvertedDocEndConsumer
其子类NormsWriter
,负责写nrm信息。
- 存储域的处理
StoredFieldsWriter
负责写 fnm、fdt、fdx信息。
|
|
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 |
用于缓存Term信息的空闲块。 |
ByteBlockAllocator byteBlockAllocator = new ByteBlockAllocator(); | 通过ArrayList |
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 |
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 对象。
|
|
处理文档(线程索引链)
每个DocumentsWriterThreadState
对象都有一个文档及域处理对象DocFieldProcessorPerThread
,它的成员函数processDocument()
被调用来对文档及域进行处理。
DocFieldProcessorPerThread
是线程索引链的源头。
开始处理
在 DocFieldProcessorPerThread.processDocument()
中会调用相关的对象对文档进行处理(DocFieldConsumerPerThread
和StoredFieldWriterPerThread
等)。
几乎各部分的处理对象都有startDocument()
和finishDocument()
两个方法,一般在startDocument()
方法中清理上篇被处理文档遗留的数据,在finishDocument
中收集本次处理的结果数据并返回。
处理文档的每个域(域索引链)
一个线程可以处理多个文档,而几乎每个文档的域大致相同,因而复用域处理对象DocFieldProcessorPerField
,对于每个域都有一个此对象。DocFieldProcessorPerField
对象是域索引链的源头,和基本索引链、线程索引链不同的是,域索引链仅仅负责处理索引域而不负责存储域。
DocFieldProcessorPerThread
中维护着一个链式哈希表DocFieldProcessorPerField[] fieldHash
方便更快查找域对应的处理对象,一个数组DocFieldProcessorPerField[] fields
方便按域名字典顺序进行查找。DocFieldProcessorPerField
并非每个域都有一个处理对象,而是对每一组相同名字的域有相同的处理对象。
- 不分词的域处理
- 构造Token及其属性
- 将Token加入倒排表
- 分词的域处理
- 构建域的TokenStream
- 循环构造Token及其属性,并加入倒排表
结束处理
存储域返回一个二进制的存储域缓存DocumentsWriter.DocWriter
,索引域返回结果 null。最终返回到DocumentsWriter.finishDocument()
,然后通过DocumentsWriter.updateDocument()
条件判断是否要写到磁盘中。
感谢:
http://www.cnblogs.com/forfuture1978/archive/2010/02/02/1661439.html