您的位置:首页 > 其它

solr搜索过程解析

2015-09-18 09:02 288 查看
Search步骤

a.SolrParams参数准备

q – 查询语句

sort – 排序字段&排序方式

rows – 返回多少条 start – 起始点

fl – 返回字段,需要返回scorer字段的话这个KEY对应的VALUE里包含”score”就可以了

qt – 查询类型,根据这个字段找Handler

fq – FilterQuery

b.根据coreName到coreContains拿到SolrCore,再到SolrParams里去qt的VALUE到SolrCore找对应的SolrRequestHandler

如果没找到需要调用core.close表示归还,在从coreContains那里拿core时将它的被引用做了++操作了的

c.根据SolrParams&SolrCore构造一个SolrQueryRequest,其包含如下属性SolrCore, SolrParams, Map<obj, obj> context

实例化一个SolrQueryResponse,有了这两个对象后就可以调用solrcore.execute执行查询操作了

d.solrcore.execute – 实际上执行的是handler.handleRequest(request, response)

[1].SolrRequestHandler可以配置三个不同重要性的默认参数,defaults,appends,invariants上面提到过,handler

在处理请求的第一步就是用这三个SolrParams包装用户提交的SolrParams。其包装逻辑是params为用户提交的SolrParams

defaults作为params的默认取值SolrParams -> params

appends作为params里getParams添加参数的SolrParams –> params

params作为invariants的默认取值SolrParams –> params

[2].先前提到对于SearchHandler有SearchComponent,这里只需要注意下QueryComponent就可以了,添加其他的组件

一 是可能没有用二是会带来内存开销,比如StatsComponent组件加入的话查询会返回符合结果的DocSet,其本身也没什么用在 SearchHandler处理查询请求逻辑里面其会先初始化一个ResponseBuilder,这个对象用来存放各个SearchComponent 处理的其他或者结构需要的数据,资源协调者;其再迭代SearchComponent调用prepare函数;其再迭代SearchComponent调 用process函数

整个core里SearchComponent只有一个实例,处理查询请求的数据当然不能放在这些SearchComponent里,只能放在资源协调者ResponseBuilder上…

[3].组件QueryComponent的prepare(ResponseBuilder)逻辑

---------

request.params.get[fl]拿到希望返回的域,判断里面是否包含scorer –> 设置到ResponseBuilder上

request.params.get[defType]拿到QueryParserPlugin名字,如果为null则使用LuceneQParserPlugin.NAME

request.params.get[q]拿到查询字符串 –>设置到ResponseBuilder上

QParser.getParser(queryStr, queryParserType, request)解析得到Qparser,其解析过程大致是

---------

a.实例化一个Map<String, String>用来存放从queryStr以正则表达式{!(.*)}抽取的group(1)里符合正则

x|x=’(.*)’|x=”(.*)”|x=/$(.*)/$|x=(.*) |x=(.*)$ - x表示符合JAVA变量命名规则的字符串

对于第一种往MAP里PUT(“type”, x),对于第四种则PUT(x, request.params.get(group(1)));其他的put(x, group(1))

queryStr里正则{!(.*)}剩下的PUT(“v”, 剩下的str),MAP –> MapSolrParams作为localParams

b.从localParams找”typd”.value作为指定QueryParserPlugin的值,如果没有还是使用默认的LuceneQParserPlugin.NAME

如果queryStr里包含正则{!(.*)}则queryStr=localParams.get(“v”)

c.根据指定的QueryParserPlugin的名称到core里获取QueryParserPlugin,调用其createParser函数构造Qparser

其传入的参数有{queryStr, localParams, requst.params, request}

d.以LuceneQParserPlugin为例子查看其构造Qparser - LuceneQParser的过程,Qparser的作用主要是构造一个Query

在实例化时如果localParams.get(“tags”)不为null则会取request.context.get(“tags”)其值是一个

Map<str, collection>,如果取的值是null则new以个PUT到request.context里,再来解析localParams.get(“tags”)

得到的字符串,有”,”切割得到的子字符串PUT到request.context.get(“tags”)得到的Map中,VALUE=

List.add(LuceneQParserPlugin),以解析{!tag=”wdx,monkey”}为例子,context.get(“tags”)里有两个映射

“wdx” -> List – {LuceneQParserPlugin} //LuceneQParserPlugin持有request, queryStr, params, localParams

“monkey” -> List – {LuceneQParserPlugin}

---------

调用Qparser.parse获取Query的逻辑是

a.到localParam¶ms里找”df” – 默认字段,找VALUE时localParam优先,找不到再到params里面,如果没找到则

到core.indexSchema那里拿

b.到localParam¶ms里找”q.op” – BooleanQuery默认关系,拿的逻辑同上

c.实例化一个SolrQueryParser解析queryStr -> Query输出

得到Query –>设置到ResponseBuilder上

---------

抽取localParam¶ms里的”sort”,”rows”,”start” –> 设置到ResponseBuilder上,其中sort可以指定

schema里指定的域名|score|docid

同时把Qparser设置到ResponseBuilder上

---------

处理”fq” – FilterQuery

request.params.get(“fq”)得到过滤查询String[],迭代这个数组调用QParser.getParser(str, null, request)

获取Qparser解析str得到过滤查询Query,QParser.getParser的逻辑上面介绍过,这样得到一个过滤查询的链表,将这个

链表设置到ResponseBuilder上

[4].组件QueryComponent的process(ResponseBuilder)逻辑

a.每个request都只持有一个IndexSearcher的引用,通过调用core.getSearcher获取,QueryComponent

需要先获取IndexSearcher,core.getSearcher里的逻辑是

----getSearcher的逻辑

getSearcher – (forceNew, returnSearcher, waitSearcher-Futures)

关注solr全局三个点调用getSearcher函数 : solrCore初始化时(false, false, null),QueryComponent处理查询

请求时(false, true, null),UpdateHandler在处理commit请求时(true, false, new Future[1])

---------

1.solrCore初始化时

根据solrconfig配置的IndexReaderFactory&DirectoryFactory获取索引的IndexReader,再使用这个reader

封装一个SolrIndexReader,再使用这个SolrIndexReader封装一个RefCounted(searcher的引用计数器,当搜索

组件获取一个组件后引用++,用完后调用close引用--,当引用数为0时将这个引用从core管理的一个当前被使用的

searcher的链表移除,同时调用searcher.close回收资源),将这个引用添加到core管理的一个当前被使用的searcher

的链表里如果firstSearcherListeners不为空则回调这些监听器,这个回调是交给core的一个newSingleThreadExecutor去

做的,再往这个线程池里添加一个任务:将这个RefCounted设置为core当前最新的searcher的引用计数器

最后返回null,因为returnSearcher=false

在solrCore初始化时这样做的主要目的是在初始化时就加载好IndexSearcher,搜索请求来了之后能立即返回,而不必等待加载IndexSearcher

---------

2.QueryComponent处理查询请求时

由于core当前最新的searcher的引用计数器不为null且这个获取IndexSearcher的请求不是强制要求获取最新的,且

returnSearcher=true故直接返回core当前最新的searcher的引用计数器,且这个引用计数器做++

这里面还有段当前searcher的引用计数器为null的逻辑,但是没有发现有什么情况会导致这种情况发生故不累述了

---------

3.UpdateHandler在处理commit请求时

首先到core管理的一个当前被使用的searcher的链表里获取目前最新的searcher;同时会加载索引目录下的

index.properties文件(如果存在的话),拿到KEY=’index’的值,其指明目前索引的存放地方;如果获取的目录和当前

最新的searcher使用的目录一致且solrConfig.reopenReaders为true则获取通过searher.reader.reopen获取

最新的reader -> 封装成searcher,否则直接IndexReader.open获取reader。

获取到searcher后的一段逻辑[RefCount封装,添加到searchers链表]和core初始化时是一样的,接下来的逻辑是

如果solrConfig.useColdSearcher为TRUE其当前searcher的引用为null-导致来自QueryComponent的请求阻塞

[现在还没发现什么情况会导致searcher的引用为null]

立即将这个新的searcher的引用设置为core当前最新的searcher的引用计数器,这样来自QueryComponent的请求

拿到这个引用后返回,当时这时这个新建的searcher是没有经过其前一个searcher的cache热身的,同时这样会导致这个

新建的searcher不会进行热身活动

如果solrConfig.useColdSearcher为FALSE则会往线程池里添加一个热身的任务

如果newSearcherListeners不为空则回调这些监听器,也是给线程池的任务

最后如果先前没有做将新的searcher的引用设置为core当前最新的searcher的引用计数器的行为的话,则往线程池添加

一个任务 – 将新的searcher的引用设置为core当前最新的searcher的引用计数器

最后返回null,因为returnSearcher=false

---------

----solr的索引视图

用IndexReader构建SolrIndexSearcher时会先用solr的视图包装这个IndexReader –> SolrIndexReader

solr 的索引视图在lucene上添加了一层快速处理多个索引目录的视图,在用IndexReader构建SolrIndexReader时会获取这个 reader下的所有叶子Reader-SegmentReader,并建立索引,基于这样索引传入一个DOCID后能快速的定位 SegementReader,即其会持有其下的所有SegementReader&其start-offset,同时search时如果是 TermQuery则直接用叶子reader来做

----SolrIndexSearcher.cache

a.SolrIndexSearcher有四个缓存 – 对于cache来说有三个角色CacheConfig,SolrCache,CacheRegenerator

其分工是CacheConfig来自于solrconfig文件配置,在一个searcher初始化时会迭代fieldValueCache,filterCache,

queryResultCache,documentCache的配置,如果有配置则通过CacheConfig @class创建SolrCache实例

CacheRegenerator是负责新searcher创建后用老的searcher热身行为的,会遍历老的searcher的各个cache的key-value

具体做什么由各个CacheRegenerator说了算,因为这时候由于update的commit操作会有新的doc进入索引,所有老的value都不能用了,唯一能做的就是知道哪些key比较热门

对于filterCache来说其会使用新的searcher去加载这些热门的key的值放到cache里

对于queryResultCache来说其会使用新的searcher去加载这些热门query的值放到cache里,当时查询参数flag会加上

NO_CHECK_QCACHE

-------

SolrCache : filterCache queryResultCache documentCache

queryResultCache – key=QueryResultKey value=DocList,主要是存放一个查询请求返回的DocList数据,QueryResultKey计算hash的影响因素有 : query filters sort

documentCache – key=int value=Document,主要是存放docid->document的映射,在加载document时都会走这个缓存,如果不存在则用reader去加载,然后PUT到cache里

filterCache :key=Query value=DocSet,主要存放query->docSet的映射,满足这个query的所有docid的映射,其作用和

queryResultCache比区别在于其存放的可能只是中间结果,queryResultCache存放的是最终结果

值得注意的是使用这些cache是很耗内存空间的,为了限制内存空间的使用,cache的实现也是使用LRU机制,通过实现LinkedHashMap的removeEldestEntry函数里判断目前的大小是否大于了限制来实现的

-------

b.对于纯粹的来自QueryComponent组件的查询请求是只需要返回DocList就可以了,可能还需要分数,这里分析为了简便只考虑需要DocList和分数的情况

根据offset+len计算当期期望的最大的docId,再和maxDoc比较,不能比maxDoc大

如果queryResultCache存在且这个查询没有过滤查询则到用这个查询封装一个QueryResultKey到queryResultCache取值

如果存在且[查询不需要分数或者cache里有分数]这样就立即拿到了docList返回,查询结束

如果cache里没有找到或者需要分数而cache里没分数则需要走查询了,对于查询的结果如果可以cache则put到queryResultCache

---------------

拿到DocList了后就可以给SolrQueryResponse返回了...
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: