Lucene4.3进阶开发之柳暗花明( 六)

2024-05-15 04:48

本文主要是介绍Lucene4.3进阶开发之柳暗花明( 六),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

[b][color=red][size=x-large]转载请务必注明,原创地址,谢谢配合!
[url]http://qindongliang1922.iteye.com/blog/1999154[/url]
[/size][/color][/b]

[b][color=olive][size=medium]上篇文章,散仙介绍了IndexWriter的作用,它的最大价值体现在对索引的创建,管理和维护上,通过与IndexWriterConfig这个配置管理类的组合,可以实现最佳的索引策略,当然前提是你得了解IndexWriterConfig里一些重要的参数的配置含义。[/size][/color][/b]


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

所以我们经常会在代码里这样写:
[/size][/color][/b]

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

[b][color=green][size=large]大多数,情况下,我们的代码都是这样写的,实际上IndexSearcher的构造函数有4个,我们最常用的一般有2个,部分源码如下:[/size][/color][/b]
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<AtomicReaderContext> 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 <a
* href="https://issues.apache.org/jira/browse/LUCENE-2239">LUCENE-2239</a>).
*
* @lucene.experimental */
public IndexSearcher(IndexReader r, ExecutorService executor) {

this(r.getContext(), executor);
}


[b][color=green][size=large]看了源码,我们就会发现,我们常用的构造函数实际上是会调用含有线程池并行检索的2参的构造方法,只不过,把线程池设置为null而已,这其实是一个优化的操作,在某些时候能够带来极大的性能提升,这个稍后散仙会详细分析。下面先来看下IndexSearcher里面的一些常用的API方法[/size][/color][/b]
[b][color=olive][size=large][table]
|方法名|描述
|IndexSearcher(IndexReader r) |构建一个搜索实例,使用指定的Reader
|IndexSearcher(IndexReader r, ExecutorService executor) |创建一个并行的检索实例,使用ExecutorService 提供的线程池
|doc(int docID) |通过一个docid获取一个对应的doc
|explain(Query query, int doc) |获取query详细的评分依据信息
|getIndexReader() |获取IndexReader实例
|search(Query query, int n) |获取前N个检索的结果
|search(Query query, Collector results) |通过collector对检索结果进行自定义控制
|search(Query query, Filter filter, Collector results) |通过检索,过滤,以及收集,获取一个特定的检索结果
|search(Query query, Filter filter, int n) |经过滤后 的前N个结果
|search(Query query, Filter filter, int n, Sort sort) |经过滤,排序后的前n个结果
|search(Query query, Filter filter, int n, Sort sort, boolean doDocScores, boolean doMaxScore) |对排序后的结果,是否开启评分策略
|searchAfter(ScoreDoc after, Query query, int n) |检索上一次query后的数据,通常用来分页使用
|setSimilarity(Similarity similarity) |设置自定义的打分策略
|search(Weight weight, int nDocs, Sort sort, boolean doDocScores, boolean doMaxScore) |检索指定分数以上的结果
[/table][/size][/color][/b]

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

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

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

首先呢,这个功能,并不是说所有的场景下,都有明显的作用,比如,我的索引里就只有一个段文件,那么你开启再多的线程也没用,因为这个并行检索,是一个线程对应一个段文件。
另外一种情况,我的索引非常小,然后我又压缩成多个段文件,然后使用这个并行检索去检索数据,其实这时候的性能可能连一个单线程都不如,这也就是单线程与多线程的使用场景的区分,只要正确的理解了什么时候使用单线程,什么时候使用多线程,才有可能达到我们最想要的结果。

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

[/size][/color][/b]
if (executor == null) {
return search(leafContexts, weight, after, nDocs);
} else {
//通过一个公用的队列,来合并结果集
final HitQueue hq = new HitQueue(nDocs, false);
final Lock lock = new ReentrantLock();//锁
final ExecutionHelper<TopDocs> runner = new ExecutionHelper<TopDocs>(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();



[b][color=olive][size=large]然后在具体的线程类里的实现:[/size][/color][/b]
  private static final class SearcherCallableNoSort implements Callable<TopDocs> {

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;
}
}

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

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

[b][size=large][color=olive]好了,今天散仙要写的,就到此为止了,感谢各位道友的光临,如有什么不足之处,欢迎指正交流。

[/color][/size][/b]


[b][color=red][size=x-large]转载请务必注明,原创地址,谢谢配合!
[url]http://qindongliang1922.iteye.com/blog/1999154[/url]
[/size][/color][/b]

这篇关于Lucene4.3进阶开发之柳暗花明( 六)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/990851

相关文章

SpringBoot 多环境开发实战(从配置、管理与控制)

《SpringBoot多环境开发实战(从配置、管理与控制)》本文详解SpringBoot多环境配置,涵盖单文件YAML、多文件模式、MavenProfile分组及激活策略,通过优先级控制灵活切换环境... 目录一、多环境开发基础(单文件 YAML 版)(一)配置原理与优势(二)实操示例二、多环境开发多文件版

使用docker搭建嵌入式Linux开发环境

《使用docker搭建嵌入式Linux开发环境》本文主要介绍了使用docker搭建嵌入式Linux开发环境,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录1、前言2、安装docker3、编写容器管理脚本4、创建容器1、前言在日常开发全志、rk等不同

Python实战之SEO优化自动化工具开发指南

《Python实战之SEO优化自动化工具开发指南》在数字化营销时代,搜索引擎优化(SEO)已成为网站获取流量的重要手段,本文将带您使用Python开发一套完整的SEO自动化工具,需要的可以了解下... 目录前言项目概述技术栈选择核心模块实现1. 关键词研究模块2. 网站技术seo检测模块3. 内容优化分析模

从基础到进阶详解Python条件判断的实用指南

《从基础到进阶详解Python条件判断的实用指南》本文将通过15个实战案例,带你大家掌握条件判断的核心技巧,并从基础语法到高级应用一网打尽,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录​引言:条件判断为何如此重要一、基础语法:三行代码构建决策系统二、多条件分支:elif的魔法三、

基于Java开发一个极简版敏感词检测工具

《基于Java开发一个极简版敏感词检测工具》这篇文章主要为大家详细介绍了如何基于Java开发一个极简版敏感词检测工具,文中的示例代码简洁易懂,感兴趣的小伙伴可以跟随小编一起学习一下... 目录你是否还在为敏感词检测头疼一、极简版Java敏感词检测工具的3大核心优势1.1 优势1:DFA算法驱动,效率提升10

Python进阶之列表推导式的10个核心技巧

《Python进阶之列表推导式的10个核心技巧》在Python编程中,列表推导式(ListComprehension)是提升代码效率的瑞士军刀,本文将通过真实场景案例,揭示列表推导式的进阶用法,希望对... 目录一、基础语法重构:理解推导式的底层逻辑二、嵌套循环:破解多维数据处理难题三、条件表达式:实现分支

基于Python编写自动化邮件发送程序(进阶版)

《基于Python编写自动化邮件发送程序(进阶版)》在数字化时代,自动化邮件发送功能已成为企业和个人提升工作效率的重要工具,本文将使用Python编写一个简单的自动化邮件发送程序,希望对大家有所帮助... 目录理解SMTP协议基础配置开发环境构建邮件发送函数核心逻辑实现完整发送流程添加附件支持功能实现htm

Python开发简易网络服务器的示例详解(新手入门)

《Python开发简易网络服务器的示例详解(新手入门)》网络服务器是互联网基础设施的核心组件,它本质上是一个持续运行的程序,负责监听特定端口,本文将使用Python开发一个简单的网络服务器,感兴趣的小... 目录网络服务器基础概念python内置服务器模块1. HTTP服务器模块2. Socket服务器模块

Java 与 LibreOffice 集成开发指南(环境搭建及代码示例)

《Java与LibreOffice集成开发指南(环境搭建及代码示例)》本文介绍Java与LibreOffice的集成方法,涵盖环境配置、API调用、文档转换、UNO桥接及REST接口等技术,提供... 目录1. 引言2. 环境搭建2.1 安装 LibreOffice2.2 配置 Java 开发环境2.3 配

基于Python实现进阶版PDF合并/拆分工具

《基于Python实现进阶版PDF合并/拆分工具》在数字化时代,PDF文件已成为日常工作和学习中不可或缺的一部分,本文将详细介绍一款简单易用的PDF工具,帮助用户轻松完成PDF文件的合并与拆分操作... 目录工具概述环境准备界面说明合并PDF文件拆分PDF文件高级技巧常见问题完整源代码总结在数字化时代,PD