IndexReader用来读取一个时间点的索引信息。
这也就是说如果IndexWriter对现有索引进行更新,IndexReader必须重新打开才能检索到这些更新。
对索引的检索就是通过这个抽象类提供的接口实现的。
疑问:索引文件可能很大,不可能把所有索引放进内存,创建一个IndexReader后为什么不能直接检索到之后索引更新的内容?
答:虽然不会把索引内容全部放进内存,但是Lucene索引是分段的,新的更新内容会形成新的段,不会对已有的索引文件进行更改,当前时间点创建的IndexReader应该会记录当前的所有分段信息(所有段文件)。使用IndexReader进行的检索只会在IndexReader记录的段里进行。
猜测追问:如果对已索引文件进行删除操作,不会更新已有段文件吗?
追问回答:不会改变已有分段文件,但会改变segments_{}文件,这个文件记录分段元数据,包括当前有哪些端,每段都删除了多少文件,删除了几次(delGen),然后,在为各个分段生成liv文件,用来记录当前段的可用文档;liv文件的格式:_${segGen}_{delGen}.liv
追问:虽然普通更新索引不会影响已打开的IndexReader,但是如果有Merge操作把以前的分段merge为一个新的段,那么会不会影响已打开的IndexReader呐?
答:不会,merge会生成新的段,但不会改变已有的段,虽然从用户视角看merge会删除一些分段,但是从操作系统角度看,一旦一个文件被打开一个 inputstream 也即打开了一个文件描述符,在内核中,此文件会保持 reference count,只要 reader 还 没有关闭,文件描述符还在,文件是不会真的被删除的,仅仅 reference count 减一。
疑问:Lucene创建索引的时候,flush操作和commit操作有什么联系和区别?
答:flush操作仅仅把当前内存中新创建的索引信息flush到系统缓存区(何时同步到硬盘由OS决定)上,每flush一次都会形成一个新的段,但是这些新的索引段对于IndexReader来说确是永不可见的(除非通过IndexWriter的getReader方法重新获取Reader),因为没有把segment_N也就是段信息的元数据一并flush;
而commit操作中包含flush操作和sync操作(保证写入硬盘,重量级操作),每commit一次都会形成新的段(生成新段看flush操作),另外还会将segment_N也就是段信息的元数据写入硬盘,这时新建的索引段对IndexReader才是可见的(当然要重新打开IndexReader),新索引的数据才能被检索到。
猜想与疑问:创建索引,需要打开一个IndexWriter,这时会获取到要使用的段信息(假设为A段),接下来创建的索引都放在A段,但是如果是多线程的情况呐,是每一个线程都使用一个新的段呐,还是所有的线程都使用同一个段A,直到IndexWriter 进行Commit操作?
答:每一个线程都负责一个独立的新段,每一个线程都有一个DocumentsWriterPerThread参数,也就是dwpt;dwpt初始化的时候会更新段信息,标记为新的段(详见IndexWriter的newSegmentName方法);dwpt在flush的时候会被加入flush列表,线程会将dwpt引用设为null,这样在该线程再次被使用的时候就会将dwpt参数重新初始化,以创建新的段。
追问:创建索引过程中,从DocumentWriterPerThreadPool获取ThreadState时,需要把ThreadState lock,根据代码显示,获取ThreadState过程的线程同步问题是由Synchronized关键字控制的,那么为什么还需要lock呐?
共同学习,写下你的评论
评论加载中...
作者其他优质文章