Solr整体请求过程详细分析.docx
- 文档编号:12311586
- 上传时间:2023-04-18
- 格式:DOCX
- 页数:34
- 大小:37.07KB
Solr整体请求过程详细分析.docx
《Solr整体请求过程详细分析.docx》由会员分享,可在线阅读,更多相关《Solr整体请求过程详细分析.docx(34页珍藏版)》请在冰豆网上搜索。
Solr整体请求过程详细分析
Solr 请求过程执行概述
Solr 的请求(包括索引数据更新和查询)都是通过 SolrCore 类的
execute(SolrRequestHandler handler, SolrQueryRequest req, SolrQueryResponse rsp) 方法来执
行的。
其中第一个参数表示执行这一过程的处理器,这些处理器都在 handler 包下,例如
XmlUpdateRequestHandler。
第二个参数表示请求其中包含请求的参数(通常是查询时我们 通过表单传入的参数)以及
请求的内容流(例如更新索引时发送的 xml 格式的数据)。
第三个参数代表响应结果,handler 处理请求后会将结果保存在第三个参数中。
SolrQueryRequest 接口的谱系图
其中有斜线标示的方法已经废弃
SolrQueryResponse 接口的谱系图
索引更新过程
xml 格式的更新数据被保存在请求的 streams:
Iterable
其中的
ContentStream 接口谱系图如下:
注意这里的 GiantContentStream 是我自己加入的类。
这个类继承自 ContentStreamBase 并
且改写了构造函数与 InputStream getStream()方法。
这个方法正是更新时被处理器读取数据
时调用的关键。
看一看这个类的大纲也就明白了这个类的本质。
我们看到这样的代码:
final NamedList
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
∙如果 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=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) {
// 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,然后
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Solr 整体 请求 过程 详细 分析
![提示](https://static.bdocx.com/images/bang_tan.gif)