博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Lucene4.3进阶开发之柳暗花明( 六)
阅读量:6496 次
发布时间:2019-06-24

本文共 5563 字,大约阅读时间需要 18 分钟。

hot3.png

本篇文章散仙要介绍的是IndexSearcher这个类,这个类是Lucene在进行检索时必不可少的一个组件,可以称为是检索的入口,通过这个入口之后,我们就可以获取与我们检索的关键词相关的一系列Doc,然后我们就可以进行后续相关的业务处理。

所以我们经常会在代码里这样写: 

Directory directory=FSDirectory.open(new File("D:\\索引测试"));//获取一个索引目录IndexReader reader=DirectoryReader.open(directory);//返回一个复合Reader=》DirectoryReader//构造IndexSearcher 检索环境IndexSearcher searcher=new IndexSearcher(reader);

大多数,情况下,我们的代码都是这样写的,实际上IndexSearcher的构造函数有4个,我们最常用的一般有2个,部分源码如下: 

final IndexReader reader; // package private for testing!    // NOTE: these members might change in incompatible ways  // in the next release  protected final IndexReaderContext readerContext;  protected final List
 leafContexts;  /** used with executor - each slice holds a set of leafs executed within one thread */  protected final LeafSlice[] leafSlices;  // These are only used for multi-threaded search  private final ExecutorService executor;  // the default Similarity  private static final Similarity defaultSimilarity = new DefaultSimilarity();    /**   * Expert: returns a default Similarity instance.   * In general, this method is only called to initialize searchers and writers.   * User code and query implementations should respect   * {@link IndexSearcher#getSimilarity()}.   * @lucene.internal   */  public static Similarity getDefaultSimilarity() {    return defaultSimilarity;  }    /** The Similarity implementation used by this searcher. */  private Similarity similarity = defaultSimilarity;  /** Creates a searcher searching the provided index. */  public IndexSearcher(IndexReader r) {   //调用的是2参的构造函数    this(r,null);  }  /** Runs searches for each segment separately, using the   *  provided ExecutorService.  IndexSearcher will not   *  shutdown/awaitTermination this ExecutorService on   *  close; you must do so, eventually, on your own.  NOTE:   *  if you are using {@link NIOFSDirectory}, do not use   *  the shutdownNow method of ExecutorService as this uses   *  Thread.interrupt under-the-hood which can silently   *  close file descriptors (see 
LUCENE-2239).   *    * @lucene.experimental */  public IndexSearcher(IndexReader r, ExecutorService executor) {      this(r.getContext(), executor);  }

看了源码,我们就会发现,我们常用的构造函数实际上是会调用含有线程池并行检索的2参的构造方法,只不过,把线程池设置为null而已,这其实是一个优化的操作,在某些时候能够带来极大的性能提升,这个稍后散仙会详细分析。下面先来看下IndexSearcher里面的一些常用的API方法 

212451_wGGU_1417419.png

212452_NZKe_1417419.png

IndexSearcher类里面提供了大量的方法,用来对检索的数据集的限制和过滤,从而达到我们业务需要的一部分数据,当然我们也可也通过setSimilarity方法来设置我们的自定义的打分策略,还可以通过其他的一些方法,来实现排序,过滤,收集,调试打分信息等等。 

最后,回到文章开始,散仙来分析下,IndexSearcher的并行构造,如何使用多线程来提升检索性能。 

大多数时候,我们默认使用的都是单线程检索,这时候的检索总耗时是顺序检索所有段文件的时间之和,而如果我们使用了并行检索,这时候,我们的检索总耗时,其实就是检索段文件里,耗时最大的那个线程的时间,因为我们是并行检索,所以影响耗时的其实就是检索耗时最长的那个线程的耗时,这有点像“木桶效应”,决定木桶装水的多少,不是由最长的木板决定的,而是由最短的那块木板决定的,反映到这里,其实就是散仙刚提及的耗时可能最长的那个线程,决定了检索的总耗时。 

首先呢,这个功能,并不是说所有的场景下,都有明显的作用,比如,我的索引里就只有一个段文件,那么你开启再多的线程也没用,因为这个并行检索,是一个线程对应一个段文件。 

另外一种情况,我的索引非常小,然后我又压缩成多个段文件,然后使用这个并行检索去检索数据,其实这时候的性能可能连一个单线程都不如,这也就是单线程与多线程的使用场景的区分,只要正确的理解了什么时候使用单线程,什么时候使用多线程,才有可能达到我们最想要的结果。 

所以,这个并行优化的功能,最适合的场景就是我的索引非常大,然后我们把这份索引,压缩成了多个段文件,可能有5个,或者10个以上的段文件,这时候利用这个功能,检索就有很大优势了,下面我们在来看下源码里具体处理: 

if (executor == null) {      return search(leafContexts, weight, after, nDocs);    } else {    	//通过一个公用的队列,来合并结果集      final HitQueue hq = new HitQueue(nDocs, false);      final Lock lock = new ReentrantLock();//锁      final ExecutionHelper
 runner = new ExecutionHelper
(executor);          for (int i = 0; i < leafSlices.length; i++) { // search each sub        runner.submit(new SearcherCallableNoSort(lock, this, leafSlices[i], weight, after, nDocs, hq));      }      int totalHits = 0;      float maxScore = Float.NEGATIVE_INFINITY;      for (final TopDocs topDocs : runner) {        if(topDocs.totalHits != 0) {          totalHits += topDocs.totalHits;          maxScore = Math.max(maxScore, topDocs.getMaxScore());        }      }       //最后从队列里,取值给ScoreDoc进行返回      final ScoreDoc[] scoreDocs = new ScoreDoc[hq.size()];      for (int i = hq.size() - 1; i >= 0; i--) // put docs in array        scoreDocs[i] = hq.pop();

然后在具体的线程类里的实现:

  private static final class SearcherCallableNoSort implements Callable
 {    private final Lock lock;    private final IndexSearcher searcher;    private final Weight weight;    private final ScoreDoc after;    private final int nDocs;    private final HitQueue hq;    private final LeafSlice slice;    public SearcherCallableNoSort(Lock lock, IndexSearcher searcher, LeafSlice slice,  Weight weight,        ScoreDoc after, int nDocs, HitQueue hq) {      this.lock = lock;      this.searcher = searcher;      this.weight = weight;      this.after = after;      this.nDocs = nDocs;      this.hq = hq;      this.slice = slice;    }    @Override    public TopDocs call() throws IOException {      final TopDocs docs = searcher.search(Arrays.asList(slice.leaves), weight, after, nDocs);      final ScoreDoc[] scoreDocs = docs.scoreDocs;      //it would be so nice if we had a thread-safe insert       lock.lock();      try {        for (int j = 0; j < scoreDocs.length; j++) { // merge scoreDocs into hq          final ScoreDoc scoreDoc = scoreDocs[j];          if (scoreDoc == hq.insertWithOverflow(scoreDoc)) {            break;          }        }      } finally {        lock.unlock();      }      return docs;    }  }

通过源码,我们大概可以看出,这个提升,其实是利用了多线程的方式来完成的,通过实现Callable接口,以及重写其的call方法,最后通过公用的全局锁,来控制把检索到的结果集添加到公用的命中队列里,这样一来,一个检索,就被并行的分散到多个线程里,然后最后通过一个全局的容器,来获取所有线程检索的结果,由此以来,在某些场合就能大大提升检索性能。 

当然这种提升是否,还跟我们的硬件环境有关系,如果我们的机器CPU不够强劲,或者我们在单核或双核上的机器上跑,可能会出现预期之外的结果,不过,现在的服务器基本都是配置很好的,一般不会出现这种情况。 

转载于:https://my.oschina.net/heroShane/blog/201955

你可能感兴趣的文章
[git]图解git常用命令
查看>>
洛谷 P2947 [USACO09MAR]向右看齐Look Up【单调栈】
查看>>
zoj 2313 Chinese Girls' Amusement(2-A)
查看>>
Iterator接口分析
查看>>
Tomcat v7.0 Server at localhost are already in use,tomcat提示端口被占用,tomcat端口已经被使用,tomcat端口占用...
查看>>
UGUI之控件以及按钮的监听事件系统
查看>>
Codeforces 814A - An abandoned sentiment from past(水题)
查看>>
POJ 2349 Arctic Network (最小生成树Kruskal)
查看>>
vmstat
查看>>
springboot集成mybatis-generator
查看>>
org.springframework.beans.NotWritablePropertyException
查看>>
TransactionScope使用
查看>>
OC面向对象和小知识点
查看>>
Git忽略提交规则 - .gitignore配置运维总结
查看>>
<html>
查看>>
android音乐播放器开发 SweetMusicPlayer 载入歌曲列表
查看>>
学习pwn的前提工作及部分解决方案
查看>>
C语言读写文件两种方式ASCII 和 二进制。
查看>>
PHP采集相关笔记
查看>>
设置session的过期时间
查看>>