文章目录
一、查询解析器与语法
1.查询流程
2.通用查询参数
1.defType
defType用来选择解析参数q指定的主查询串的查询解析器,默认使用solr的标准查询解析器(defType=lucene)。
Solr中提供了三种解析器供选择:
lucene: solr的Standard Query Parser 标准查询解析器
dismax: DisMax Query Parser
edismax: Extended DisMax Query Parser
2.sort
指定如何对搜索结果进行排序,asc 升序,desc 降序。Solr可根据如下信息对结果进行排序:
- 文档相关性得分
- 函数计算的结果
- 设置了docValues="true"的基本数据类型字段(numerics, string, boolean, dates, etc.)
- 存储了docValues的可排序分词索引字段(SortableTextFields)。
- 单值不分词索引的字段。
对于基本数据类型和SortableTextFields ,如果是多值的,排序规则:
升序:取最小值;
降序:取最大值;
如要指定用什么值:则在传参时用sort=field(name,max) or sort=field(name,min) 方式传参。
3.start
分页查询的起始行号(从0开始),默认为0。
4.rows
查询返回多少行,默认为10。
5.fq
Filter Query 用来在主查询的结果上进行过滤,不影响相关性评分。Fq对于提速复杂的查询非常有用。因为fq指定的过滤查询结果是独立于主查询被缓存起来的。对于下次查询,如果用到了该过滤查询,则直接从缓存中取出结果进行对主查询的结果进行过滤即可。
fq的传参说明:
可以一次传传多个fq:
fq=popularity:[10 TO *]&fq=section:0
也可将多个过滤条件组合在一个fq:
fq=+popularity:[10 TO *] +section:0
说明:几个fq就缓存几个过滤结果集,利用好这一点可能会影响整体性能
6.fl
field list,指定结果中返回哪些字段,指定的字段必须是 stored=“true” or docValues=“true” 的。多个字段用空格或英文逗号间隔。需要评分时通过 score 指定。如果传入的值为*,则stored=“true” or docValues=“true” and useDocValuesAsStored="true"的字段都会返回。
7.debug
debug参数用于指定在结果中返回调试信息。
8.explainOther
在一个查询中附带解释另一个查询的评分,在结果中返回它的得分解释。这可以让我们理解为什么某个文档没有返回。
9.timeAllowed
限定查询在多少毫秒内返回,如果到时间了还未执行完成,则直接返回部分结果。对于时间敏感、对结果召回率要求不高的场景下非常有用
10.omitHeader
如设为true,则响应体中忽略表示查询执行状态(如耗时)的头信息。
11.wt
指定响应的内容格式:json、xml、csv等。SearchHandler根据它选择ResponseWriter。
12.cache
设置是否对查询结果、过滤查询的结果进行缓存。默认为true。
13.logParamsList
solr默认会日志记录所有的请求参数,如果不需要记录所有,则通过此参数指定要记录的参数名,如:
logParamsList=q,fq
14.echoParams
指定在响应体的内容的头部中返回哪些查询参数。(我是没用过)
3.标准查询解析器
优点:它支持一个健壮且相当直观的语法,允许我们创建各种结构的查询。
缺点:它不能容忍语法错误。
Standard Query Parser 还支持以下请求参数:
q:用标准查询语法定义的查询表达式,必需。
q.op:指定查询表达式的默认操作, “AND” or “OR”,覆盖默认配置值。
df:指定默认查询字段
sow: Split on whitespace 按空格分割,如果设置为true,则会分别对分割出的文本进行分词处理。默认false。
转义:
对语法字符: + - && || ! ( ) { } [ ] ^ “ ~ * ? : \ / 进行转义。
支持嵌入solr查询(子查询),切入查询可以使用任意的solr查询解析器
inStock:true OR {!dismax qf='name manu' v='ipod'}
支持特殊的filter(…) 语法来说明某个子句的结果要作为过滤查询进行缓存
q=features:songs OR filter(inStock:true)
补充:q=features:songs & fq=+filter(inStock:true) +filter(price:[* TO 100])
对于这个语句,会新增三个过滤器缓存,两个是filter子句,一个是整个fq
查询中的时间表示
createdate:1976-03-06T23\:59\:59.999Z
createdate:"1976-03-06T23:59:59.999Z"
createdate:[1976-03-06T23:59:59.999Z TO *]
createdate:[1995-12-31T23:59:59.999Z TO 2007-03-06T00:00:00Z]
timestamp:[* TO NOW]
pubdate:[NOW-1YEAR/DAY TO NOW/DAY+1DAY]
createdate:[1976-03-06T23:59:59.999Z TO 1976-03-06T23:59:59.999Z+1YEAR]
createdate:[1976-03-06T23:59:59.999Z/YEAR TO 1976-03-06T23:59:59.999Z]
范围查询的表示:[ ]表示闭区间,{ }表示开区间
4.DisMax Query Parser
定义:一个查询,可以为不同字段设置评分权重,在合并它命中文档的查询子句时,每个文档的分值取各个子句中的最大得分值。
特点:
- 只支持查询语法的一个很小的子集:简单的短语查询、+ - 修饰符、AND OR 布尔操作;
- 简单的语法,不抛出语法错误异常给用户。
- 可以在多个字段上进行短语查询。
- 可以灵活设置各个查询字段的相关性权重。
- 可以灵活增加满足某特定查询文档的相关性权重。
常用参数说明:
q
指定主查询表达式。注意简单短语,不可使用通配符,+号会被当成 或 处理。
q.alt
提供一个备选语句,当q没有指定或为空时,执行这个查询。
qf
qf指定要查询的字段及权重。
qf="fieldOne^2.3 fieldTwo fieldThree^0.4"
注意:qf 指定solr从哪些field中搜索。像上面的例子只会在fieldOne,fieldTwo, fieldThree这三个field中搜索。如果你同时还指定了q参数,比如q=“hadoop”,那么solr会认为是到 fieldOne,fieldTwo, fieldThree这三个field中搜索hadoop,这三个是并集的关系。生成的query为:fieldOne:hadoop^2.3 | fieldTwo:hadoop | fieldThree:hadoop^0.4
所以此时q就不要写成类似q=“title:hadoop”
这样了。因为这样的话最终的查询 为:fieldOne:title:hadoop^2.3 | fieldTwo:title:hadoop | fieldThree:title:hadoop^0.4
。 除非你的这几个field里有“title:hadoop”这样的词存在,否则是查不到结果的。
mm (Minimum Should Match)
默认操作符为AND时,mm的默认值是100%。默认操作符为OR时,mm的默认值为0%。
pf (Phrase Fields)
pf用来设置当某个字段匹配所有查询字句的短语时,该字段的加权权重。定义格式同qf
ps (Phrase Slop)
短语的移动因子。当查询给入多个词、短语时。对这些词进行短语匹配的移动因子。
qs (Query Phrase Slop)
用户给入的q 主查询中包含短语时,通过qs可指定短语的移动因子。
tie (Tie Breaker)
这个tie参数通常是一个小于1的浮点数,当查询命中多个field的时候,最终的score获得多少将由这个tie参数来进行调节。比如命中了field1,field2这2个field。如果field1.score= 10,field2.score=3。那么 score = 10 + tie * 3.也就是说,如果tie=1的话,最终的score就相当于多个字段得分总和;如果tie=0,那么最终的score就相当于是命中的field的最高分。通常情况下呢,官方推荐tie=0.1。
bq (Boost Query)
bq指定一个加权查询,当主查询中命中的文档符合bq加权查询时,将获得更高的得分。可以指定多个bq参数。
q=cheese&bq=date:[NOW/DAY-1YEAR TO NOW/DAY]^5.0
bf (Boost Functions)
bf用来定义加权函数,然后可在bq中使用加权函数,可用bq替代
以下两种写法等效
bf=recip(rord(creationDate),1,1000,1000)
bq={!func}recip(rord(creationDate),1,1000,1000)
5.Extended DisMax Query Parser
特点:
支持的语法很丰富
很好的容错能力
灵活的加权评分设置
参数说明:
sow
sow:Split on whitespace 按空格拆分。如果设置为true,则对每个单独的空格分隔的文本分别调用文本分析。默认是false。
mm.autoRelax
如设置为true,最少匹配数自动放松。此参数用于解决因停用词过滤导致的查询不到文档的问题。
慎用!可能会影响搜索的准确度,取决于索引内容。
boost
加权函数查询,符合该查询的文档的得分=主查询得分 * 该加权查询得分。注意这里是乘。而 bq、bf是加
lowercaseOperators
支持小写的 and or。
ps (Phrase Slop)
作为短语加权匹配时的移动因子,会作为 ps2 ps3的默认值。
pf2
指定要进行二元组短语加权的字段及权重,类似pf,不同之处:pf要求短语完整匹配,pf2是将查询的词分解为二元组进行短语匹配来加权。如查询的词为:solr based lucece java,pf2会对包含短语”solr based”或”based lucene”或”lucene java”的文档加权。
ps2
pf2使用的移动因子,如未给定则使用ps。
pf3
指定要进行三元组短语查询加权的字段及权重。
如查询的词为:solr based lucece java 会被分为” solr based lucece”、” based lucece java”,在pf3指定的字段上进行短语匹配、加权。
ps3
pf3使用的移动因子,如未给定则使用ps。
stopwords
控制是否在查询的分析器中开启停用词过滤,默认是true。
uf
指定允许用户可以查询哪些字段以及开启嵌入查询支持,默认是所有字段。uf值支持通配符,以空格间隔多个字段。
示例:
- To allow only title field, use
uf=title
. - To allow title and all fields ending with ‘_s’, use
uf=title *_s
. - To allow all fields except title, use uf=
* -title
. - To disallow all fielded searches, use uf=
-*
. - To allow embedded Solr queries (e.g. query:"…" or val:"…" or {!lucene …}), you must expressly enable this by referring to the magic field query in uf
查询字段别名
在eDismax中提供了查询字段别名机制,我们可以通过 f.alias.qf=realField参数来定义别名。
示例:
defType=edismax&q=title:”lucene solr”&f.title.qf=title_t_zh
因是对查询字段取别名,可以更灵活的使用:
f.who.qf=name^20
6.函数
solr查询也可使用函数,可用来过滤文档、提高相关性值、根据函数计算结果进行排序、以及返回函数计算结果。在标准查询解析器、dismax、edismax中都可以使用函数。
函数的使用方式有:
-
用作函数查询,查询参数值是一个函数表达式,来计算相关性得分或过滤
q={!func}div(popularity,price)&fq={!frange l=1000}customer_ratings
-
在排序中使用
sort=div(popularity,price) desc, score desc
-
在结果中使用
fl=sum(x, y),id,a,b,c,score
-
在加权参数 bf、boost中使用来计算权重
q=dismax&bf="ord(popularity)^0.5 recip(rord(price),1,1000,1000)^0.3
-
在设置评分计算函数的特殊关键字 val 中使用
q=_val_:mynumericfield _val_:"recip(rord(myfield),1,2,3)"
Function Query 函数查询说明
函数查询:指我们在查询参数q、fq中使用了函数来改变相关性得分或过滤的一类特殊查询。函数对所有匹配的文档分别进行计算得到一个值作为一个加分值,加入到文档的相关性得分中。
改变评分:
方式一:整个查询就是一个函数表达式,匹配所有文档,文档的得分就是函数值
q={!func}div(popularity,price)
说明:{!func} 说明q参数需要用func查询解析器来解析,func:Function Query Parser
方式二:值挂接,加入一个评分项,文档的得分=其他关键字得分 + 函数值
q=ipod AND _val_:"div(popularity,price)"
方式三:查询解析器挂接(显式嵌套查询)
q=ipod AND _query_:"{!func}div(popularity,price)"
方式四:查询解析器挂接(隐式嵌套查询)
q=ipod AND {!func v ="div(popularity,price)"}
通过函数来过滤文档:
如果需要对搜索结果进行过滤,只留下函数计算产生特定值的文档,可以选择函数区间解析器(Function Range query parser,简称frange)。在q/fq参数中应用frange 执行一个特定的函数查询,然后过滤掉函数值落在最低值和最高值范围之外的文档。l是最小值,u是最大值
q={!frange l=0.01 u=0.1}div(popularity,price)
q=ipod&fq={!frange l=0.05 u=0.1}div(popularity,price)
部分函数说明详见最后附录。
Solr中提供的函数:
官网参考:http://lucene.apache.org/solr/guide/7_6/function-queries.html
7.本地参数
作为查询参数值的前缀,用来为查询参数添加元数据说明用的参数。形式 {!key=value key=value}
示例:
指定 AND 组合、默认查询字段是title、使用dismax解析器
q={!q.op=AND df=title type=dismax}solr rocks
二、facet查询
在搜索结果的基础上进行按指定维度的统计,以展示搜索结果的另一面信息。
通用查询参数
facet:true/false 对当前搜索是否启用分面
facet.query:指定一个额外的分面查询语句
q=*:*&facet=true&facet.query=price:[* TO 5}&facet.query=price:[5 TO 10}&facet.query=price:[10 TO 20}&facet.query=price:[20 TO 50}&facet.query=price:[50 TO *]
注意:如果要让Solr对字符串执行分析(用于搜索)和构面,请使用copyField创建字段的两个版本:一个Text和一个String。确保两者都是indexed=“true”。
除非另有说明,否则以下所有参数都可以使用以下语法指定: f.<fieldname>.facet.<parameter>
1.字段(field) facet
常用查询参数
facet.field:指定对哪个字段进行分面计算。该参数可以多次指定以返回多个字段分面。字段需是索引字段。
facet.sort:分面结果的排序方式:count:根据统计数量排,index: 索引的词典顺序
facet.limit:确定每个分面返回多少个唯一分面值。可取值:整数>=-1,-1表示不限制,默认100。
facet.offset:对分面值进行分页,指定页偏移。 默认0。
facet.prefix:指定限制字段分面值必须以xxx开头,用以筛选分面值。
facet.missing:true/false,是否在分面字段中返回所有不包含值(值为缺失)的文档计数。
facet.mincount:指定分面结果中的分面值的统计数量>=mincount的才返回。
除facet.field以外的字段指定方式: f.filedname.facet.sort=count
2.区间(range) facet
区间分面将数值或时间字段值分成一些区间段,按区间段进行统计。
查询参数
facet.range:指定对哪个字段计算区间分面。可多次用该参数指定多个字段。
facet.range.start:起始值f.lastModified_dt.facet.range.start=NOW/DAY-30DAYS
facet.range.end:结束值f.lastModified_dt.facet.range.end=NOW/DAY+30DAYS
facet.range.gap:间隔值,创建子区间。对于数值字段,间隔值是数值,对于时间字段,用时间数学表达式(如+1DAY、+2MONTHS、+1HOUR等)f.lastModified_dt.facet.range.gap=+1DAY
facet.range.hardend:如果间隔在下限和上限之间不是均匀分布,最后一个区间的大小要小于其他区间,当该参数为true时,最后区间的最大值就是上限值,如果为false,则最后区间会自动上扩,与其他区间等长。
facet.range.other:区间外的值是否统计,可选值:
- before: 统计<start的文档数
- after:统计>end的文档数
- between:统计区间内的
- none:不统计
- all:统计before、after、between的数量
facet.range.include:区间的边界是如何包含的,默认是包含下边界值,不包含上边界值 (最后一个区间包含) 。可选值:
- lower:所有区间包含下边界
- upper:所有区间包含上边界
- edge:第一个区间包含下边界,最后一个区间包含上边界。
- outer:范围前“before” 和 后 “after”的统计区间将包含范围的下边界、上边界。即使范围内的区间包含了上、下边界。
- all:选择所有选项:lower、upper、edge、outer
facet.mincount 也可使用。
注意:避免重复统计,不要同时选择lower和upper、outer、all
3.多级(pivot) facet
查询参数
facet.pivot:指定分级字段,以逗号间隔。可多个facet.pivot参数指定多个分级分面。 facet.pivot=cat,popularity,inStock&facet.pivot=cat,inStock
facet.pivot.mincount:最少匹配文档数,默认1。
更多关于pivot facet 的用法详见官方文档。
字段分面、区间分面的返回结果以字段名为分面名,查询分面是以查询为分面名,Key 关键字用来对分面项进行重命名。
q=*:*&facet=true&facet.query={!key="<5"}price:[* TO 5}&facet.query={!key="5-10"}price:[5 TO 10}&facet.query={!key="10-20"}price:[10 TO 20}&facet.query={!key="20-50"}price:[20 TO 50}&facet.query={!key=">50"}price:[50 TO *]&facet.field={!key="类别"}cat
三、高亮显示
在搜索结果展示中突出显示搜索的关键字。
参数说明
hl:是否启用高亮,默认false 。
hl.fl:要高亮处理的字段列表,可以逗号、空格间隔,可以使用通配符*
hl.tag.pre :高亮前缀,可以是任意字符串,一般为html、xml标签,默认是<em>
hl.tag.post:高亮后缀,默认是</em>
hl.encoder :对字段值进行何种编码处理,默认空,不做处理。如果指定为html,会对字段值中的html字符进行编码处理:如 < 转为 < & 转为 &
hl.maxAnalyzedChars :对字段值的最多多少个字符进行高亮处理,默认值51200 个字符。
hl.snippets :一个字段中可以有几个高亮片段,默认1。
hl.fragsize :高亮片段的最大字符数,默认100,无上限。
hl.method:指定高亮的实现方式,可选值: unified, original, fastVector。默认是 original。
hl.q:如果你要高亮的词不是主查询中的词,可通过此参数指定
hl.qparser:指定hl.q的查询解析器
hl.requireFieldMatch:是否只是查询用到的字段才高亮,默认false(hl.fl中指定的字段进行高亮处理)。
hl.usePhraseHighlighter:使用短语高亮,默认true。
hl.highlightMultiTerm:对于通配符查询(多词项查询)是否高亮,默认true
默认示例techproducts里有如下配置:
四、拼写检查 和 查询建议
1.拼写检查
拼写检查解决因查询词拼写错误导致搜索结果不佳的问题。针对用户拼写错误或关键字生僻,而搜索结果不佳,提供相近的词建议。
Solr中提供的拼写检查实现类:
IndexBasedSpellChecker 独立拼写检查索引的实现方式
DirectSolrSpellChecker 使用solr主索引进行拼写检查
FileBasedSpellChecker 通过文件来提供拼写检查推荐词的实现方式
WordBreakSolrSpellChecker 可灵活拆分、组合词,基于主索引的实现方式。
拼写检查组件配置
在solrconfig.xml中配置拼写检查组件,并在查询请求处理器中配置使用拼写检查组件(往往作为最后一个组件)。
一个拼写检查器就是一种拼写检查实现方式。由name、classname、field、其他参数构成。 在一个组件中可定义拼写检查器(字典),供查询处理器选择使用(可同时选择多个,推荐结果为多个的执行结果的合并)。
DirectSolrSpellChecker配置参数说明
<searchComponent name="spellcheck" class="solr.SpellCheckComponent">
<lst name="spellchecker">
<str name="name">default</str>
<!-- 通过查询哪个字段的词项来提供推荐词 -->
<str name="field">text</str>
<str name="classname">solr.DirectSolrSpellChecker</str>
<!-- 使用的拼写检查度量方法, 默认值internal 使用的是 levenshtein距离算法-->
<str name="distanceMeasure">internal</str>
<!-- 一个词被认定为推荐词需满足的最低精确度。
0-1之间的浮点数,值越大精确度越高,推荐的词越少 -->
<float name="accuracy">0.5</float>
<!-- 允许的最大编辑数(即最多多少个字符不同),可取值:1、2. -->
<int name="maxEdits">2</int>
<!-- 枚举词项来推荐时,要求的最低相同前缀字符数。-->
<int name="minPrefix">1</int>
<!-- 返回的最大推荐词数。默认5 -->
<int name="maxInspections">5</int>
<!-- 要求的查询词项的最低字符数,默认4.
查询的词的字符数小于这个数就不进行拼写检查推荐。-->
<int name="minQueryLength">4</int>
<!-- 最大文档频率,高于该值的查询词不进行拼写检查的。可以是百分比或绝对值 -->
<float name="maxQueryFrequency">0.01</float>
<!-- 要求推荐词的文档频率高于这个值。可以是百分比或绝对值
<float name="thresholdTokenFrequency">0.01</float>
-->
</lst>
</searchComponent>
在查询请求处理器中配置使用拼写检查
<requestHandler name="/spell" class="solr.SearchHandler" startup="lazy">
<lst name="defaults">
<str name="spellcheck.dictionary">default</str>
<str name="spellcheck">on</str>
<str name="spellcheck.extendedResults">true</str>
<str name="spellcheck.count">10</str>
<str name="spellcheck.alternativeTermCount">5</str>
<str name="spellcheck.maxResultsForSuggest">5</str>
<str name="spellcheck.collate">true</str>
<str name="spellcheck.collateExtendedResults">true</str>
<str name="spellcheck.maxCollationTries">10</str>
<str name="spellcheck.maxCollations">5</str>
</lst>
<arr name="last-components">
<str>spellcheck</str>
</arr>
</requestHandler>
拼写检查常用请求参数说明
spellcheck:是否使用拼写检查,true/false
spellcheck.q:进行拼写检查的查询表达式,如未指定则使用q。
spellcheck.count:返回最多多少个推荐词。默认1。
spellcheck.dictionary:指定使用的拼写检查器(字典)名,默认default,同时使用多个则多次传参指定。
spellcheck.accuracy:一个词被认定为推荐词需满足的最低精确度。0-1之间的浮点数,值越大精确度越高,推荐的词越少
spellcheck.onlyMorePopular:如果设置为true,只返回比原始查询词文档频率更高的推荐词。
spellcheck.maxResultsForSuggest:原始查询匹配的文档数低于多少时才应该进行推荐,这个参数指定这个阀值。
spellcheck.extendedResults:true,获取推荐词的其他信息,如文档频次
spellcheck.collate:true、指示solr为原始查询生成一个最佳的校对查询供用户使用。如输入“jawa class lording”,校对推荐"java class loading“
spellcheck.maxCollations:最多生成多少个校对查询。默认1
spellcheck.build:true,则solr会为拼写检查建立字典(如未建立)。directSolrSpellCheck不需要此选项。
spellcheck.reload:是否重新加载spellchecker(以重新加载拼写检查字典)
拼写检查请求示例
对solr示例集合techproducts使用以下查询:
http://localhost:8983/solr/techproducts/spell?df=text&spellcheck.q=delll+ultra+sharp&spellcheck=true&spellcheck.collateParam.q.op=AND&wt=xml
注:拼写检查对中文极不友好,尤其使用自定义分词器的字段。有兴趣的同学可以自行查找详细配置方式,我尝试下来觉得效果并不好。
2.查询建议
拼写检查是查询后返回推荐结果,自动建议查询词则在用户输入查询词就根据用户的输入给出建议查询词,提供更好的用户体验。
Solr中可以基于拼写检查组件实现自动建议查询。也提供了专门的建议查询组件solr.SuggestComponent。在techproducts示例的内核配置文件solrconfig.xml中配置有建议查询词组件和请求处理器,可直接参考。
配置示例:
<searchComponent name="suggest" class="solr.SuggestComponent">
<!-- 可以定义多个建议器 -->
<lst name="suggester">
<!-- 建议器名称 -->
<str name="name">mySuggester</str>
<!-- 建议字典的查找实现类,默认 JaspellLookupFactory -->
<str name="lookupImpl">FuzzyLookupFactory</str>
<!-- 建议字典的实现类,默认 HighFrequencyDictionaryFactory -->
<str name="dictionaryImpl">DocumentDictionaryFactory</str>
<!-- 基于哪个字段来提供建议查询词 -->
<str name="field">cat</str>
<!-- 指定权重字段 -->
<str name="weightField">price</str>
<!-- 指定要使用的分词器(通过fieldType)
<str name="suggestAnalyzerFieldType">string</str>
<!-- 是否在solr启动时就构建字典 -->
<str name="buildOnStartup">false</str>
</lst>
</searchComponent>
<requestHandler name="/suggest" class="solr.SearchHandler" startup="lazy">
<lst name="defaults">
<str name="suggest">true</str>
<str name="suggest.count">10</str>
</lst>
<arr name="components">
<str>suggest</str>
</arr>
</requestHandler>
详细参数、参数选项请参考: http://lucene.apache.org/solr/guide/7_6/suggester.html
常用查询参数:
suggest :是否使用建议查询词,true/false
suggest.q:进行建议查询词用的查询表达式。
suggest.count:返回最多多少个推荐词。
suggest.dictionary:指定使用的建议器(字典)名,必需。
suggest.build:true,构建建议字典索引,在初次请求时构建,后续请求不需要带这个参数。
suggest.reload:是否重新加载建议查询词索引。
请求示例:
http://localhost:8983/solr/techproducts/suggest?suggest=true&suggest.build=true&suggest.dictionary=mySuggester&suggest.q=elec
响应示例:
{
"responseHeader": {
"status": 0,
"QTime": 35
},
"command": "build",
"suggest": {
"mySuggester": {
"elec": {
"numFound": 3,
"suggestions": [
{
"term": "electronics and computer1",
"weight": 2199,
"payload": ""
},
{
"term": "electronics",
"weight": 649,
"payload": ""
},
{
"term": "electronics and stuff2",
"weight": 279,
"payload": ""
}
]
}
}
}
}
注:我在对中文做查询建议的时候,suggestAnalyzerFieldType设置为中文分词字段会无效。设置为string时有效。对我的场景也并不好用,不如单独建一套索引专门用作查询建议。
五、结果分页
1.基本分页
在Solr中,使用start和rows参数能做到基本的分页搜索,并且可以通过调整queryResultCache
和queryResultWindowSize
配置来影响性能。
但是这种分页存在一个缺点,当从Solr中获取大量排序结果时,对start或者rows参数使用非常大的值可能会非常低效,这一点有点类似MySQL的limit(个人理解,没有专门研究limit的底层原理)。
比如说请求start=0&rows=1000000
显然效率低下,因为它要求Solr在内存中维护和排序一百万个文档。但start=999000&rows=1000
同样效率低下。因为Solr无法计算出哪个文档是按排列顺序排在第999001个的结果,因而需要遍历前999000个结果才能找到我们需要的部分的开头位置。对于MySQL的limit也一样,在我的实践中limit 2000000,1000
(2.8s),limit 1000000,1000
(1.6s)要显著慢于limit 0,1000
(0.016s)。
如果索引是分布式的,那这一现象会更加严重。如果从每个分片中检索一百万个文档,对于十个分片的索引,必须检索并排序一千万个条目,才能找出与那些查询参数匹配的1000个文档。
2.游标
Solr中的游标是一个逻辑概念,它不涉及在服务器上缓存任何状态信息。而是返回给客户端的最后一个文档的排序值用于计算“标记”,该“标记”表示排序值的有序空间中的逻辑点。可以在后续请求的参数中指定该“标记”,以告诉Solr从何处继续。
将游标与Solr一起使用,要指定一个cursorMark值为*的参数。作用有二:①start=0,②使用游标。
Solr的响应将包含一个nextCursorMark。提取它并将其作为下一个请求的cursorMark参数传递回Solr ,就可以继续获取后续的文档。直到你得到了足够的文档,或者结果已经完全返回了,此时返回的nextCursorMark与传递的cursorMark相同。
伪代码如下:
while (! done) {
q.set(CursorMarkParams.CURSOR_MARK_PARAM, cursorMark);
QueryResponse rsp = solrServer.query(q);
String nextCursorMark = rsp.getNextCursorMark();
boolean hadEnough = doCustomProcessingOfResults(rsp);
if (hadEnough || cursorMark.equals(nextCursorMark)) {
done = true;
}
cursorMark = nextCursorMark;
}
使用游标时的约束:
- cursorMark和start 互斥,用游标时请求必须不包含start参数,或者使用值“ 0” 指定。
- sort子句必须包含uniqueKey字段。
- 涉及NOW的时间计算的排序将引起混乱的结果,因为每个文档都会在每个后续请求中获得新的排序值。这可能导致游标永无休止,并且不断地反复返回相同的文档。
光标标记值是基于结果中每个文档的排序值计算的,这意味着具有相同排序值的多个文档,如果其中一个是结果页面上的最后一个文档,在这种情况下,使用该请求的后续请求cursorMark将不知道应跳过哪些具有相同标记值的文档。所以要求将uniqueKey字段用作排序条件中的子句可确保将返回确定的顺序,并且每个cursorMark值都将标识文档序列中的唯一点。
游标追踪:
如果使用类似自增主键、时间戳的排序规则,可以实现对文档的更新做监控。通过不断的使用游标,可以不断获取到最后的一批文档,然后做业务操作。
伪代码如下:
while (true) {
$doneForNow = false
while (not $doneForNow) {
$results = fetch_solr($params)
// do something with $results
if ($params[cursorMark] == $results[nextCursorMark]) {
$doneForNow = true
}
$params[cursorMark] = $results[nextCursorMark]
}
sleep($some_configured_delay)
}
3.索引更新对于基本分页和游标分页的影响
索引更新可能会导致前后两次请求会 包含重复结果 或者 漏掉部分结果,但是对游标分页的影响要远小于基本分页。
具体示例,详见官方文档
http://lucene.apache.org/solr/guide/7_6/pagination-of-results.html
六、结果折叠和展开
折叠结果:就是对搜索结果根据某字段的值进行分组去重。
展开结果:在返回结果中附带上折叠结果的展开列表
默认示例techproducts:
http://localhost:8983/solr/techproducts/select?q=*:*&fq={!collapse%20field=price}&expand=true
折叠解析器常用参数:
field:指定折叠字段,必须是单值的String 、int 、float 类型的字段。
min or max:通过min或max指定的数值字段或函数查询来选择每个组的头文档(取最大或最小值的文档)。
sort:指定组内排序规则来选择排在第一的文档作为头文档。默认是选取组中相关性评分最高的文档作为头文档。
min、max、sort只可用其一。
nullPolicy:对不包含折叠字段的文档采取什么处理策略:
- ignore:忽略,默认选项。
- expand:独立为一个组。
- collapse:折叠为一个组。
展开解析器常用参数:
如果你需要在结果中返回每个折叠组的展开列表,在请求中加上参数 expand=true
expand.sort:组内排序规则,默认是相关性评分。
expand.rows:每组返回的文档数。默认5
注:目前我使用的业务场景暂时用不到这个功能,就没仔细看←。→
七、结果分组
根据某个字段对结果进行分组,每组返回几个文档(默认一个)。
常用参数说明:
group :true,对搜索结果进行分组。
group.field :分组字段,必须是单值、索引的字段。
group.func:根据函数查询结果值进行分组(分布式下不可用)。
group.query:指定分组的查询语句,类似 facet.query。
rows:返回的分组数,默认10
start:分页起始行
group.limit:每组返回的文档数,默认1。
group.offset:组内返回的文档的偏移量。
sort:如何排序组。
group.sort:组内排序规则
group.main:用分组结果中的文档作为主结果返回
默认示例:
http://localhost:8983/solr/techproducts/select?fl=id,name&q=solr+memory&group=true&group.field=manu_exact
http://localhost:8983/solr/techproducts/select?fl=id,name,manufacturer&q=solr+memory&group=true&group.field=manu_exact&group.main=true
http://localhost:8983/solr/techproducts/select?indent=true&fl=name,price&q=memory&group=true&group.query=price:[0+TO+99.99]&group.query=price:[100+TO+*]&group.limit=3
group和facet的区别:
facet的查询结果主要是分组信息:有什么分组,每个分组包括多少记录。但是分组中有哪些数据是不可知道的,只有进一步搜索。
group则类似于关系型数据库的group by,可以用于一个或者几个字段去重、显示一个group的某几条记录等。
八、词项(terms)组件
terms component,可以基于词项做统计功能,不受基本查询或任何过滤器的限制,效率很高。
常用参数:
terms
如果设置为true,则启用术语组件。默认情况下,术语组件处于关闭状态(false)。
terms.fl
指定从中检索术语的字段。
terms.list
获取以逗号分隔的术语列表的文档频率。术语始终按索引顺序返回。如果terms.fl定义了多个,将在每个请求的字段中为每个术语返回这些统计信息。
例: terms.fl=price&terms.fl=name&terms.list=7.99
{
responseHeader: {
status: 0,
QTime: 0
},
terms: {
price: {
7.99: 4 //有4个文档的price为7.99
},
name: { } //没有文档的name包含"7.99"词项
}
}
terms.limit
指定要返回的最大术语数。默认值为10。如果限制设置为小于0的数字,则不会强制执行最大限制。
terms.mincount
指定为了使术语包含在查询响应中而要返回的最小文档频率。
terms.maxcount
指定术语必须包含在查询响应中的最大文档频率。默认设置为-1,不设置上限。
terms.prefix
将匹配限制为以指定字符串开头的字词。
terms.regex
将匹配项限制为与正则表达式匹配的项。
terms.sort
定义如何对返回的术语进行排序。有效选项为count,按词条频率排序,最高的词条频率排在最前面;或index,按索引顺序排序。
示例:
http://localhost:8983/solr/techproducts/terms?terms.fl=name&wt=xml
<response>
<lst name="responseHeader">
<int name="status">0</int>
<int name="QTime">2</int>
</lst>
<lst name="terms">
<lst name="name">
<int name="one">5</int>
<int name="184">3</int>
<int name="1gb">3</int>
<int name="3200">3</int>
<int name="400">3</int>
<int name="ddr">3</int>
<int name="gb">3</int>
<int name="ipod">3</int>
<int name="memory">3</int>
<int name="pc">3</int>
</lst>
</lst>
</response>
注:也可以用这个组件做自动建议,利用prefix参数。我没在生产中用过这个组件,感觉没啥用←。→
九、统计组件
直接先上示例:http://localhost:8983/solr/techproducts/select?q=*:*&wt=xml&stats=true&stats.field={!func}termfreq('text','memory')&stats.field=price&rows=0&indent=true
<lst name="stats">
<lst name="stats_fields">
<lst name="termfreq(text,memory)">
<double name="min">0.0</double>
<double name="max">3.0</double>
<long name="count">32</long>
<long name="missing">0</long>
<double name="sum">10.0</double>
<double name="sumOfSquares">22.0</double>
<double name="mean">0.3125</double>
<double name="stddev">0.7803018439949604</double>
<lst name="facets"/>
</lst>
<lst name="price">
<double name="min">0.0</double>
<double name="max">2199.0</double>
<long name="count">16</long>
<long name="missing">16</long>
<double name="sum">5251.270030975342</double>
<double name="sumOfSquares">6038619.175900028</double>
<double name="mean">328.20437693595886</double>
<double name="stddev">536.3536996709846</double>
<lst name="facets"/>
</lst>
</lst>
</lst>
查询参数:
stats
如果为true,则调用Stats组件。
stats.field
指定应为其生成统计信息的字段。可以在查询中多次调用此参数,以请求多个字段的统计信息。
响应字段说明:
min:最小值
max:最大值
sum:总和
count:包含字段的文档数
missing:不含字段的文档数
sumOfSquares:平方和
mean:平均值
stddev:标准差
支持的局部参数:
标记和排除过滤器: stats.field={!ex=filterA}price
更改输出键: stats.field={!key=my_price_stats}price
和pivot facet结合使用:stats.field={!tag=my_pivot_stats}price
局部参数示例:
http://localhost:8983/solr/techproducts/select?q=*:*&fq={!tag=stock_check}inStock:true&stats=true&stats.field={!ex=stock_check+key=instock_prices+min=true+max=true+mean=true+percentiles='90,99'}price&stats.field={!key=all_prices}price&rows=0&indent=true&wt=xml
<lst name="stats">
<lst name="stats_fields">
<lst name="instock_prices">
<double name="min">0.0</double>
<double name="max">2199.0</double>
<double name="mean">328.20437693595886</double>
<lst name="percentiles">
<double name="90.0">564.9700012207031</double>
<double name="99.0">1966.6484985351556</double>
</lst>
</lst>
<lst name="all_prices">
<double name="min">0.0</double>
<double name="max">2199.0</double>
<long name="count">12</long>
<long name="missing">5</long>
<double name="sum">4089.880027770996</double>
<double name="sumOfSquares">5385249.921747174</double>
<double name="mean">340.823335647583</double>
<double name="stddev">602.3683083752779</double>
</lst>
</lst>
</lst>
结合pivot facet:
参数:
stats=true
stats.field={!tag=piv1,piv2 min=true max=true}price
stats.field={!tag=piv2 mean=true}popularity
facet=true
facet.pivot={!stats=piv1}cat,inStock
facet.pivot={!stats=piv2}manu,inStock
响应:
{"facet_pivot":{
"cat,inStock":[{
"field":"cat",
"value":"electronics",
"count":12,
"pivot":[{
"field":"inStock",
"value":true,
"count":8,
"stats":{
"stats_fields":{
"price":{
"min":74.98999786376953,
"max":399.0}}}},
{
"field":"inStock",
"value":false,
"count":4,
"stats":{
"stats_fields":{
"price":{
"min":11.5,
"max":649.989990234375}}}}],
"stats":{
"stats_fields":{
"price":{
"min":11.5,
"max":649.989990234375}}}},
{
"field":"cat",
"value":"currency",
"count":4,
"pivot":[{
"field":"inStock",
"value":true,
"count":4,
"stats":{
"stats_fields":{
"price":{
"..."
"manu,inStock":[{
"field":"manu",
"value":"inc",
"count":8,
"pivot":[{
"field":"inStock",
"value":true,
"count":7,
"stats":{
"stats_fields":{
"price":{
"min":74.98999786376953,
"max":2199.0},
"popularity":{
"mean":5.857142857142857}}}},
{
"field":"inStock",
"value":false,
"count":1,
"stats":{
"stats_fields":{
"price":{
"min":479.95001220703125,
"max":479.95001220703125},
"popularity":{
"mean":7.0}}}}],
"..."}]}}}}]}]}}
十、近实时(NRT)搜索
近实时(NRT)搜索意味着对文档进行索引后即可立即进行搜索。NRT搜索是SolrCloud的主要功能之一,很少在主/从配置中尝试过。
文件的耐久性和可搜索性由来控制commits。“ Near Real Time”中的“ Near”是可配置的,以满足您的应用程序的需求。提交可以是“硬”提交,也可以是“软”提交,可以由客户端(例如SolrJ)通过REST调用来发出,也可以配置为在中自动发生solrconfig.xml。通常给出的建议是在中配置提交策略solrconfig.xml(请参见下文),并避免在外部发出提交。
通常在NRT应用程序中,硬提交配置为openSearcher=false,软提交配置为使文档可见以进行搜索。
发生提交时,将启动各种后台任务,例如段合并。这些后台任务不会阻止对索引的其他更新,也不会延迟文档搜索的可用性。
配置NRT时,请特别注意缓存和自动热设置,因为它们会对NRT性能产生重大影响。对于极短的autoCommit间隔,请考虑完全禁用缓存和自动预热。
提交和搜索
一个硬提交电话fsync上的索引文件,以确保他们已经刷新到稳定存储器。当前事务日志已关闭,而新的日志已打开。有关在没有硬提交的情况下如何恢复数据的信息,请参见下面的“事务日志”讨论。可选地,硬提交也可以使文档可见以进行搜索,但是不建议将其用于NRT搜索,因为它比软提交更昂贵。
一个柔软的承诺是更快,因为它不仅使指数的变化可见和不fsync索引文件,开始一个新段或启动新的事务日志。具有NRT要求的搜索集合将希望经常进行软提交,以满足应用程序的可见性要求。softCommit可能比硬提交(openSearcher = true)“便宜”,但它不是免费的。建议根据应用需求合理地设置此时间。
硬提交和软提交都有两个主要配置参数:maxDocs和maxTime。
maxDocs
整数。定义激活前要处理的更新数量。
maxTime
整数。激活前要等待的毫秒数。
如果同时指定了这两个参数,则将第一个失效。通常,最好使用maxTime而不是maxDocs,尤其是在成批索引大量文档时。明智地使用maxDocs和maxTime调整提交策略。
硬提交有一个附加参数 openSearcher
openSearcher
true | false,是否使文档可见以进行搜索。对于NRT应用程序,通常将其设置为false,soft commit并配置为控制何时可见文档以进行搜索。
交易日志(日志)
事务日志是自上次硬提交以来更新的“滚动窗口”。每次发生各种硬提交时,当前事务日志都将关闭,并打开一个新的事务日志。软提交对事务日志没有影响。
启用tlog时,将在索引调用返回到客户端之前将要添加到索引的文档写入tlog。如果发生kill -9异常关闭(电源中断,JVM崩溃等),则在启动Solr时会重播任何写入日志但尚未通过硬提交提交的文档。因此,数据不会丢失。
正常关闭Solr(使用bin/solr stop命令)后,Solr将关闭tlog文件和索引段,因此启动时无需重播。
令人困惑的一点是,事务日志中包含多少数据。日志不包含所有文档,仅包含自上次硬提交以来的所有文档。不再需要较旧的事务日志文件时,将删除它们。
上面的隐含含义是,如果禁用硬提交,则事务日志将永远增长。因此,重要的是在索引编制时启用硬提交。
配置提交
如上所述,通常最好在内部配置您的提交(硬和软),solrconfig.xml并避免从外部源发送提交。检查您的solrconfig.xml文件,因为默认设置可能无法满足您的需求。这是针对两种提交方式的NRT配置示例,每60秒执行一次硬提交,每30秒进行一次软提交。请注意,这些不是某些示例中的值!
选择的时间autoSoftCommit决定了将文档发送到Solr之后,文档变为可搜索且不影响事务日志的最长时间。选择您的应用程序可以忍受的时间间隔,通常为15-60秒是合理的,甚至更长,具体取决于要求。在时间设置为非常短的时间间隔(例如1秒)的情况下,请考虑禁用缓存(尤其是queryResultCache和filterCache),因为它们的实用性很小。
其他
mlt,cluster,基于地理位置的查询,评分提升组件 等功能我都没有在生产用过,感觉有用的场景也不多,就没做研究。
参考资料:
solr官方文档:http://lucene.apache.org/solr/guide/7_6/index.html
附录
A.部分函数说明
1.数据转换函数
2.数学函数
3.相关性函数
示例: doc1:(fieldX:A B C) and doc2:(fieldX:A A A A):
docFreq(fieldX:A) = 2 (A appears in 2 docs)
freq(doc1, fieldX:A) = 4 (A appears 4 times in doc 2)
totalTermFreq(fieldX:A) = 5 (A appears 5 times across all docs)
sumTotalTermFreq(fieldX) = 7 in fieldX, there are 5 As, 1 B, 1 C
利用函数查询和相关性函数,我们就可以自定义相关性计算模型。
4.布尔函数