分类目录归档:搜索资料

高级搜索技术–Filter

来源:互联网

Filter顾名思义 就是用来过虑搜索结果的,在Lucene中有3个过虑器

1,DateFilter constrains the document space to only documents with a specified date field within a given range of dates.

2,QueryFilter uses the results of query as the searchable document space for a new query.

3,CachingWrapperFilter is a decorator over another filter caching its results to increase performance when used again.

import lia.common.LiaTestCase;
import org.apache.lucene.document.DateField;
005 import org.apache.lucene.index.Term;
006 import org.apache.lucene.search.BooleanQuery;
007 import org.apache.lucene.search.CachingWrapperFilter;
008 import org.apache.lucene.search.DateFilter;
009 import org.apache.lucene.search.Filter;
010 import org.apache.lucene.search.Hits;
011 import org.apache.lucene.search.IndexSearcher;
012 import org.apache.lucene.search.Query;
013 import org.apache.lucene.search.QueryFilter;
014 import org.apache.lucene.search.RangeQuery;
015 import org.apache.lucene.search.TermQuery;
016
017 import java.util.Date;
018
019 public class FilterTest extends LiaTestCase {
020   private Query allBooks;
021   private IndexSearcher searcher;
022   private int numAllBooks;
023   private CachingWrapperFilter cachingFilter;
024
025   protected void setUp() throws Exception {
026     super.setUp();
027
028     allBooks = new RangeQuery(new Term("pubmonth","190001"),
029                                    new Term("pubmonth", "200512"),
030                                    true);
031     searcher = new IndexSearcher(directory);
032     Hits hits = searcher.search(allBooks);
033     numAllBooks = hits.length();
034   }
035
036   public void testDateFilter() throws Exception {
037     Date jan1 = parseDate("2004-01-01");
038     Date jan31 = parseDate("2004-01-31");
039     Date dec31 = parseDate("2004-12-31");
040
041     DateFilter filter = new DateFilter("modified", jan1, dec31);
042
043     Hits hits = searcher.search(allBooks, filter);
044     assertEquals("all modified in 2004",
045         numAllBooks, hits.length());
046
047     filter = new DateFilter("modified", jan1, jan31);

048     hits = searcher.search(allBooks, filter);
049     assertEquals("none modified in January",
050         0, hits.length());
051   }
052
053   public void testQueryFilter() throws Exception {
054     TermQuery categoryQuery =
055        new TermQuery(new Term("category", "/philosophy/eastern"));
056
057     Filter categoryFilter = new QueryFilter(categoryQuery);////
QueryFilter
058
059     Hits hits = searcher.search(allBooks, categoryFilter);
060     assertEquals("only tao te ching", 1, hits.length());
061   }
062
063   public void testFilterAlternative() throws Exception {

064     TermQuery categoryQuery =
065        new TermQuery(new Term("category", "/philosophy/eastern"));
066
067     BooleanQuery constrainedQuery = new BooleanQuery();
068     constrainedQuery.add(allBooks, true, false);
069     constrainedQuery.add(categoryQuery, true, false);
070
071     Hits hits = searcher.search(constrainedQuery);
072     assertEquals("only tao te ching", 1, hits.length());
073   }
074
075
076   public void testQueryFilterWithRangeQuery() throws Exception {
077     Date jan1 = parseDate("2004-01-01");
078     Date dec31 = parseDate("2004-12-31");
079
080     Term start = new Term("modified",
081         DateField.dateToString(jan1));
082     Term end = new Term("modified",
083         DateField.dateToString(dec31));
084
085     Query rangeQuery = new RangeQuery(start, end, true);
086
087     Filter filter = new QueryFilter(rangeQuery);
088     Hits hits = searcher.search(allBooks, filter);
089     assertEquals("all of 'em", numAllBooks, hits.length());
090   }
091
092   public void testCachingWrapper() throws Exception {

093     Date jan1 = parseDate("2004-01-01");
094     Date dec31 = parseDate("2004-12-31");
095
096     DateFilter dateFilter =
097         new DateFilter("modified", jan1, dec31);
098
099     cachingFilter =
100         new CachingWrapperFilter(dateFilter);

101     Hits hits = searcher.search(allBooks, cachingFilter);
102     assertEquals("all of 'em", numAllBooks, hits.length());
103   }
104 }

Solr请求过程执行概述

Solr的请求(包括索引数据更新和查询)都是通过 SolrCore类的

execute(SolrRequestHandler handler, SolrQueryRequest req, SolrQueryResponse rsp) 方法来执行的。

其中第一个参数表示执行这一过程的处理器,这些处理器都在handler包下,例如XmlUpdateRequestHandler。

第二个参数表示请求其中包含请求的参数(通常是查询时我们 通过表单传入的参数)以及请求的内容流(例如更新索引时发送的xml格式的数据)。

第三个参数代表响应结果,handler处理请求后会将结果保存在第三个参数中。

SolrQueryRequest 接口的谱系图

 

其中有斜线标示的方法已经废弃

 

SolrQueryResponse接口的谱系图

 

 

索引更新过程

xml格式的更新数据被保存在请求的streams : Iterable<ContentStream>参数中。其中的ContentStream接口谱系图如下:

 

 

注意这里的GiantContentStream是我自己加入的类。 这个类继承自ContentStreamBase并且改写了构造函数与InputStream getStream()方法。这个方法正是更新时被处理器读取数据时调用的关键。看一看这个类的大纲也就明白了这个类的本质。

 

我们看到这样的代码:
final NamedList<Object> responseHeader = new SimpleOrderedMap<Object>();
rsp.add("responseHeader", responseHeader);
这段代码的作用是什么呢?它是创建一个请求头的载体,然后将其加入到响应类中,这样我们就可以在响应中获得我们请求头,这个请求头中包含我们的请求参数。
关于NameList这个类,说明文件说明中说它是一个name/value对的有序表,具有如下特性:

  • name可以重复
  • name/value 对的有序
  • 可以通过index访问
  • name和value都可以为空

看看它的谱系图:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

再看这样一段代码:
NamedList toLog = rsp.getToLog();
//toLog.add("core", getName());
toLog.add("webapp", req.getContext().get("webapp"));
toLog.add("path", req.getContext().get("path"));
toLog.add("params", "{" + req.getParamString() + "}");
toLog是什么呢?它是SolrQueryResponse类的一个属性,定义如下:

protected NamedList toLog = new SimpleOrderedMap();
上面我们已经看到了SimpleOrderedMap类是NamedList的子类,这里通过属性的名字不难 想象它是记录需要写入日志中的内容的 载体。的确我们在打印出来的日志中很容易找到如下内容:
信息: [database] webapp=null path=null params={} status=0 QTime=1438

执行更新关键的代码是这里:

handler.handleRequest(req,rsp);
setResponseHeaderValues(handler,req,rsp);

第一句执行更新过程,第二句只是将req中的参数部分以及经过处理后的rsp中的toLog中的内容写如到rsp中的请求头 (responseHeader )中来。

我们来看最关键的第一句: handler.handleRequest(req,rsp);

 

关于 handler.handleRequest(req,rsp)。更新常用的handler是XmlUpdateRequestHandler,所以我们 进入该类的handleRequest方法中查看究竟。我们发现XmlUpdateRequestHandler中并没有该方法,我们猜想该方法可以在父 类中,果然。于是我们就研究父类的handleRequest方法。

 

SolrPluginUtils.setDefaults(req,defaults,appends,invariants);
该方法内部的代码如下:
SolrParams p = req.getParams();
if (defaults != null) {
p = new DefaultSolrParams(p,defaults);
}
if (appends != null) {
p = new AppendedSolrParams(p,appends);
}
if (invariants != null) {
p = new DefaultSolrParams(invariants,p);
}
req.setParams(p);
AppendedSolrParams是DefaultSolrParams的子类,可以看出来,这段代码的作用是设置req使其具有实质性的参数内容。

代码:rsp.setHttpCaching(httpCaching);
作用:设置是否进行缓存功能。

代码:handleRequestBody( req, rsp );
这段代码是处理体,由于我们获得的是XmlUpdateRequestHandler的实例,所以我们又要跳到 XmlUpdateRequestHandler类的handleRequestBody方法中了。handleRequestBody所做事情如下:

  • SolrParams params = req.getParams();获得请求参数内容。
  • UpdateRequestProcessorChain processingChain =
    req.getCore().getUpdateProcessingChain( params.get( UpdateParams.UPDATE_PROCESSOR ) );获得更新请求处理链。
  • UpdateRequestProcessor processor = processingChain.createProcessor(req, rsp);获得更新请求处理器。
  • Iterable<ContentStream> streams = req.getContentStreams();获得更新请求的内容流。
  • 如果streams为空,进行提交的尝试处理,处理失败抛出错误。如果不为空,如下处理:

for( ContentStream stream : req.getContentStreams() ) {
Reader reader = stream.getReader();
try {
XMLStreamReader parser = inputFactory.createXMLStreamReader(reader);
this.processUpdate( processor, parser );
}
finally {
IOUtils.closeQuietly(reader);
}
}
这段处理的本质是循环获取每部分内容流,然后获得内容流的Reader实例。接下构建XMLStreamReader 实例并调用this.processUpdate( processor, parser );来完成更新。最后尝试进行提交(仅仅是可能,因为我们可能在更新的同时在请求参数中加入commit参数,并设其值为true)。

  • processor.finish();

我们一步一步来观察这个详细的过程。
首先是获得处理链,版本的solrconfig.xml配置文件,所以没有添加处理链的配置参数。所以我们在 eq.getCore().getUpdateProcessingChain方法中传入的参数是null,但是尽管如此依然获得了一个包含两个 UpdateRequestProcessorFactory的处理链。虽然我们知道Map类型中可以包含键为null的内容,但是这个内容是怎么获得的呢?
实际上SolrCore的构造函数中调用了SolrCore类的loadUpdateProcessorChains方法,这就是加载处理链的地方。在这 段代码中我们看到这两个UpdateRequestProcessorFactory的原型:
new RunUpdateProcessorFactory(),
new LogUpdateProcessorFactory()。
也就是说在没有进行处理链配置的时候,就使用它们作为默认值。
我们从RunUpdateProcessorFactory来看更新处理器工厂的本质。它是RunUpdateProcessor的工厂,工厂的本质是产 生实例,所以它最重要的方法是getInstance(SolrQueryRequest, SolrQueryResponse, UpdateRequestProcessor),这里的最后一个参数是下一个更新请求处理器,这就是“链”的由来。那么 RunUpdateProcessor是什么呢?请看下面。

 

 

 

 

 

 

 

 

RunUpdateProcessor实质是处理添加,提交,删除的地方,每个更新方法的参数是UpdateCommand的子类。也就是最终我们的更新 请求都会通过这里来进行处理。而LogUpdateProcessorFactory又有所不同,它是关于日志处理器 (LogUpdateProcessor)的一个工厂类。

然后,我们利用获得的处理链来构建处理器:
UpdateRequestProcessor processor = processingChain.createProcessor(req, rsp);
在createProcessor方法的内部,我们看到每个处理器工厂都被轮流调用了,也就是最终返回的processor 是和一连串的处理器相关的。

最后,我们来看如何利用获得的处理器以及多个内容流streams来处理更新请求的。
for( ContentStream stream : req.getContentStreams() ) {
Reader reader = stream.getReader();
try {
XMLStreamReader parser = inputFactory.createXMLStreamReader(reader);
this.processUpdate( processor, parser );
}
finally {
IOUtils.closeQuietly(reader);
}
}
我们在一个for循环中对每个ContentStream 作如下处理。先获得其Reader的实例,然后利用xml分析工具构建对Reader的解析字符流。
XMLStreamReader parser = inputFactory.createXMLStreamReader(reader);
使用this.processUpdate( processor, parser );来处理更新。

 

XmlUpdateRequestHandler的processUpdate( processor, parser )做了什么事情呢?请跟随我来看一看。
主要到while (true)这样一个循环,在循环内部是对parser :XMLStreamReader的循环读取与处理。这个循环中有一个switch语句,:
int event = parser.next();
switch (event){
case XMLStreamConstants.END_DOCUMENT:
parser.close();
return;
case XMLStreamConstants.START_ELEMENT:
String currTag = parser.getLocalName();//获取当前标签名

if (currTag.equals(ADD)) {
............
//这里主要是设置addCmd的一些属性
}
else if ("doc".equals(currTag)) {
......
processor.processAdd(addCmd);
......
}
else if ( COMMIT.equals(currTag) || OPTIMIZE.equals(currTag)) {
......
processor.processCommit( cmd );
......
}
else if (DELETE.equals(currTag)) {
......
processDelete( processor, parser);
......
}
break;
}

 

在这个过程中,我们看到xml解析时,发现当前标签,然后判断其内 容。如果内容为add,那么使用
addCmd = new AddUpdateCommand()来创建新的addCmd实例,并设置其overwriteCommitted等属性。如果标签内容为doc,使用如 下代码先清空addCmd的内容,然后从xml数据中读取一 个新的Doc并将其放如addCmd中。
addCmd.clear();
addCmd.solrDoc = readDoc( parser );
然后调用processor.processAdd(addCmd)方法来进行处理,至于commit与delete也是类似的方法处理。

 

下面我们来看processor.processAdd(addCmd)做了些什么,清楚它的实质以后,关于提交与删除的处理就可以用同样的方法来进行处 理了。
通过简单的信息输入发现,这里有三个类的processAdd方法被调用了。首先调用的是RunUpdateProcessor的processAdd方法,在这个方法中调用 了父类(UpdateRequestProcessor)的processAdd方法,调用父类的方法的时候我们注意到方法中有if (next != null) next.processAdd(cmd)代码,正是这个代码的调用使得LogUpdateProcessor中的processAdd得以调用,这正是 处理得以实现的根本。也就是说之前构造时候的链式仅仅时使其具有链式的能力,而调用父类 (UpdateRequestProcessor)的processAdd方法使得链式真正发挥了作用。

 

下面我们沿着RunUpdateProcessor、UpdateRequestProcessor、LogUpdateProcessor这条主线一步 一步来看。
在RunUpdateProcessor中的processAdd方法中。通过如下的代码获得了cmd的doc属性,这就是说将原来的 SolrInputDocument引入了schema信息建立了lucene下 的docement。
cmd.doc = DocumentBuilder.toDocument(cmd.getSolrInputDocument(), req.getSchema());
然后通过下面的代码来实现 lucene下的文档对象的更新。
updateHandler.addDoc(cmd);
这里的updateHandler是什么东西呢?输出updateHandler.getClass().getName()信息很容易发现该类是 org.apache.solr.update.DirectUpdateHandler2。这是更新默认的处理器,也可以在配置文件中进行设定。
我们进入该类看个究竟吧。
简单浏览代码概要发现所有的更新处理最终都落实到这里,常常惹人头痛的“两把锁”--提交锁与访问锁也都在这里。暂且先不管这么多吧,我们先来看看我们关心的addDoc方法。

 

 

我们进入了DirectUpdateHandler2中的addDoc方法,现在我们来看看里面发生了什么惊天动地的事情吧。
前面几行代码是增加一些记录添加的文档有多少等的一 些信息以及如果当前ID域不存在的情况下允许重复的处理,暂且别管它。我们从读索引访问加锁开始。
iwAccess.lock();
获得锁以后我们在一个try语句中有如下的代码:
synchronized (this) {
// adding document -- prep writer
closeSearcher();
openWriter();
tracker.addedDocument();
}
这段代码做了什么呢?一个一个的分析。

closeSearcher()
关闭搜索器,这样使得没有搜索器会从索引文件读取内容。我试图证明 这个观点,但是还没有找到直接的证据,暂时保留意见吧。

openWriter();
这个过程先后经过了下面的方法:

DirectUpdateHandler2的 openWriter()
UpdateHandler的createMainIndexWriter("DirectUpdateHandler2", false)方法。第二个参数表示是否removeAllExisting。
UpdateHandler的SolrIndexWriter(name,core.getIndexDir(), removeAllExisting, schema, core.getSolrConfig().mainIndexConfig);参数分别为名字,索引目录路径,是否移去所有已经存在的内容,代表schema.xml内容的实例,代表索引的相关配置属性的实例。
最后就是调用lucene的 IndexWriter的构建方法了。
这一切的目的就是使得当前DirectUpdateHandler2的实例具备一个可用的IndexWriter的实例,注意这里是lucene下的 IndexWriter了。

tracker.addedDocument()
tracker是CommitTracker的实例,作为DirectUpdateHandler2的属性,我把它叫做提交跟踪器。代码说明中这样介绍: 这是一个跟踪自动提交的帮助类。

为了保持思路的完整性暂且别管这个类,后面再来理它。先来看看后面的代码。
后面其实是先获得cmd中的overwriteCommitted以及overwriteCommitted的值来决定如何添加文档到索引中。一种是允许重复ID的添加,另外一种是不允许重复的添加。代码如下:
if (cmd.overwriteCommitted || cmd.overwritePending) {
if (cmd.indexedId == null) {
cmd.indexedId = getIndexedId(cmd.doc);
}
//id唯一的添加方式
writer.updateDocument(idTerm.createTerm(cmd.indexedId), cmd.getLuceneDocument(schema));
} else {
// allow duplicates
writer.addDocument(cmd.getLuceneDocument(schema));
}

 

 

了解一下SOLR的跟踪器。先来看看构造函数。
public CommitTracker() {
docsSinceCommit = 0;
pending = null;

docsUpperBound = core.getSolrConfig().getInt("updateHandler/autoCommit/maxDocs", -1);
timeUpperBound = core.getSolrConfig().getInt("updateHandler/autoCommit/maxTime", -1);

SolrCore.log.info("AutoCommit: " + this);
}
docsSinceCommit记录自上一次提交以来已经具有的doc的数目,pending 是ScheduledFuture的对象,显然这个类里面用到了多线程。docsUpperBound和 timeUpperBound是从配置文件获得的提交条件,doc的上界以及时间的上界。
再来看看addedDocument方法吧,这个方法没有参数。首先利用下面的两行代码来对已经添加的doc数目进行递增,同时设置当前时间给 lastAddedTime ,也就是获得最近添加doc时的系统时间。
docsSinceCommit++;
lastAddedTime = System.currentTimeMillis();
下面是一个条件为docsUpperBound > 0 && (docsSinceCommit > docsUpperBound)的if语句,这个语句的意思是,在doc数目上界大于0且自上次提交以来的doc数目已经超过了允许的上界,那么做以下事情:
如果上次提交延迟,中断上次提交延迟的任务。为pending分配一个新的任务(这里就是CommitTracker本身)。这段代码的本质就是在doc 已经超过允许的值的情况下进行提交。
接下来就是分配一个超时触发的提交任务,也就是在设定的时间到了以后触发提交过程。

既然schedule方法中的Runnable参数是this,也就是提交跟踪器,那么我们就有必要来看一看它的run方法了。这个方法包括提交的所有过程。

CommitUpdateCommand command = new CommitUpdateCommand( false );
command.waitFlush = true;
command.waitSearcher = true;
//no need for command.maxOptimizeSegments = 1; since it is not optimizing
commit( command );
autoCommitCount++;

这里首先创建一个提交更新命令的对象,并设置构造函数的参数为false,意思是optimize(优化)为false。r然后设置其两个属性 waitFlush 和waitSearcher 为true,然后调用commit方法处理刚才创建的提交更新命令的对象。最后将自动更新计数增1。不管自动提交过程成功或者失败,都会在finally 语句块中将pending设为null,以取消pending对线程的监听。

最后的几句代码是判断是否又有新的内容添加进来(通过lastAddedTime > started否判断),如果有又重复这样一个过程:

1.如果已经添加进来的doc的数目大于允许的doc的上界,那么启动一个自动提交线程。

2.如果超时时间大于0,那么也启动一个自动提交线程,这里的时间延迟是时间上界。

如此说来,这个run中最重要的实质部分就是commit(command)方法,我们必须知道这个方法做了什么。按照顺序来解释代码。

if (cmd.optimize) {
optimizeCommands.incrementAndGet();
} else {
commitCommands.incrementAndGet();
}

根据是否优化(optimize)来设置是优化计数器递增还是一般提交计数器递增。

————————————————————

Future[] waitSearcher = null;
if (cmd.waitSearcher) {
waitSearcher = new Future[1];
}

构造一个Future的空数组,然后根据是否设置waitSearcher(决定是否在获得新的Searcher后继续进行还是立即先下执行)来决定是否为这个Future数组进行实例分配。如果对其进行分配,后面的代码会在某个位置利用waitSearcher来等待其关注的代码执行完毕后继续向前执 行。

iwCommit.lock();
try {
log.info("start "+cmd);

if (cmd.optimize) {
closeSearcher();
openWriter();
writer.optimize(cmd.maxOptimizeSegments);
}

closeSearcher();
closeWriter();

callPostCommitCallbacks();
if (cmd.optimize) {
callPostOptimizeCallbacks();
}
// open a new searcher in the sync block to avoid opening it
// after a deleteByQuery changed the index, or in between deletes
// and adds of another commit being done.
core.getSearcher(true,false,waitSearcher);

// reset commit tracking
tracker.didCommit();

log.info("end_commit_flush");

error=false;
}
finally {
iwCommit.unlock();
//TEST
log.info("提交锁解锁2");
addCommands.set(0);
deleteByIdCommands.set(0);
deleteByQueryCommands.set(0);
numErrors.set(error ? 1 : 0);
}
这里使用锁来进行同步它之后执行的代码。如果需要优化(optimize),那么关闭关联的搜索器,打开写索引器,然后调用 writer.optimize(cmd.maxOptimizeSegments)方法。该方法的作用是使得索引断的数目少于 maxOptimizeSegments。

closeSearcher();
closeWriter(); li

为了防止上面的if没有成立(即优化没有进行)的时候没有进行搜索器和写索引器的关闭,那么我们这里再进行一次以确保确实被执行了。

callPostCommitCallbacks();
if (cmd.optimize) {
callPostOptimizeCallbacks();
}

对于这段代码,目前不知道有什么作用。先不理它了。

core.getSearcher(true,false,waitSearcher);
这里显然是重新获得搜索器,这里正是提交过程是精髓所在,所谓提交就是使得刚才的更新生效,也就是搜索器的重新建立。因为我们的查询结果都是从搜索器获得 的。这里将waitSearcher作为参数传递给了getSearcher方法,也为后面的等待searcher获取过程完毕才继续执行埋下了伏笔。

tracker.didCommit();只是完成reset工作。finally里面的块也是完成一些变量的重设工作。

最后这段代码:

if (waitSearcher!=null && waitSearcher[0] != null) {
try {
waitSearcher[0].get();
} catch (InterruptedException e) {
SolrException.log(log,e);
} catch (ExecutionException e) {
SolrException.log(log,e);
}
}

这正是上面埋下的伏笔,这里通过查看waitSearcher来获知搜索器建立的线程是否已经完成了,如果没有完成,等待,如果完成了那么继续向下运行。
我们知道了所谓提交就是使得更新处理 对查询生效。也就是搜索器的重新建立,这里我们就来看看这个搜索器重新获得的过程是怎么实现的。 也就是DirectUpdateHandler2类commit方法中的这一小句代码所做的事情: core.getSearcher(true,false,waitSearcher)。
我们来看看SolrCore类中的getSearcher(boolean forceNew, boolean returnSearcher, final Future[] waitSearcher)方法的参数说明。
forceNew:是否强制建立新的,因为可以利用原有的来建立,故由此 一说。 (提交当然是ture)
returnSearcher:是否需要返回一个 Searcher。(提交false即可,因为没有必要返回新的)
waitSearcher:这里将填 充一个Future,这个Future要直到获得一个搜索器以后才返回。利用它可以获得waitSearcher功能。

 

下面来看看代码。一开始就来了一个synchronized语句块。这个语句快说明如下:
// it may take some time to open an index.... we may need to make
// sure that two threads aren't trying to open one at the same time
// if it isn't necessary.
翻译一下先: 打开一个索引会花一 点时间...如果没有必要,我们需要确保没有两个线程同时打开一个索引。
synchronized (searcherLock) {
// 看是否有一个搜索器可以直接返回。前提:不需要强制返回新的搜索器。
if (_searcher!=null && !forceNew) {...
}

// 看是否有正在建立中的搜索器,如果有,我们等待它建立
if (onDeckSearchers>0 && !forceNew && _searcher==null) {
try {
searcherLock.wait();
} catch (InterruptedException e) {
log.info(SolrException.toStr(e));
}
}

//经过了上面的等待,我们再次查看是否有可用的搜索器可以返回了
if (_searcher!=null && !forceNew) {...
}

到此位置,既然没有可以直接返回的,或者需要强制建立新的,我们只好开始建立过程了。
首先设置一个信号,通知其它线程,一个新的搜索器正在建立过程中。先开始onDeckSearchers合法性检查。
onDeckSearchers++;
if (onDeckSearchers < 1) {
// 检查onDeckSearchers 是否小于1,因为上面进行了加加操作,所以不 应小于1,但是
//只是出于编程习惯的检查,实际上并不会发生。
log.severe(logid+"ERROR!!! onDeckSearchers is " + onDeckSearchers);
onDeckSearchers=1; // reset
} else if (onDeckSearchers > maxWarmingSearchers) {
//如果超过了最大允许的“预热”搜索器的数目,抛出错误
} else if (onDeckSearchers > 1) {//仅仅输出警告信息,因为搜索器的建立也不是随便允许多个的
log.info(logid+"PERFORMANCE WARNING: Overlapping onDeckSearchers=" + onDeckSearchers);
}
}

如果把搜索器的建立比作一个工程,那么上面的过程是对这个工程的可行性,环境污染性,资源消耗程度等方面的评估。下面就正是开工咯。
首先建立一个新的搜索器,如果这个搜索器建立的过程失败了,我们在一个同步 块中进行处理(onDeckSearchers递减)。失败后我们没有什么可以继续的了,抛出错误并返回。如果成功了那么还要继续进行一系列的处理。下面 代码中的 tmp = new SolrIndexSearcher....就是获得一个搜索器的过程。它有可能失败。即使成功,我们仍然需要进行进一步的处理,别忘记了我们的返回类型 是RefCounted<SolrIndexSearcher> 而不是SolrIndexSearcher类型,而且新建立的搜索器是”裸体“的,没有任何内容的。
SolrIndexSearcher tmp;
try {
tmp = new SolrIndexSearcher(this, schema, "main", IndexReader.open(FSDirectory.getDirectory(getIndexDir()), true), true, true);
} catch (Throwable th) {
synchronized(searcherLock) {
onDeckSearchers--;
// notify another waiter to continue... it may succeed
// and wake any others.
searcherLock.notify();
}
// need to close the searcher here??? we shouldn't have to.
throw new RuntimeException(th);
}
这里简单写出SolrIndexSearcher的构造函数。根据参数名很容易明白其含义。

SolrIndexSearcher(SolrCore core, IndexSchema schema, String name, IndexReader r, boolean closeReader, boolean enableCache);

我们假设新的处理器的建立成功了,也就是tmp确实获得了一个搜索器,那么我们就需要进一步的处理,注意成功获得tmp的情况下 onDeckSearchers没有递减,且 searcherLock.notify()也还没有执行。我们可以猜想,下面的处理中肯定会出现这两个语句。
处理包括哪些呢?
final SolrIndexSearcher newSearcher=tmp;//将获得的搜索器引用赋予newSearcher,从此newSearcher就代表了这个新建立 的搜索器。

newSearchHolder=newHolder(newSearcher);//将新获得的搜索器包装成newSearchHolder,使其具备引用计数功能

根据returnSearcher是否为ture,决定newSearchHolder.incref()是 否执行,也即其引用计数是否增加。

下面这三个语句是用来记录是否需要搜索器计数的递减和是否已经完成注册。如果在后面的检查中发现 decrementOnDeckCount[0]=true,我们还需要执行递减操作。

final boolean[] decrementOnDeckCount=new boolean[1];
decrementOnDeckCount[0]=true;
boolean alreadyRegistered = false;

接下来A.如果_searcher为null且useColdSearcher为ture(意思是原有的_searcher为空,并且配置文件中指定使用 “冷的搜索器")那么我们将newSearchHolder 进行注册(注册时已经执行了onDeckSearchers--与 searcherLock.notifyAll()),同时 decrementOnDeckCount[0]=false; boolean alreadyRegistered = true;(意思是后面通过对这两个变量的检查就可以确定已经注册完毕且建立计数也已经递减了)。B.如果 _searcher不为空,那么currSearcherHolder=_searcher; currSearcherHolder.incref();(也就是这里currSearcherHolder获 得了原有的_searcher)。

接下来的这段代码currSearcher = currSearcherHolder==null ? null : currSearcherHolder.get();她的本质是使得currSearcher保存了原有的_searcher内容。

到这里的时候,我们可以肯定,如果原有_searcher不为空,那么 currSearcher中是有内容的,而如果原来 _searcher为空,这里的currSearcher也就为空,下面的处理就是:

A.如果currSearcher不为空(请注意这时候它来自_searcher),我们将它预热newSearcher。

B.如果currSearcher为空,也就是我们不具备直接预热的来源,如果这时候具有一个第一次(也就是之前没有任何搜索器)建立搜索器的监听器(firstSearcherListeners),那么我们就可以让这 个监听器来处理newSearcher。

C如果currSearcher不为空,也就是原有的_searcher具有内容,如果这时候具有具有一个新搜索器建立时的监听器(newSearcherListeners),那么我们就用这个监听器来 根据currSearcher来对newSearcher进行处理。

最后看是不是已经注册了,因为前面的处理中仅仅在获得了newSearcher且_searcher为空的时候才有可能进行注册,也就是假如 _searcher不为空的情况下,newSearcher很可能是没有注册的,这样的话,我们这里就需要进行一个注册。

最后剩下的就是决定是否需要返回搜索器了,同时决定是否需要将与搜索器建立过程相关的future与传递进来的future参数进行关联。

对于像监听器(newSearcherListeners与firstSearcherListeners)与搜索器注册这样的概念我们还没有理解。

主要是想弄清楚Solr1.5中的搜索器注册以及建立新搜索器监听器和建立第一次搜索器监听器的概念,同时弄清楚预热是个什么概念。

 

 

注册搜索器

SolrCore::registerSearcher(RefCounted<SolrIndexSearcher> newSearcherHolder)
方法目的:用newSearcherHolder来替代_searcher(SolrCore的属性),同时该方法内部的下面代码完成了对 newSearcherHolder所持有的搜索器的注册。
SolrIndexSearcher newSearcher = newSearcherHolder.get();
newSearcher.register(); //

newSearcher.register()做了什么事情呢?先看代码。
>>>>
core.getInfoRegistry().put("searcher", this);
core.getInfoRegistry().put(name, this);
for (SolrCache cache : cacheList) {
cache.setState(SolrCache.State.LIVE);
core.getInfoRegistry().put(cache.name(), cache);
}
registerTime=System.currentTimeMillis();
1.将搜索器的信息加入核的信息记录器并与searcher关联
2.将搜索器的信息加入核的信息记录器并与搜索器的名字关联
3.将与这个搜索器关联的每个缓存的名字与实例的管理加入核的信息记录器中
4.设置当前搜索器的注册时间

  • firstSearcherListeners

在SolrCore中有一个事件监听器(SolrEventListener)的列表firstSearcherListeners。
SolrEventListener是一个接口,尽管它有init(NamedList args),postCommit(),newSearcher(SolrIndexSearcher newSearcher, SolrIndexSearcher currentSearcher)三个方法,现在我们只关心其newSearcher方法。SolrEventListener类谱系图如下:

 

 

 

 

 

这两个监听器都是在solrconfig.xml中设置的。但是如果没有设置怎么办呢,应该有一个默认值。但是发现其实如果配置文件中没有设置的话,那么 这个监听过程是不存在的。也就是什么都不住。

  • newSearcherListeners

与firstSearcherListeners类似,不赘述。

  • 预热

SolrIndexSearcher::warm(SolrIndexSearcher old)
这个预热过程就是将old搜索器中的内容拷贝到本方法所属的搜索器中来。一般每个搜索器具有三个缓存 filterCache,queryResultCache,documentCache,我把他们分别叫做过滤缓存,查询结果缓存,文档缓存。

solr-wiki—-solr分布式索引

Solr是一个独立的企业级搜索应用服务器,它对外提供类似于Web-service的API接口。用户可以通过http请求,向搜索引擎服务器提交一定格式的XML文件,生成索引;也可以通过Http Get操作提出查找请求,并得到XML格式的返回结果。
Solr是一个高性能,采用Java5开发,基于Lucene的全文搜索服务器。同时对其进行了扩展,提供了比Lucene更为丰富的查询语言,同时实现了可配置、可扩展并对查询性能进行了优化,并且提供了一个完善的功能管理界面,是一款非常优秀的全文搜索引擎。

What is Distributed Search?

当一个索引越来越大,达到一个单一的系统无法满足磁盘需求的时候,或者一次简单的查询实在要耗费很多的时间的时候,我们就可以使用solr的分布式索引 了。在分布式索引中,原来的大索引,将会分成多个小索引(索引并不定规模小,之前称之为小索引相对于之前的整个索引来讲的),solr可以将从这些小索引 返回的结果合并,然后返回给客户端。

 

如果当前的solr查询请求能够很快被处理,而你只是希望整个搜索系统的处理能力,那么你可以看看这篇文章http://wiki.apache.org/solr/CollectionDistribution。

 

Distributed Searching

参数“shards”能够使请求被分发到shards所指定的小索引上。

shards 的格式  :host:port/base_url[,host:port/base_url]*

目前只有查询请求才能够被分发。能够处理这种要求分发的请求的组件包括standard request handler 、以及它的子类、任何使用 支持分发的组件的其他handler。

 

目前支持分布式查询的组件有:

  • 查询组件-----根据一个请求返回一个文档集。
  • 面查询组件-----目前只支持排序的数字类的域。solr1.4将可以支持字符类型的域
  • 高亮显示组件
  • debug组件。

对于分布式搜索组件,你也可以参考   WritingDistributedSearchComponents 。

 

Distributed Searching Limitations

  • 索引的文档必须有一个 唯一键
  • 当拥有相同的id的文档被查询到时,solr将选择第一个文档。其他的结果将被丢弃。
  • No distributed idf
  • 不支持“竞价排名”组件 QueryElevationComponent
  • 在处理的过程中,也即是 stage过程中,索引是有可能发生变化的。这样在索引更新的时刻,可能会发现搜索的结果跟索引不匹配的现象。
  • 目前并不支持 时间类型的 facetting搜索。(将在solr1.4被支持)
  • 目前只支持 能够排序的域的 facet搜索
  • shard 的数量会受到get请求的方法的限制,大多数的web 服务器,只支持4000个字符左右的get请求。
  • 当“start”这个参数很大的时候,效率比较低下。举个例子,你发起一个请求,参数start=50000 ,rows=25 .被请求的索引的每个shard有500,000个文档。这时候,就会导致500,000条记录,在网络上被传输。

Distributed Deadlock

每个小索引,都有可能既接收顶级的 查询请求,也 向其他的小索引发出次级的查询请求。在这里,你就要小心了,http服务器中设置的能够处理的http请求的线程数一定要大于 这个小索引所接收到的 顶级请求和次级请求的总和。如果配置没有这样配好的话,一个由分发而产生的死锁很有可能会发生。

现在我们来试想下一种最简单的情况,只有两个小索引,每个小索引只能够起一个线程来处理Http 请求。两个索引都能接收顶级请求,并将请求分发给另一个索引。因为它们没有多余的线程可以处理多于一个的请求,所以 servlet容器就会阻塞 刚来的请求,这种情况一直持续到正在处理的请求被处理完毕。(但是,那个正在处理的请求永远也不会被处理完毕,因为它正在等待它分发出去的请求的回应,而 这个分发出去的请求被阻塞了)。

关于死锁,目前笔者想到的方法是设定分发请求的超时时间。这个应该不难实现。

 

Distributed Indexing

怎样建立小索引,这点随用户的喜好而定。一种很简单的方法决定那条记录放在那个索引上可以使用类似这样的一种公式 uniqueId.hashCode() % numServers。

Example

出于测试目的,我们在一台机器的两个不同的端口启动 solr 服务。

 

Shell代码  收藏代码
  1. #make a copy
  2. cd solr
  3. cp -r example example7574
  4. #change the port number
  5. perl -pi -e s/8983/7574/g example7574/etc/jetty.xml  example7574/exampledocs/post.sh
  6. #in window 1, start up the server on port 8983
  7. cd example
  8. java -server -jar start.jar
  9. #in window 2, start up the server on port 7574
  10. cd example7574
  11. java -server -jar start.jar
  12. #in window 3, index some example documents to each server
  13. cd example/exampledocs
  14. ./post.sh [a-m]*.xml
  15. cd ../../example7574/exampledocs
  16. ./post.sh [n-z]*.xml
  17. #now do a distributed search across both servers with your browser or curl
  18. curl 'http://localhost:8983/solr/select?shards=localhost:8983/solr,localhost:7574/solr&indent=true&q=ipod+solr'

lucene通过修改boost值改善index索引

来源:互联网

并不是所有的Document和Field是平等创建的。Document增量是个使得这种需求能够简单实现的一个特征。默认情况下,所有的 Document都没有增量,他们都有相同的增量因数1.0。通过改变某个Document的增量因数,可以让Lucene认为它比索引中的其他 Document更重要或更不重要。在索引的时候只需执行setBoost(float)方法。

看两个示例,就能明白其中的用法。

示例1,使用默认的的boost

[java] view plaincopy
  1. package com.cn;
  2. import org.apache.lucene.analysis.standard.StandardAnalyzer;
  3. import org.apache.lucene.document.Document;
  4. import org.apache.lucene.document.Field;
  5. import org.apache.lucene.index.IndexWriter;
  6. import org.apache.lucene.index.IndexWriterConfig;
  7. import org.apache.lucene.index.Term;
  8. import org.apache.lucene.search.IndexSearcher;
  9. import org.apache.lucene.search.Query;
  10. import org.apache.lucene.search.ScoreDoc;
  11. import org.apache.lucene.search.TermQuery;
  12. import org.apache.lucene.search.TopDocs;
  13. import org.apache.lucene.store.Directory;
  14. import org.apache.lucene.store.RAMDirectory;
  15. import org.apache.lucene.util.Version;
  16. public class TT {
  17. public static void main(String []args) throws Exception {
  18. String [] ids = {"1","2","3","4","5"};
  19. String [] province = {"shanghai","beijing","liaoning","liaoning","zhejiang"};
  20. String [] contents = {"shanghai is a city","beijing is a city","jinzhou is a city","shenyang is a city","hangzhou is a city"};
  21. String [] city = {"shanghai","beijing","jinzhou","shenyang","hangzhou"};
  22. Directory directory = new RAMDirectory();
  23. IndexWriter indexWriter = new IndexWriter(directory,new IndexWriterConfig(Version.LUCENE_34, new StandardAnalyzer(Version.LUCENE_34)));
  24. for(int i = 0;i < ids.length;i++){
  25. Document doc = new Document();
  26. doc.add(new Field("id",ids[i],Field.Store.YES,Field.Index.NOT_ANALYZED));
  27. doc.add(new Field("province",province[i],Field.Store.YES,Field.Index.ANALYZED));
  28. doc.add(new Field("contents",contents[i],Field.Store.YES,Field.Index.ANALYZED));
  29. doc.add(new Field("city",city[i],Field.Store.YES,Field.Index.ANALYZED));
  30. indexWriter.addDocument(doc);
  31. }
  32. System.out.println("total:"+indexWriter.numDocs());
  33. indexWriter.close();
  34. queryMethod(directory,"contents","city");
  35. }
  36. public static void queryMethod(Directory directory,String item,String txt)throws Exception {
  37. Term term = new Term(item,txt);
  38. Query query = new TermQuery(term);
  39. IndexSearcher indexSearcher = new IndexSearcher(directory);
  40. TopDocs topDocs = indexSearcher.search(query, 10);
  41. System.out.println("it has "+topDocs.totalHits+" "+txt+" in "+item);
  42. ScoreDoc [] scoreDoc = topDocs.scoreDocs;
  43. for(int i=0;i<scoreDoc.length;i++){
  44. Document d = indexSearcher.doc(scoreDoc[i].doc);
  45. System.out.println("city:"+d.get("city"));
  46. System.out.println("contents:"+d.get("contents"));
  47. }
  48. }
  49. }

运行结果为:

total:5
it has 5 city in contents
province:shanghai  city:shanghai
contents:shanghai is a city
province:beijing  city:beijing
contents:beijing is a city
province:liaoning  city:jinzhou
contents:jinzhou is a city
province:liaoning  city:shenyang
contents:shenyang is a city
province:zhejiang  city:hangzhou
contents:hangzhou is a city

 

示例2,增加了boost,代码为

[java] view plaincopy
  1. package com.cn;
  2. import org.apache.lucene.analysis.standard.StandardAnalyzer;
  3. import org.apache.lucene.document.Document;
  4. import org.apache.lucene.document.Field;
  5. import org.apache.lucene.index.IndexWriter;
  6. import org.apache.lucene.index.IndexWriterConfig;
  7. import org.apache.lucene.index.Term;
  8. import org.apache.lucene.search.IndexSearcher;
  9. import org.apache.lucene.search.Query;
  10. import org.apache.lucene.search.ScoreDoc;
  11. import org.apache.lucene.search.TermQuery;
  12. import org.apache.lucene.search.TopDocs;
  13. import org.apache.lucene.store.Directory;
  14. import org.apache.lucene.store.RAMDirectory;
  15. import org.apache.lucene.util.Version;
  16. public class TT {
  17. public static void main(String []args) throws Exception {
  18. String [] ids = {"1","2","3","4","5"};
  19. String [] province = {"shanghai","beijing","liaoning","liaoning","zhejiang"};
  20. String [] contents = {"shanghai is a city","beijing is a city","jinzhou is a city","shenyang is a city","hangzhou is a city"};
  21. String [] city = {"shanghai","beijing","jinzhou","shenyang","hangzhou"};
  22. Directory directory = new RAMDirectory();
  23. IndexWriter indexWriter = new IndexWriter(directory,new IndexWriterConfig(Version.LUCENE_34, new StandardAnalyzer(Version.LUCENE_34)));
  24. for(int i = 0;i < ids.length;i++){
  25. Document doc = new Document();
  26. doc.add(new Field("id",ids[i],Field.Store.YES,Field.Index.NOT_ANALYZED));
  27. doc.add(new Field("province",province[i],Field.Store.YES,Field.Index.ANALYZED));
  28. doc.add(new Field("contents",contents[i],Field.Store.YES,Field.Index.ANALYZED));
  29. doc.add(new Field("city",city[i],Field.Store.YES,Field.Index.ANALYZED));
  30. if(province[i].equals("liaoning")){
  31. if(city[i].equals("shenyang")){
  32. doc.setBoost(5.0f);
  33. }else{
  34. doc.setBoost(2.0f);
  35. }
  36. }
  37. indexWriter.addDocument(doc);
  38. }
  39. System.out.println("total:"+indexWriter.numDocs());
  40. indexWriter.close();
  41. queryMethod(directory,"contents","city");
  42. }
  43. public static void queryMethod(Directory directory,String item,String txt)throws Exception {
  44. Term term = new Term(item,txt);
  45. Query query = new TermQuery(term);
  46. IndexSearcher indexSearcher = new IndexSearcher(directory);
  47. TopDocs topDocs = indexSearcher.search(query, 10);
  48. System.out.println("it has "+topDocs.totalHits+" "+txt+" in "+item);
  49. ScoreDoc [] scoreDoc = topDocs.scoreDocs;
  50. for(int i=0;i<scoreDoc.length;i++){
  51. Document d = indexSearcher.doc(scoreDoc[i].doc);
  52. System.out.println("city:"+d.get("city"));
  53. System.out.println("contents:"+d.get("contents"));
  54. }
  55. }
  56. }

运行结果为: total:5
it has 5 city in contents
city:shenyang
contents:shenyang is a city
city:jinzhou
contents:jinzhou is a city
city:shanghai
contents:shanghai is a city
city:beijing
contents:beijing is a city
city:hangzhou
contents:hangzhou is a city

从两个例子中看出属于辽宁省的结果在靠前位置了,而且shenyang比jinzhou还要靠前。

lucene 3.4 contrib/facet 切面搜索

来源:互联网
solr  有facet  search  ,BOBO也有;现在lucene3.4之后也有了,这个是贡献版本,在apache 官方的包里面有提供,这种功能对于分组统计和类别统计是一个很好的帮手;

有了这个就不用羡慕solr了,不是我抗拒solr,只是像我们公司有时间让我们开发的情况下,我更偏向于底层点的api开发,lucene更得心应手。

再说现在的solr没有近实时搜索,听说要4.0后有。

废话不说,直接上代码

 

  1. public class Indexer {
  2. //需要索引的信息
  3. public static String[] docTitles = {
  4. "white car",
  5. "white dog",
  6. };
  7. public static String[] docTexts = {
  8. "the white car is the one I want.",
  9. "the white dog does not belong to anyone.",
  10. };
  11. //分的类别
  12. public static CategoryPath[] categories = {
  13. new CategoryPath("root","a","f1"), new CategoryPath("root","a","f2")
  14. };
  15. public static void index (Directory indexDir, Directory taxoDir) throws Exception {
  16. // 创建一个普通的indexwriter
  17. IndexWriter iw = new IndexWriter(indexDir, new IndexWriterConfig(ExampleUtils.EXAMPLE_VER, SimpleUtils.analyzer));
  18. //创建一个 taxonomy writer
  19. TaxonomyWriter taxo = new DirectoryTaxonomyWriter(taxoDir, OpenMode.CREATE);
  20. int nDocsAdded = 0;
  21. int nFacetsAdded = 0;
  22. for (int docNum=0; docNum<docTexts.length; docNum++)
  23. {
  24. // 准备当前的切面
  25. List<CategoryPath> facetList = SimpleUtils.categoryPathArrayToList(categories[0]);
  26. //
  27. CategoryDocumentBuilder categoryDocBuilder = new CategoryDocumentBuilder(taxo).setCategoryPaths(facetList);
  28. // 创建document
  29. Document doc = new Document();
  30. doc.add(new Field(SimpleUtils.TITLE, docTitles[docNum], Store.YES, Index.ANALYZED));
  31. doc.add(new Field(SimpleUtils.TEXT, docTexts[docNum], Store.NO, Index.ANALYZED));
  32. // 把切面的索引信息添加到document
  33. categoryDocBuilder.build(doc);
  34. // 最终写入索引
  35. iw.addDocument(doc);
  36. nDocsAdded ++;
  37. nFacetsAdded += facetList.size();
  38. }
  39. // commit changes.
  40. // we commit changes to the taxonomy index prior to committing them to the search index.
  41. // this is important, so that all facets referred to by documents in the search index
  42. // will indeed exist in the taxonomy index.
  43. taxo.commit();
  44. iw.commit();
  45. // close the taxonomy index and the index - all modifications are
  46. // now safely in the provided directories: indexDir and taxoDir.
  47. taxo.close();
  48. iw.close();
  49. System.out.println("Indexed "+nDocsAdded+" documents with overall "+nFacetsAdded+" facets.");
  50. }
  51. public static void main(String[] args){
  52. String indexp = "D:/work/data/index/facet_index/n_index";
  53. String indexp_t = "D:/work/data/index/facet_index/t_index";
  54. try {
  55. Directory  directory =  FSDirectory.open(new File(indexp));
  56. Directory  directory_t =  FSDirectory.open(new File(indexp_t));
  57. new Indexer().index(directory, directory_t);
  58. } catch (Exception e) {
  59. e.printStackTrace();
  60. }
  61. }
  62. }
  1. public class Searcher {
  2. public void query(Directory indexDir, Directory taxoDir) throws CorruptIndexException, IOException
  3. {
  4. Query q = new TermQuery(new Term(SimpleUtils.TEXT, "white"));
  5. IndexReader indexReader = IndexReader.open(indexDir, true);
  6. IndexSearcher searcher = new IndexSearcher(indexReader);
  7. TaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoDir);
  8. TopScoreDocCollector topDocsCollector = TopScoreDocCollector.create(10, true);
  9. FacetIndexingParams     indexingParams = new DefaultFacetIndexingParams();
  10. FacetSearchParams facetSearchParams = new FacetSearchParams(indexingParams);
  11. facetSearchParams.addFacetRequest(new CountFacetRequest(new CategoryPath("root","a"), 10));
  12. FacetsCollector facetsCollector = new FacetsCollector(facetSearchParams, indexReader, taxoReader);
  13. // perform documents search and facets accumulation
  14. searcher.search(q, MultiCollector.wrap(topDocsCollector, facetsCollector));
  15. // Obtain facets results and print them
  16. List<FacetResult> res = facetsCollector.getFacetResults();
  17. System.out.println(res.size());
  18. int i = 0;
  19. for (FacetResult facetResult : res) {
  20. System.out.println("Res " + (i++) + ": " + facetResult);
  21. System.out.println("-------------------------------------");
  22. System.out.println( facetResult.getFacetResultNode().getNumSubResults());
  23. System.out.println( facetResult.getFacetResultNode().getOrdinal());
  24. System.out.println( facetResult.getFacetResultNode().getValue());
  25. System.out.println( facetResult.getFacetResultNode().getResidue());
  26. }
  27. }
  28. public static void main(String[] args) {
  29. String indexp = "D:/work/data/index/facet_index/n_index";
  30. String indexp_t = "D:/work/data/index/facet_index/t_index";
  31. try {
  32. Directory  directory =  FSDirectory.open(new File(indexp));
  33. Directory  directory_t =  FSDirectory.open(new File(indexp_t));
  34. new Searcher().query(directory, directory_t);
  35. } catch (Exception e) {
  36. e.printStackTrace();
  37. }
  38. }
  39. }