Solr整体请求过程详细分析.docx
- 文档编号:3301185
- 上传时间:2022-11-21
- 格式:DOCX
- 页数:20
- 大小:230.68KB
Solr整体请求过程详细分析.docx
《Solr整体请求过程详细分析.docx》由会员分享,可在线阅读,更多相关《Solr整体请求过程详细分析.docx(20页珍藏版)》请在冰豆网上搜索。
Solr整体请求过程详细分析
Solr请求过程执行概述
Solr的请求(包括索引数据更新和查询)都是通过SolrCore类的
execute(SolrRequestHandlerhandler,SolrQueryRequestreq,SolrQueryResponsersp)方法来执行的。
其中第一个参数表示执行这一过程的处理器,这些处理器都在handler包下,例如XmlUpdateRequestHandler。
第二个参数表示请求其中包含请求的参数(通常是查询时我们通过表单传入的参数)以及请求的内容流(例如更新索引时发送的xml格式的数据)。
第三个参数代表响应结果,handler处理请求后会将结果保存在第三个参数中。
SolrQueryRequest接口的谱系图
其中有斜线标示的方法已经废弃
SolrQueryResponse接口的谱系图
索引更新过程
xml格式的更新数据被保存在请求的streams:
Iterable
其中的ContentStream接口谱系图如下:
注意这里的GiantContentStream是我自己加入的类。
这个类继承自ContentStreamBase并且改写了构造函数与InputStreamgetStream()方法。
这个方法正是更新时被处理器读取数据时调用的关键。
看一看这个类的大纲也就明白了这个类的本质。
我们看到这样的代码:
finalNamedList
rsp.add("responseHeader",responseHeader);
这段代码的作用是什么呢?
它是创建一个请求头的载体,然后将其加入到响应类中,这样我们就可以在响应中获得我们请求头,这个请求头中包含我们的请求参数。
关于NameList这个类,说明文件说明中说它是一个name/value对的有序表,具有如下特性:
∙name可以重复
∙name/value对的有序
∙可以通过index访问
∙name和value都可以为空
看看它的谱系图:
再看这样一段代码:
NamedListtoLog=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类的一个属性,定义如下:
protectedNamedListtoLog=newSimpleOrderedMap();
上面我们已经看到了SimpleOrderedMap类是NamedList的子类,这里通过属性的名字不难想象它是记录需要写入日志中的内容的载体。
的确我们在打印出来的日志中很容易找到如下内容:
信息:
[database]webapp=nullpath=nullparams={}status=0QTime=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);
该方法内部的代码如下:
SolrParamsp=req.getParams();
if(defaults!
=null){
p=newDefaultSolrParams(p,defaults);
}
if(appends!
=null){
p=newAppendedSolrParams(p,appends);
}
if(invariants!
=null){
p=newDefaultSolrParams(invariants,p);
}
req.setParams(p);
AppendedSolrParams是DefaultSolrParams的子类,可以看出来,这段代码的作用是设置req使其具有实质性的参数内容。
代码:
rsp.setHttpCaching(httpCaching);
作用:
设置是否进行缓存功能。
代码:
handleRequestBody(req,rsp);
这段代码是处理体,由于我们获得的是XmlUpdateRequestHandler的实例,所以我们又要跳到XmlUpdateRequestHandler类的handleRequestBody方法中了。
handleRequestBody所做事情如下:
∙SolrParamsparams=req.getParams();获得请求参数内容。
∙UpdateRequestProcessorChainprocessingChain=
req.getCore().getUpdateProcessingChain(params.get(UpdateParams.UPDATE_PROCESSOR));获得更新请求处理链。
∙UpdateRequestProcessorprocessor=processingChain.createProcessor(req,rsp);获得更新请求处理器。
∙Iterable
∙如果streams为空,进行提交的尝试处理,处理失败抛出错误。
如果不为空,如下处理:
for(ContentStreamstream:
req.getContentStreams()){
Readerreader=stream.getReader();
try{
XMLStreamReaderparser=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的原型:
newRunUpdateProcessorFactory(),
newLogUpdateProcessorFactory()。
也就是说在没有进行处理链配置的时候,就使用它们作为默认值。
我们从RunUpdateProcessorFactory来看更新处理器工厂的本质。
它是RunUpdateProcessor的工厂,工厂的本质是产生实例,所以它最重要的方法是getInstance(SolrQueryRequest,SolrQueryResponse,UpdateRequestProcessor),这里的最后一个参数是下一个更新请求处理器,这就是“链”的由来。
那么RunUpdateProcessor是什么呢?
请看下面。
RunUpdateProcessor实质是处理添加,提交,删除的地方,每个更新方法的参数是UpdateCommand的子类。
也就是最终我们的更新请求都会通过这里来进行处理。
而LogUpdateProcessorFactory又有所不同,它是关于日志处理器(LogUpdateProcessor)的一个工厂类。
然后,我们利用获得的处理链来构建处理器:
UpdateRequestProcessorprocessor=processingChain.createProcessor(req,rsp);
在createProcessor方法的内部,我们看到每个处理器工厂都被轮流调用了,也就是最终返回的processor是和一连串的处理器相关的。
最后,我们来看如何利用获得的处理器以及多个内容流streams来处理更新请求的。
for(ContentStreamstream:
req.getContentStreams()){
Readerreader=stream.getReader();
try{
XMLStreamReaderparser=inputFactory.createXMLStreamReader(reader);
this.processUpdate(processor,parser);
}
finally{
IOUtils.closeQuietly(reader);
}
}
我们在一个for循环中对每个ContentStream作如下处理。
先获得其Reader的实例,然后利用xml分析工具构建对Reader的解析字符流。
XMLStreamReaderparser=inputFactory.createXMLStreamReader(reader);
使用this.processUpdate(processor,parser);来处理更新。
XmlUpdateRequestHandler的processUpdate(processor,parser)做了什么事情呢?
请跟随我来看一看。
主要到while(true)这样一个循环,在循环内部是对parser:
XMLStreamReader的循环读取与处理。
这个循环中有一个switch语句,:
intevent=parser.next();
switch(event){
caseXMLStreamConstants.END_DOCUMENT:
parser.close();
return;
caseXMLStreamConstants.START_ELEMENT:
StringcurrTag=parser.getLocalName();//获取当前标签名
if(currTag.equals(ADD)){
............
//这里主要是设置addCmd的一些属性
}
elseif("doc".equals(currTag)){
......
processor.processAdd(addCmd);
......
}
elseif(COMMIT.equals(currTag)||OPTIMIZE.equals(currTag)){
......
processor.processCommit(cmd);
......
}
elseif(DELETE.equals(currTag)){
......
processDelete(processor,parser);
......
}
break;
}
在这个过程中,我们看到xml解析时,发现当前标签,然后判断其内容。
如果内容为add,那么使用
addCmd=newAddUpdateCommand()来创建新的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){
//addingdocument--prepwriter
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{
//allowduplicates
writer.addDocument(cmd.getLuceneDocument(schema));
}
了解一下SOLR的跟踪器。
先来看看构造函数。
publicCommitTracker(){
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方法了。
这个方法包括提交的所有过程。
CommitUpdateCommandcommand=newCommitUpdateCommand(false);
command.waitFlush=true;
command.waitSearcher=true;
//noneedforcommand.maxOptimizeSegments=1;sinceitisnotoptimizing
commit(command);
autoCommitCount++;
这里首先创建一个提交更新命令的对象,并设置构造函数的参数为false,意思是optimize(优化)为false。
r然后设置其两个属性waitFlush和waitSearcher为true,然后调用commit方法处理刚才创建的提交更新命令的对象。
最后将自动更新计数增1。
不管自动提交过程成功或者失败,都会在finally语句块中将pending设为null,以取消pending对线程的监听。
最后的几句代码是判断是否又有新的内容添加进来(通过lastAddedTime>started否判断),如果有又重复这样一个过程:
1.如果已经添加进来的doc的数目大于允许的doc的上界,那么启动一个自动提交线程。
2.如果超时时间
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Solr 整体 请求 过程 详细 分析