lucene索引过程

代码

public class Indexer {

    /**
     * 建立索引
     * @param filePath 需要建立索引的文件的存放路径
     * @throws IOException
     */
    public static void createIndex(String filePath) throws IOException {
        //在当前路径下创建一个叫indexDir的目录
        File indexDir = new File("./indexDir");
        //创建索引目录
        Directory directory = FSDirectory.open(indexDir);
        //创建一个分词器
        Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_36);
        //创建索引配置器
        IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LUCENE_36,analyzer);
        LogMergePolicy mergePolicy = new LogByteSizeMergePolicy();
        //设置segment添加文档(Document)时的合并频率
        //值较小,建立索引的速度就较慢
        //值较大,建立索引的速度就较快,>10适合批量建立索引
        mergePolicy.setMergeFactor(50);
        //设置segment最大合并文档(Document)数
        //值较小有利于追加索引的速度
        //值较大,适合批量建立索引和更快的搜索
        mergePolicy.setMaxMergeDocs(5000);
        //启用复合式索引文件格式,合并多个segment
        mergePolicy.setUseCompoundFile(true);
        indexWriterConfig.setMergePolicy(mergePolicy);
        //设置索引的打开模式
        indexWriterConfig.setOpenMode(OpenMode.CREATE_OR_APPEND);
        //创建索引器
        IndexWriter indexWriter = new IndexWriter(directory,indexWriterConfig);
        File fileDir = new File(filePath);
        for(File file : fileDir.listFiles()) {
            //Document是Lucene的文档结构,需要索引的对象都要转换为Document
            Document document = new Document();
            //文件名,可查询,分词,存储到索引库记录中
            document.add(new Field("name",getFileName(file),Store.YES,Index.ANALYZED));
            //文件路径,可查询,不分词,存储到索引库记录中
            document.add(new Field("path",file.getAbsolutePath(),Store.YES,Index.NOT_ANALYZED));
            //大文本内容,可查询,不存储,实际上可根据文件路径去找到真正的文本内容
            //document.add(new Field("content",new FileReader(file)));
            //小文本内容,可以存储到索引记录库
            document.add(new Field("content",getFileContent(file),Store.YES,Index.ANALYZED));
            //把文档添加到索引库
            indexWriter.addDocument(document);
        }
        //提交索引到磁盘上的索引库,关闭索引器
        indexWriter.close();
    }

名词解释

IndexWriter:索引器,负责创建和维护一条索引。在Lucene3.6版本,只推荐使用一个构造方法IndexWriter(Directory d,IndexWriterConfig conf),其他的构造方法都已经过时。所有关于IndexWriter的配置都是通过IndexWriterConfig来进行管理。

IndexWriterConfig:索引器配置类,管理所有有关索引器的配置。只有一个构造方法IndexWriterConfig(Version matchVersion,Analyzer analyzer),构造方法中的参数matchVersion是Lucene的版本,analyzer是分词器。

创建IndexWriter实例时,通过IndexWriterConfig来设置其相关配置:
1.Analyzer:分析器
2.matchVersion:所用Lucene的版本
3.ramBufferSizeMB:随机内存 默认为16M.
用于控制buffer索引文档的内存上限,如果buffer的索引文档个数到达该上限就写入硬盘。当然,一般来说越大索引速度越快
4.maxBufferedDocs:最大缓存文档数,该参数决定写入内存索引文档个数,到达该数目后就把该内存索引写入硬盘,生成一个新的索引segment文件。所以该参数也就是一个内存buffer,一般来说越大索引速度越快,默认不启用.
5.maxBufferedDeleteTerms:最大缓存删除词条数,当数量达到时会刷新现有的索引段。默认不启用.
6.readerTermsIndexDivisor:索引分词抽样因子,如果值设为N,那么在读取索引的时候每N*termIndexInterval个分词中抽取一个作为样本到RAM中,N值越大所占用的内存越小。
7.mergedSegmentWarmer:默认值为NULL
8.termIndexInterval:索引分词间隔,值越大IndexReader耗费的内存越小。这个参数决定着查询每个分词时所耗费的计算量。通常情况下它代表着在一个分词被定位到时,需要扫描的其他分词的最大值。默认值为32
9.delPolicy:索引删除策略,默认为KeepOnlyLastCommitDeletionPolicy,只保持最近一次的提交信息。
10.Commit:IndexCommit索引的变更如果要可见(如IndexDeletionPolicy,IndexReader中),必须提交COMMIT。每次提交都有一个唯一的segments_N文件与之关联。默认NULL
11.openMode:IndexWriter的打开模式,默认为CREATE_OR_APPEND,如果索引存在则打开进行扩充,否则新建。
12.Similarity:定义索引中分词的权重及打分情况。默认值为IndexSearcher.getDefaultSimilarity()
13.mergeScheduler:合并定时器,每个线程执行一个merge操作,默认为ConcurrentMergeScheduler
14.writeLockTimeout:写锁超时时间 默认为1000毫秒
15.indexingChain:索引链,定义文档是如何被索引的,默认采DocumentsWriterPerThread.defaultIndexingChain。有关索引链后面会详细介绍。
16.Codec:编码或解码一个倒排索引段,用于生成一个新的段。
17.infoStream:用于调试信息的管理,默认为InfoStream.getDefault()不记录任何调试信息。
18.mergePolicy:索引段的合并策略。默认为TieredMergePolicy根据每一层允许的段数合并大小相似的段。
19.flushPolicy:索引段数据刷新策略。即RAM内存中的数据何时刷新到物理结构中。默认为FlushByRamOrCountsPolicy,即根据RAM的使用情况,maxBufferedDocs和maxBufferedDeleteTerms的值来制定刷新策略。
20.readerPooling:实例化IndexReader是非常昂贵的操作,且它是一个线程安全的,跟索引目录是一一对应的,最好的方式就是用一个Pool去维护这些IndexReader:保证一个文件目录只有一个实例,且不同的IndexReader可以动态的组合。默认为false 不使用Pool.
21.indexerThreadPool:对documents建立索引的线程池,默认线程池中保持8个线程。
22.perThreadHardLimitMB:设置每个段内存使用的上限,如果达到该上限就会对段进行强制刷新。默认值为1945M。

在索引算法确定的情况下,最为影响Lucene索引速度有三个参数--IndexWriter中的 MergeFactor, MaxMergeDocs, RAMBufferSizeMB 。这些参数无非是控制内外存交换和索引合并频率,从而达到提高索引速度。当然这些参数的设置也得依照硬件条件灵活设置。

MaxBufferedDocs

该参数决定写入内存索引文档个数,到达该数目后就把该内存索引写入硬盘,生成一个新的索引segment文件。
所以该参数也就是一个内存buffer,一般来说越大索引速度越快。
MaxBufferedDocs这个参数默认是disabled的,因为Lucene中还用另外一个参数(RAMBufferSizeMB)控制这个bufffer的索引文档个数。
其实MaxBufferedDocs和RAMBufferSizeMB这两个参数是可以一起使用的,一起使用时只要有一个触发条件满足就写入硬盘,生成一个新的索引segment文件。

RAMBufferSizeMB

控制用于buffer索引文档的内存上限,如果buffer的索引文档个数到达该上限就写入硬盘。当然,一般来说也只越大索引速度越快。
当我们对文档大小不太确定时,这个参数就相当有用,不至于outofmemory error.

MergeFactor

这个参数是用于子索引(Segment)合并的。
Lucene中索引总体上是这样进行,索引现写到内存,触发一定限制条件后写入硬盘,生成一个独立的子索引-lucene中叫Segment。一般来说这些子索引需要合并成一个索引,也就是optimize(),否则会影响检索速度,而且也可能导致open too many files。
MergeFactor 这个参数就是控制当硬盘中有多少个子索引segments,我们就需要现把这些索引合并冲一个稍微大些的索引了。
MergeFactor这个不能设置太大,特别是当MaxBufferedDocs比较小时(segment 越多),否则会导致open too many files错误,甚至导致虚拟机外面出错。

段合并过程

LogMergePolicy 中,选择可以合并的段的基本逻辑是这样的:

  • !  选择的可以合并的段都是在硬盘上的,不再存在内存中的段,也不是像早期的版本一样

    每添加一个 Document 就生成一个段,然后进行内存中的段合并,然后再合并到硬盘中。

  • !  由于从内存中 flush 到硬盘上是按照设置的内存大小来 DocumentsWriter.ramBufferSize触发的,所以每个刚 flush 到硬盘上的段大小差不多,当然不排除中途改变内存设置,

      接下来的算法可以解决这个问题。
    
  • !  合并的过程是尽量按照合并几乎相同大小的段这一原则,只有大小相当的 mergeFacetor

      个段出现的时候,才合并成一个新的段。
    
  • !  在硬盘上的段基本应该是大段在前,小段在后,因为大段总是由小段合并而成的,当小

    段凑够 mergeFactor 个的时候,就合并成一个大段,小段就被删除了,然后新来的一定

    是新的小段。

  • !  比如mergeFactor=3,开始来的段大小为10M,当凑够310M的时候,0.cfs,1.cfs,2.cfs

    则合并成一个新的段 3.cfs,大小为 30M,然后再来 4.cfs, 5.cfs, 6.cfs,合并成 7.cfs,大小为 30M,然后再来 8.cfs, 9.cfs, a.cfs 合并成 b.cfs, 大小为 30M,这时候又凑够了 3 30M 的,合并成 90M c.cfs,然后又来 d.cfs, e.cfs, f.cfs 合并成 10.cfs,大小为 30M,然后 11.cfs 大小为 10M,这时候硬盘上的段为:c.cfs(90M) 10.cfs(30M),11.cfs(10M)。 

多线程并发索引
为了支持多线程并发索引,对每一个线程都有⼀个 DocumentsWriterThreadState, 其 为每⼀个线程根据 DocConsumer consumer 的索引链来创建每个线程的索引链 (XXXPerThread),来进⾏对文档的并发处理。 虽然对文档的处理过程可以并行,但是将⽂档写⼊入索引文件却必须串行进行,线程处理完的对象放入WaitQueue队列中,但是这存在一个问题:当⼤文档很⼤很大,处理的很慢很慢的时候,后来的线程⼆可能已经 处理了很多的⼩⽂档了,这些⽂档都是在 waitQueue 中,则占有了越来越多的内存,长此以往,有内存不够的危险。在 WaitQueue.add 最后调⽤用了 doPause()函数,当 waitingBytes 足够⼤的时候(为⽤用户指定的内存使⽤用量(RAMBufferSizeMB)的 10%),doPause 返回 true,于是后 来的线程⼆会进⼊入 wait 状态,不再处理另外的文档,⽽是等待线程⼀一处理⼤文档结束。 当线程⼀一处理⼤大⽂文档结束的时 候,调⽤用 notifyAll 唤醒等待他的线程。

索引结构

段文件(Segments File):segments.gen  segments_N,保存了此索引包含多少个段,每个段包含多少文档

锁文件( Lock File ):write.lock

段信息(Segments Info):.si

复合文件(Copound File):.cfs

字段信息(Fields):.fnm

字段索引(Fields Index):.fdx

字段数据(Field Data):.fdt

词典(Term Dictionary).tim

词索引(Term Index).tip

频率信息(Frequencies).doc

位置信息(Positions).pos

Payloads .pay

Norms .nvd,.nvm

每个文档的值(Per-Document Values).dvd,.dvm

词向量索引(Term Vector Index).tvx

词向量文件(Term Vector Documents).tvd

词向量字段(Term Vector Fields).tvf

删除文档 .del



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值