👨🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
🌌上期文章:详解SpringCloud微服务技术栈:ElasticSearch实践1——RestClient操作索引库与文档
📚订阅专栏:微服务技术全家桶
希望文章对你们有所帮助
之前已经使用了DSL实现了索引的增删改查以及文档的增删改,并且通过RestClient进行实现。
但是文档的查询操作很复杂,并且分类比较多,所以先用DSL语句进行各种查询操作的实现,再用RestClient实现各类查询。
DSL查询ElasticSearch文档
- DSL查询分类和基本语法
- 全文检索查询
- 精确查询
- 地理查询
- 复合查询
- 相关性算分
- Function Score Query
- Boolean Query
DSL查询分类和基本语法
ElasticSearch提供了基于JSON的DSL来定义查询,常见查询的分类:
1、查询所有:查询出所有数据,一般测试的时候用,例如match_all
2、全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配,例如:
(1)match_query
(2)multi_match_query
3、精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段,例如:
(1)ids
(2)range
(3)term
4、地理(geo)查询:根据经纬度查询,例如:
(1)geo_distance
(2)geo_bounding_box
5、复合(compound)查询:将上述各种查询条件组合起来,合并查询条件,例如:
(1)bool
(2)function_score
基本语法:
GET /indexName/_search
{
"query": {
"查询类型": {
"查询条件": "条件值"
}
}
}
如果是查询所有,那么就没有查询的条件,例如:
GET /hotel/_search
{
"query": {
"match_all": {
}
}
}
不过这种查询方法,最终显示的结果可能不会全部出来,避免压力过大,至于如何全部搜索出来,后续演示一下分页查询就好了。
全文检索查询
全文检索查询,会对用户输入的内容分词,常用于搜索框搜索:
match查询是全文检索查询的一种,会对用户输入内容分词,然后去倒排索引库检索,例如:
# 将外滩如家分词为外滩和如家进行检索,都包含的酒店排名最高
GET /hotel/_search
{
"query": {
"match": {
"all": "外滩如家"
}
}
}
multi_match与match相似,但是允许查询多个字段:
# 查询酒店的品牌和名称是否与“外滩如家”匹配
GET /hotel/_search
{
"query": {
"multi_match": {
"query": "外滩如家",
"fields": ["brand", "name"]
}
}
}
更推荐使用match,要查询的字段之前就已经全部放在all里面了。而multi_match要查询的字段太多,效率会更低下。
精确查询
精确查询一般是查找keyword、数值、日期、boolean等类型字段,不会
对搜索条件做分词。常见的2种:
(1)term:根据词条精确值查询
GET /hotel/_search
{
"query": {
"term": {
"city": {
"value": "上海"
}
}
}
}
(2)range:根据值的范围查询
# 查询 100<价格<=300 的酒店
GET /hotel/_search
{
"query": {
"range": {
"price": {
"gt": 100,
"lte": 300
}
}
}
}
地理查询
根据经纬度查询。常见应用场景包括携程、滴滴、微信等。
geo_bounding_box:选取两个经纬度坐标,并且根据这两个点做矩形,在矩形之内的酒店即为查询结果,比较适合用来查询一定范围内的信息。
geo_distance:查询到指定中心点小于某个距离值的所有文档。
GET /hotel/_search
{
"query": {
"geo_distance": {
"distance": "15km",
"location": "31.21, 121.5"
}
}
}
复合查询
复合查询就是把其它查询组合起来,实现更复杂的搜素逻辑
相关性算分
function score:算分函数查询,可以控制文档相关性算分,控制文档排名。如百度竞价。
简单的算分可以直接算词条的频率:
T
F
(词条频率)
=
词条出现次数
文档中词条总数
TF(词条频率)=\frac{词条出现次数}{文档中词条总数}
TF(词条频率)=文档中词条总数词条出现次数
但是当搜索内容里面包括多个词条时,词条就有权重之分了,使用TF-IDF算法:
I
D
F
(逆文档频率)
=
L
o
g
(
文档总数
包含词条的文档总数
)
s
c
o
r
e
=
∑
i
n
T
F
(词条频率)
∗
I
D
F
(逆文档频率)
IDF(逆文档频率)=Log(\frac{文档总数}{包含词条的文档总数})\\ score=\sum_i^nTF(词条频率) * IDF(逆文档频率)
IDF(逆文档频率)=Log(包含词条的文档总数文档总数)score=i∑nTF(词条频率)∗IDF(逆文档频率)
也就是说,这个词条在全部文档中出现的越多,它就越不值钱
当然,现在的ES已经不使用这种算法了,而采用了BM25算法:
S
c
o
r
e
(
Q
,
d
)
=
∑
i
n
l
o
g
(
1
+
N
−
n
+
0.5
n
+
0.5
)
⋅
f
i
f
i
+
k
1
⋅
(
1
−
b
+
b
∗
d
l
a
v
g
d
l
)
Score(Q,d) = \sum_i^nlog(1+\frac{N-n+0.5}{n+0.5}) · \frac{f_i}{f_i+k_1 · (1-b+b*\frac{dl}{avgdl})}
Score(Q,d)=i∑nlog(1+n+0.5N−n+0.5)⋅fi+k1⋅(1−b+b∗avgdldl)fi
这个算法具体是怎么样更优秀的大家可以自行推导,看起来复杂但是其实也没有特别的复杂,比起TF-IDF算法,BM25算法受大量词频的影响会小很多,在大量词频的情况下也相对更平滑:
因此需要记住ES中的相关性打分的算法有哪些,我看一些大厂面试也是会考的,甚至上面的公式也是要会的。
TF-IDF:在ES5.0之前,会随着词频增加而越来越大
BM25:在ES5.0之后,会随着词频的增加而增大,但增长曲线会趋于水平
Function Score Query
使用function score query,可以修改文档的相关性算分(从原有的相关性算法基础上修改),根据新得到的算分排序。
这里直接用例子来进行说明,假设用户要搜索带着“外滩”的酒店,但是因为品牌方为“如家”的酒店跟我是合作关系,我当然就希望查询结果中带“如家”的排名会靠前一点:
这只是个例子,其使用还有多种重新算分的方法,需要掌握如下几点:
1、query:原始查询,搜索文档并根据相关性来打分(BM25算法),得到
query score
2、filter:过滤条件,符合条件的文档才会被重新算分
3、weight:算法函数,算分函数的结果成为function score
,将来会与query score运算,得到新算分,常见算分函数:
(1)weight:给一个常量值,作为function score
(2)field_value_factor:将文档中的某个字段值,作为function score
(3)random_score:随机生成一个值,作为function score
(4)script_score:自定义计算公式,公式结果作为function score
4、boost_mode:加权模式,定义query score
和function score
的运算方式:
(1)multiply:默认方式,相乘
(2)replace:用function score替换query score
(3)其他:sum、avg、max、min
总结:
function score query定义的三要素:
(1)过滤条件:决定哪些文档要加分
(2)算法函数:如何计算function score
(3)加权方式:function score与query score如何运算
Boolean Query
布尔查询与function score query不一样,function score query是在原来的算分基础上修改算分。而Boolean Query不会修改算分是一个或多个查询子句的组合。子查询的组合方式有:
must:必须匹配每个子查询,类似“与”
should:选择性匹配子查询,类似“或”
must_not:必须不匹配,不参与算分,类似“非”
filter:必须匹配,不参与算分
must_not和filter可以解决一个问题,子查询过多的时候,参与算分太多就会影响性能,而must_not和filter只会返回匹配和不匹配,却不去具体算分,同时因为几乎没有啥运算,所以ES底层会将其放到缓存中去,因此这两种方法会大大提升性能。例如用户要过滤出酒店价格低于500的,那么只需要将符合条件的呈现就行,没必要去再给价格算分后排个序。
这样的方法常见于搜索中下的筛选按钮,比如筛选出品牌、酒店的星级等(filter或must_not),高效率的先筛选出一部分符合条件的酒店,然后再去搜索框中搜索(must),从而提高整体效率。
例如:搜索名字包含“如家”,价格不高于400,在坐标31.21,121.5周围10km范围内的酒店。