zoukankan      html  css  js  c++  java
  • Lucene学习笔记: 五,Lucene搜索过程解析

    一、Lucene搜索过程总论

    搜索的过程总的来说就是将词典及倒排表信息从索引中读出来,根据用户输入的查询语句合并倒排表,得到结果文档集并对文档进行打分的过程。

    其可用如下图示:

    searchprocess_thumb6

    总共包括以下几个过程:

    1. IndexReader打开索引文件,读取并打开指向索引文件的流。
    2. 用户输入查询语句
    3. 将查询语句转换为查询对象Query对象树
    4. 构造Weight对象树,用于计算词的权重Term Weight,也即计算打分公式中与仅与搜索语句相关与文档无关的部分(红色部分)。
    5. 构造Scorer对象树,用于计算打分(TermScorer.score())。
    6. 在构造Scorer对象树的过程中,其叶子节点的TermScorer会将词典和倒排表从索引中读出来。
    7. 构造SumScorer对象树,其是为了方便合并倒排表对Scorer对象树的从新组织,它的叶子节点仍为TermScorer,包含词典和倒排表。此步将倒排表合并后得到结果文档集,并对结果文档计算打分公式中的蓝色部分。打分公式中的求和符合,并非简单的相加,而是根据子查询倒排表的合并方式(与或非)来对子查询的打分求和,计算出父查询的打分。
    8. 将收集的结果集合及打分返回给用户。

    二、Lucene搜索详细过程

    2.1、打开IndexReader指向索引文件夹

    代码为:

    IndexReader reader = IndexReader.open(FSDirectory.open(indexDir));

    其实是调用了DirectoryReader.open(Directory, IndexDeletionPolicy, IndexCommit, boolean, int) 函数,其主要作用是生成一个SegmentInfos.FindSegmentsFile对象,并用它来找到此索引文件中所有的段,并打开这些段。

    SegmentInfos.FindSegmentsFile.run(IndexCommit commit)主要做以下事情:

    2.1.1、找到最新的segment_N文件

    • 由于segment_N是整个索引中总的元数据,因而正确的选择segment_N更加重要。
    • 然而有时候为了使得索引能够保存在另外的存储系统上,有时候需要用NFS mount一个远程的磁盘来存放索引,然而NFS为了提高性能,在本地有Cache,因而有可能使得此次打开的索引不是另外的writer写入的最新信息,所以在此处用了双保险。
    • 一方面,列出所有的segment_N,并取出其中的最大的N,设为genA

    String[] files = directory.listAll();

    long genA = getCurrentSegmentGeneration(files);

    long getCurrentSegmentGeneration(String[] files) {

        long max = -1;

        for (int i = 0; i < files.length; i++) {

          String file = files[i];

          if (file.startsWith(IndexFileNames.SEGMENTS) //"segments_N"

              && !file.equals(IndexFileNames.SEGMENTS_GEN)) { //"segments.gen"

            long gen = generationFromSegmentsFileName(file);

            if (gen > max) {

              max = gen;

            }

          }

        }

        return max;

      }

    • 另一方面,打开segment.gen文件,从中读出N,设为genB

    IndexInput genInput = directory.openInput(IndexFileNames.SEGMENTS_GEN);

    int version = genInput.readInt();

    long gen0 = genInput.readLong();

    long gen1 = genInput.readLong();

    if (gen0 == gen1) {

        genB = gen0;

    }

    • 在genA和genB中去较大者,为gen,并用此gen构造要打开的segments_N的文件名

    if (genA > genB)

        gen = genA;

    else

        gen = genB;

    String segmentFileName = IndexFileNames.fileNameFromGeneration(IndexFileNames.SEGMENTS, "", gen); //segmentFileName    "segments_4"   

    2.1.2、通过segment_N文件中保存的各个段的信息打开各个段

    • 从segment_N中读出段的元数据信息,生成SegmentInfos

    2.1.3、得到的IndexReader对象如下

    reader    ReadOnlyDirectoryReader  (id=466)    
        closed    false    
        deletionPolicy    null 

        //索引文件夹   
        directory    SimpleFSDirectory  (id=31)    
            checked    false    
            chunkSize    104857600    
            directory    File  (id=487)    
                path    "D:\lucene-3.0.0\TestSearch\index"    
                prefixLength    3    
            isOpen    true    
            lockFactory    NativeFSLockFactory  (id=488)    
        hasChanges    false    
        hasDeletions    false    
        maxDoc    12    
        normsCache    HashMap<K,V>  (id=483)    
        numDocs    -1    
        readOnly    true    
        refCount    1    
        rollbackHasChanges    false    
        rollbackSegmentInfos    null   

        //段元数据信息 
        segmentInfos    SegmentInfos  (id=457)     
            elementCount    3    
            elementData    Object[10]  (id=532)    
                [0]    SegmentInfo  (id=464)    
                    delCount    0    
                    delGen    -1    
                    diagnostics    HashMap<K,V>  (id=537)    
                    dir    SimpleFSDirectory  (id=31)    
                    docCount    4    
                    docStoreIsCompoundFile    false    
                    docStoreOffset    -1    
                    docStoreSegment    "_0"    
                    files    null    
                    hasProx    true    
                    hasSingleNormFile    true    
                    isCompoundFile    1    
                    name    "_0"    
                    normGen    null    
                    preLockless    false    
                    sizeInBytes    -1    
                [1]    SegmentInfo  (id=517)    
                    delCount    0    
                    delGen    -1    
                    diagnostics    HashMap<K,V>  (id=542)    
                    dir    SimpleFSDirectory  (id=31)    
                    docCount    4    
                    docStoreIsCompoundFile    false    
                    docStoreOffset    -1    
                    docStoreSegment    "_1"    
                    files    null    
                    hasProx    true    
                    hasSingleNormFile    true    
                    isCompoundFile    1    
                    name    "_1"    
                    normGen    null    
                    preLockless    false    
                    sizeInBytes    -1    
                [2]    SegmentInfo  (id=470)    
                    delCount    0    
                    delGen    -1    
                    diagnostics    HashMap<K,V>  (id=547)    
                    dir    SimpleFSDirectory  (id=31)    
                    docCount    4    
                    docStoreIsCompoundFile    false    
                    docStoreOffset    -1    
                    docStoreSegment    "_2"    
                    files    null    
                    hasProx    true    
                    hasSingleNormFile    true    
                    isCompoundFile    1    
                    name    "_2"    
                    normGen    null    
                    preLockless    false    
                    sizeInBytes    -1     
            generation    4    
            lastGeneration    4    
            modCount    4    
            pendingSegnOutput    null    
            userData    HashMap<K,V>  (id=533)    
            version    1268193441675    
        segmentInfosStart    null    
        stale    false    
        starts    int[4]  (id=484) 

        //每个段的Reader 
        subReaders    SegmentReader[3]  (id=467)    
            [0]    ReadOnlySegmentReader  (id=492)    
                closed    false    
                core    SegmentReader$CoreReaders  (id=495)    
                    cfsDir    CompoundFileReader  (id=552)    
                    cfsReader    CompoundFileReader  (id=552)    
                    dir    SimpleFSDirectory  (id=31)    
                    fieldInfos    FieldInfos  (id=553)    
                    fieldsReaderOrig    FieldsReader  (id=554)    
                    freqStream    CompoundFileReader$CSIndexInput  (id=555)    
                    proxStream    CompoundFileReader$CSIndexInput  (id=556)    
                    readBufferSize    1024    
                    ref    SegmentReader$Ref  (id=557)    
                    segment    "_0"    
                    storeCFSReader    null    
                    termsIndexDivisor    1    
                    termVectorsReaderOrig    null    
                    tis    TermInfosReader  (id=558)    
                    tisNoIndex    null    
                deletedDocs    null    
                deletedDocsDirty    false    
                deletedDocsRef    null    
                fieldsReaderLocal    SegmentReader$FieldsReaderLocal  (id=496)    
                hasChanges    false    
                norms    HashMap<K,V>  (id=500)    
                normsDirty    false    
                pendingDeleteCount    0    
                readBufferSize    1024    
                readOnly    true    
                refCount    1    
                rollbackDeletedDocsDirty    false    
                rollbackHasChanges    false    
                rollbackNormsDirty    false    
                rollbackPendingDeleteCount    0    
                si    SegmentInfo  (id=464)    
                singleNormRef    SegmentReader$Ref  (id=504)    
                singleNormStream    CompoundFileReader$CSIndexInput  (id=506)    
                termVectorsLocal    CloseableThreadLocal<T>  (id=508)    
            [1]    ReadOnlySegmentReader  (id=493)    
                closed    false    
                core    SegmentReader$CoreReaders  (id=511)    
                    cfsDir    CompoundFileReader  (id=561)    
                    cfsReader    CompoundFileReader  (id=561)    
                    dir    SimpleFSDirectory  (id=31)    
                    fieldInfos    FieldInfos  (id=562)    
                    fieldsReaderOrig    FieldsReader  (id=563)    
                    freqStream    CompoundFileReader$CSIndexInput  (id=564)    
                    proxStream    CompoundFileReader$CSIndexInput  (id=565)    
                    readBufferSize    1024    
                    ref    SegmentReader$Ref  (id=566)    
                    segment    "_1"    
                    storeCFSReader    null    
                    termsIndexDivisor    1    
                    termVectorsReaderOrig    null    
                    tis    TermInfosReader  (id=567)    
                    tisNoIndex    null    
                deletedDocs    null    
                deletedDocsDirty    false    
                deletedDocsRef    null    
                fieldsReaderLocal    SegmentReader$FieldsReaderLocal  (id=512)    
                hasChanges    false    
                norms    HashMap<K,V>  (id=514)    
                normsDirty    false    
                pendingDeleteCount    0    
                readBufferSize    1024    
                readOnly    true    
                refCount    1    
                rollbackDeletedDocsDirty    false    
                rollbackHasChanges    false    
                rollbackNormsDirty    false    
                rollbackPendingDeleteCount    0    
                si    SegmentInfo  (id=517)    
                singleNormRef    SegmentReader$Ref  (id=519)    
                singleNormStream    CompoundFileReader$CSIndexInput  (id=520)    
                termVectorsLocal    CloseableThreadLocal<T>  (id=521)    
            [2]    ReadOnlySegmentReader  (id=471)    
                closed    false    
                core    SegmentReader$CoreReaders  (id=475)    
                    cfsDir    CompoundFileReader  (id=476)    
                    cfsReader    CompoundFileReader  (id=476)    
                    dir    SimpleFSDirectory  (id=31)    
                    fieldInfos    FieldInfos  (id=480)    
                    fieldsReaderOrig    FieldsReader  (id=570)    
                    freqStream    CompoundFileReader$CSIndexInput  (id=571)    
                    proxStream    CompoundFileReader$CSIndexInput  (id=572)    
                    readBufferSize    1024    
                    ref    SegmentReader$Ref  (id=573)    
                    segment    "_2"    
                    storeCFSReader    null    
                    termsIndexDivisor    1    
                    termVectorsReaderOrig    null    
                    tis    TermInfosReader  (id=574)    
                    tisNoIndex    null    
                deletedDocs    null    
                deletedDocsDirty    false    
                deletedDocsRef    null    
                fieldsReaderLocal    SegmentReader$FieldsReaderLocal  (id=524)    
                hasChanges    false    
                norms    HashMap<K,V>  (id=525)    
                normsDirty    false    
                pendingDeleteCount    0    
                readBufferSize    1024    
                readOnly    true    
                refCount    1    
                rollbackDeletedDocsDirty    false    
                rollbackHasChanges    false    
                rollbackNormsDirty    false    
                rollbackPendingDeleteCount    0    
                si    SegmentInfo  (id=470)    
                singleNormRef    SegmentReader$Ref  (id=527)    
                singleNormStream    CompoundFileReader$CSIndexInput  (id=528)    
                termVectorsLocal    CloseableThreadLocal<T>  (id=530)    
        synced    HashSet<E>  (id=485)    
        termInfosIndexDivisor    1    
        writeLock    null    
        writer    null   

    从上面的过程来看,IndexReader有以下几个特性:

    • 段元数据信息已经被读入到内存中,因而索引文件夹中因为新添加文档而新增加的段对已经打开的reader是不可见的。
    • .del文件已经读入内存,因而其他的reader或者writer删除的文档对打开的reader也是不可见的。
    • 打开的reader已经有inputstream指向cfs文件,从段合并的过程我们知道,一个段文件从生成起就不会改变,新添加的文档都在新的段中,删除的文档都在.del中,段之间的合并是生成新的段,而不会改变旧的段,只不过在段的合并过程中,会将旧的段文件删除,这没有问题,因为从操作系统的角度来讲,一旦一个文件被打开一个inputstream也即打开了一个文件描述符,在内核中,此文件会保持reference count,只要reader还没有关闭,文件描述符还在,文件是不会被删除的,仅仅reference count减一。
    • 以上三点保证了IndexReader的snapshot的性质,也即一个IndexReader打开一个索引,就好像对此索引照了一张像,无论背后索引如何改变,此IndexReader在被重新打开之前,看到的信息总是相同的。
    • 严格的来讲,Lucene的文档号仅仅对打开的某个reader有效,当索引发生了变化,再打开另外一个reader的时候,前面reader的文档0就不一定是后面reader的文档0了,因而我们进行查询的时候,从结果中得到文档号的时候,一定要在reader关闭之前应用,从存储域中得到真正能够唯一标识你的业务逻辑中的文档的信息,如url,md5等等,一旦reader关闭了,则文档号已经无意义,如果用其他的reader查询这些文档号,得到的可能是不期望的文档。

    2.2、打开IndexSearcher

    代码为:

    IndexSearcher searcher = new IndexSearcher(reader);

    其过程非常简单:

    private IndexSearcher(IndexReader r, boolean closeReader) {

      reader = r;

      //当关闭searcher的时候,是否关闭其reader

      this.closeReader = closeReader;

      //对文档号进行编号

      List<IndexReader> subReadersList = new ArrayList<IndexReader>();

      gatherSubReaders(subReadersList, reader);

      subReaders = subReadersList.toArray(new IndexReader[subReadersList.size()]);

      docStarts = new int[subReaders.length];

      int maxDoc = 0;

      for (int i = 0; i < subReaders.length; i++) {

        docStarts[i] = maxDoc;

        maxDoc += subReaders[i].maxDoc();

      }

    }

    IndexSearcher表面上看起来好像仅仅是reader的一个封装,它的很多函数都是直接调用reader的相应函数,如:int docFreq(Term term),Document doc(int i),int maxDoc()。然而它提供了两个非常重要的函数:

    因而在某些应用之中,只想得到某个词的倒排表的时候,最好不要用IndexSearcher,而直接用IndexReader.termDocs(Term term),则省去了打分的计算。

    2.3、QueryParser解析查询语句生成查询对象

    代码为:

    QueryParser parser = new QueryParser(Version.LUCENE_CURRENT, "contents", new StandardAnalyzer(Version.LUCENE_CURRENT));

    Query query = parser.parse("+(+apple* -boy) (cat* dog) -(eat~ foods)");

    此过程相对复杂,涉及JavaCC,QueryParser,分词器,查询语法等,本章不会详细论述,会在后面的章节中一一说明。

    此处唯一要说明的是,根据查询语句生成的是一个Query树,这棵树很重要,并且会生成其他的树,一直贯穿整个索引过程。

    对于Query对象有以下说明:

    • BooleanQuery即所有的子语句按照布尔关系合并
      • +也即MUST表示必须满足的语句
      • SHOULD表示可以满足的,minNrShouldMatch表示在SHOULD中必须满足的最小语句个数,默认是0,也即既然是SHOULD,也即或的关系,可以一个也不满足(当然没有MUST的时候除外)。
      • -也即MUST_NOT表示必须不能满足的语句
    • 树的叶子节点中:
      • 最基本的是TermQuery,也即表示一个词
      • 当然也可以是PrefixQuery和FuzzyQuery,这些查询语句由于特殊的语法,可能对应的不是一个词,而是多个词,因而他们都有rewriteMethod对象指向MultiTermQuery的Inner Class,表示对应多个词,在查询过程中会得到特殊处理。

    2.4、搜索查询对象

    代码为:

    TopDocs docs = searcher.search(query, 50);

    其最终调用search(createWeight(query), filter, n);

    索引过程包含以下子过程:

    • 创建weight树,计算term weight
    • 创建scorer及SumScorer树,为合并倒排表做准备
    • 用SumScorer进行倒排表合并
    • 收集文档结果集合及计算打分
  • 相关阅读:
    Kafka:ZK+Kafka+Spark Streaming集群环境搭建(二十六)Structured Streaming:WARN clients.NetworkClient: Error while fetching metadata with correlation id 1 : {my-topic=LEADER_NOT_AVAILABLE}
    Kafka:ZK+Kafka+Spark Streaming集群环境搭建(二十五)Structured Streaming:同一个topic中包含一组数据的多个部分,按照key它们拼接为一条记录(以及遇到的问题)。
    Kafka:ZK+Kafka+Spark Streaming集群环境搭建(二十四)Structured Streaming:Encoder
    Kafka:ZK+Kafka+Spark Streaming集群环境搭建(二十三)Structured Streaming遇到问题:Set(TopicName-0) are gone. Some data may have been missed
    Structured Streaming编程向导
    Kafka:ZK+Kafka+Spark Streaming集群环境搭建(二十二)Spark Streaming接收流数据及使用窗口函数
    Linux:磁盘挂载
    Kafka:ZK+Kafka+Spark Streaming集群环境搭建(二十一)NIFI1.7.1安装
    Spark参数设置的方式
    mydumper安装、原理介绍
  • 原文地址:https://www.cnblogs.com/hustfly/p/3892284.html
Copyright © 2011-2022 走看看