作者:梦蕾AngeL | 来源:互联网 | 2023-07-21 18:48
Search步骤a.SolrParams参数准备q–查询语句sort–排序字段&排序方式rows–返回多少条start–起始点fl–返回字段,需要返回scorer字段的话
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 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用来存放从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,如果取的值是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返回了...