关于elasticsearch7.8搜索方式及实景应用

一、关于elasticsearch7.8的搜索方式与实景应用(上)

​ 本文是在elasticsearch7.8的环境下操作的,最近也很忙没时间去整理,终于肝了一篇出来。关于ES,接触的很早,了解他却很迟,有种相恨见晚的感觉,最近在项目上又一次碰见了它,不由得再来感叹一遍。

1、问题初始
1.1、应用场景:

在这里插入图片描述
​ 这是某网站的一个搜索,在左侧有一个分类的过滤字段。当时我们公司的业务网站也是出现了这样的一个多条件过滤搜索的情况。第一时间想到的是MySQL的条件查询…后来发现数据量很大,还得要有多字段查询,精准过滤的同时,还要支持全文搜索等等。“全文搜索”,那就不得不提一下ES。

1.2、组合查询-bool查询

​ bool查询是本次在项目里主要用到的查询方式,首先是与4种操作符的组合查询,类似于and,or,not。布尔查询允许我们利用布尔逻辑将较小的查询组合成较大的查询

must:字面意思就是必须匹配,相当于and,贡献算分。

must_not:过滤子句,必须不匹配,相当于not,这个相对来说用的比较少,不贡献算分。

should:选择性满足一个条件即可,相当于or,贡献算分。

filter:过滤子句,必须匹配,不贡献算分。

官方的说法是:bool 查询会为每个文档计算相关度评分 _score ,再将所有匹配的 mustshould 语句的分数 _score 求和,最后除以 mustshould 语句的总数。

must_not 语句不会影响评分;它的作用只是将不相关的文档排除。

所有 must 语句必须匹配,所有 must_not 语句都必须不匹配,但有多少 should 语句应该匹配呢?默认情况下,没有 should 语句是必须匹配的,只有一个例外:那就是当没有 must 语句的时候,至少有一个 should 语句必须匹配。

就像我们能控制 match 查询的精度 一样,我们可以通过 minimum_should_match 参数控制需要匹配的 should 语句的数量,它既可以是一个绝对的数字,又可以是个百分比:

知道了这层关系,就可以接着往下写了。具体格式的写法有好几种。最简单的是如下这种。举例上述图形中的过滤条件。

body={
	"query":{
		"bool":{
			"must":[
				{
					"match":{
						"行业分类":"建筑工程"
					}
				},
                {
					"match":{
						"省份地区":"广东省"
					}
				},
                {
					"match":{
						"招标结果":"开标"
					}
				},
                {
                    "range":{
                        "发布时间":{
                            #近三天,当前日期2020-07-20
                            "lte":"2020-07-20",
                            "gte":"2020-07-17"
                        }
                    }
                },
                {
                    "range":{
                    	"预算金额":{
                            "lte":"50",
                            "gte":"20"
                        }
                    }
                },
                
                #这里添加正常的字段搜索
                {
                    "bool":{
                        "should":[
                            {"match":{"某个字段":"某个关键词"}},
                            {"match":{"某个字段":"某个关键词"}},
                            {"match":{"某个字段":"某个关键词"}},
                            ......
                        ]
                    }
                }
                
			]
		}
	}
}

理解

​ 对于普通的过滤条件就直接must \ must_not -match,对于范围的过滤条件就要用到range \lte(小于等于)\gte(大于等于)

​ 这就是经典的and | or 复合查询。

​ 通常情况下,需要对结果进行过滤条件的符合查询时,可以通过mustmust_not,再获取匹配的文档,偶尔情况下,我们呢,可能不需要完全剔除这样的一个过滤,而是进行分数降低。这个时候就需要使用boosting query

对于过滤条件的处理思路:

​ 查询的时候,如果没有进行条件筛选就是正常的搜索,所以这里的条件要进行判空处理的,然后拼接成上述我们需要的格式。就可以了。总体来说上述的应用场景实际操作起来还是比较简单的。

2、其他常见查询汇总

常见的查询有term\match\bool\filter

2.1、match查询

match 查询主要的应用场景就是进行全文检索,它既能处理全文字段,又能处理精确字段。

可能运用到的其他参数

  • operator:用来控制match查询匹配词条的逻辑条件,默认值是or,如果设置为and,表示查询满足所有条件;
  • minimum_should_match:当operator参数设置为or时,该参数用来控制应该匹配的分词的最少数量;
#正常的match查询
body = {
	"query":{
		"match":{
			"title":"quick"
		}
	}
}
#添加控制参数
body = {
	"query":{
		"match":{
			"title":{
			"query":"About Search",
			"operator":"or",
			"minimum_should_match":2
			#分词最少为2
			}
		}
	}
}

过程分析:

  1. 检查字段类型 。

    标题 title 字段是一个 string 类型( analyzed )已分析的全文字段,这意味着查询字符串quick本身也应该被分析。

  2. 分析查询字符串 。

    将查询的字符串quick传入标准分析器中,输出的结果是单个项 quick 。因为只有一个单词项,所以 match 查询执行的是单个底层 term 查询。

  3. 查找匹配文档 。

    term 查询在倒排索引中查找 quick 然后获取一组包含该项的文档,本例的结果是文档:1、2 和 3 。

  4. 为每个文档评分。

    term 查询计算每个文档相关度评分 _score ,这是种将词频(term frequency,即词 quick 在相关文档的 title 字段中出现的频率)和反向文档频率(inverse document frequency,即词 quick 在所有文档的 title 字段中出现的频率),以及字段的长度(即字段越短相关度越高)相结合的计算方式。

​ 对于我们的中文分词器,不管是官方的标准分词器还是我们可能会引入的IK、jieba中文分词来说,当我们在进行字段mapping时,给title字段指定分词器后,我们的每一次对于title字段查询的字符串也会对其进行分词,例如:无人工厂

推荐大家对于中文的分词还是用IK或者jieba中文分词器。具体安装步骤,可以百度,或者私信我。

官方的分词器对于中文来说分的很散,这里会被分成,无人和工厂。ik_smart则会把无人工厂当作一个完整的词去对待。ik的分词包含ik_max_wordik_smart俩种,个人还是觉得ik_smart用起来比较舒服。对于数据量很大的ES来说,如何提高用户搜索结果的精确度,最重要的就是分词,对于用户来说分词多了,就会出现很多垃圾搜索结果。对于专业领域的搜索,个人建议是做一个专业领域的词库,可参考百度词库以及搜狗词库,有关于专业领域的一些分词下载可直接用。这里又有一个点就是自定义分词器,说好的讲搜索又开始说分词了,回头可以详细再出一篇关于ES自定义中文分词器的博客。

继续我们的搜索!

2.2、multi_match查询

如果我们希望在多个字段上执行匹配相同的查询,就要用multi_match查询。

ES共有五种多字段匹配查询:best_fieldsmost_fieldscross_fieldsphrasephrase_prefix。就是对于我们多字段查询的结果进行结果筛选返回。

默认情况下,查询的类型是best_fileds

2.2.1、best_fileds

以下是写法:

# 通常默认情况下的multi_match查询
body = {
	"multi_match":{
		"query":"无人工厂",
		# 指定多个字段
		"fields":["title","subject","..."]
	}
}
# 指定多字段查询的类型
body = {
	"multi_match":{
		"query":"无人工厂",
		"type":"best_fields",
		"fields":["title","subject","..."]
	}
}

理解:

best_fields类型与dis_max查询相同,字母dis是单词“Disjunction”的简写,意思是分离,dis_max 分离最大化查询。具体来说,是表示把同一个文档中每个字段上的查询分离,分别计算出分数,只取任一字段中最高的评分结果,作为该文档的返回结果。

实例:

# 假设ES库中的内容为
{
	#文档1
	"字段A":"我爱中国,哈哈哈哈",0.25   
	"字段B":"中国强大"0.3			==0.3

}
{
	#文档2 
	"字段A":"中国加油,哈哈哈哈。",0.55 ==0.55
	"字段B":"哈哈哈哈"0

}

# dis_max查询方式
body = {
	"query" : {
		"dis_max":{
			"queries":[
				{
					"match":{
						"字段A":"中国加油",
					}
				},
				{
					"match":{
						"字段B":"中国加油"
					}
				}
			]
		}
	}
}
# 等同与以下查询
# multi_match查询。记住哦,默认为best_fields。
body = {
    "query" : {
        "multi_match" : {
            "query" : "中国加油",
            "fields":["字段A","字段B"]
        }
    }
}
# tie_breaker 的参与
body = {
    "query" : {
        "multi_match" : {
            "query" : "中国加油",
            "fields":["字段A","字段B"],
            "tie_breaker": 0.2
        }
    }
}

理解:

上述的只是个假设的例子让各位去理解,实际可自行去体验。

执行上述搜索时

对于字符串进行分词结果为:中国,加油。俩个词。

文档1,不管是字段A,还是字段B,都含有中国,但是文档1的分值,只取字段A或者字段B中的最大值,作为该文档的返回值,而不是字段A和字段B进行累加分值返回。文档2虽然字段B没有匹配项,分值为0,但是字段A的分值却很高,所以,在返回时,文档2的评分是比文档1高。

现在7.8版本的ES的文档分值计算已经从TF/IDF,升级为BM25算法,这个算法部分感兴趣的可以自行研究,东西太多了,我也不会。

补充:

对于这样的一个搜索结果,很多人可能觉得不公平,只取最高分,那其他字段就没有任何参与权了,这样的不平衡,文档1表示不服气。

因此有了tie_breaker

假设tie_breaker=0.2

当然最佳匹配的结果还是评分最高,但是,文档1的评分却会有所提高,0.3 *(1 + 0.2)=0.36。

2.2.2、most_fields

解释好best_fields,most_fields就很好解释了。most_fields就是把每个字段的计算分数相加求每个文档的平均数,作为该文档的分值作为返回值。写法上没有区别,鉴于上述

2.2.3、phrase 和 phrase_prefix

phrase:在每个字段上运行match_phrase查询并和每个字段的_score组合。

phrase_prefix:在每个字段上运行match_phrase_prefix查询并和每个字段的_score组合。

上文中我们提到best_fields类型的查询,其类型在执行时,执行的是dis_max的match查询。

而这里的phrase 和 phrase_prefix,就是将执行时的match换成了match_phrase和match_phrase_prefix。

写法上不变

body = {
  "query": {
    "multi_match" : {
      "query":      "中国加油",
      "type":       "phrase_prefix",
      "fields":     [ "字段A", "字段B" ]
    }
  }
}
#等同于如下
#执行的是match_phrase_prefix

body = {
  "query": {
    "dis_max": {
      "queries": [
        { "match_phrase_prefix": { "字段A": "中国加油" }},
        { "match_phrase_prefix": { "字段B": "中国加油" }}
      ]
    }
  }
}

match,match_phrase,match_phrase_prefix的区别
这一部分简单解释就是,match查询会分词,而match_phrase则是会将搜索的短语当作一个整体去查询,也就是完全匹配,当然标点符号除外。

这样的话match_phrase就会显得很孬。所以官网上有一句话是这么说的:

Also, accepts analyzer, boost, lenient and zero_terms_query as explained in Match, as well as slop which is explained in Match phrase. Type phrase_prefix additionally accepts。

因此也接受analyzer,boost,lenient,slop 和zero_terms_query作为在match query中的解释。phrase_prefix类型此外接受max_expansions。

所以slop参数就是用来控制查询词条之间最大词间距。
例如:

# 原文是:南京市长江大桥。

body = {
  "query": {
    "match_phrase": {
      "message": {
        "query": "南京大桥",
        "slop": 3,
        #"analyzer" : "my_analyzer"
      }
    }
  }
}
#match是一定可以匹配到的。match_phrase只有在slop:3,最大词间距为3时,才可以匹配到结果的

analyzer参数:

这个参数就不用多说了,用来定义查询语句时对其中词条执行的分析过程。

zero_terms_query参数:

zero_terms_query默认为none,其意义为在搜索时,对于停止词就过滤了,停止词就是一些没有意义的词,不一定是结尾词,例如:了,的,嘛,英文就是 and、or、not、to等等。区分的标准具体看各个分词器的,不同分词器有一定区别。这个参数不常用。有兴趣的可以单独去了解一下。

2.2.4、cross_fields

cross_fields类型对于多个字段应该匹配的结构文档特别有用。例如,当为“Will Smith”查询first_name和last_name字段时,最佳匹配应该是"Will"在一个字段中并且"Smith"在另外一个字段中。`

这听起来像most_fields的工作,但这种方法有两个问题。第一个问题是operator和minimum_should_match在每个前缀字段中作用,以代替前缀项(请参考explanation above)。` `第二个问题是与关联性有关:在first_name和last_name字段中不同的项频率可能导致不可预期的结果。` `例如,想像我们有两个人,“Will Smith”和``"Smith Jones"``。“Smith”作为姓是非常常见的(所以重要性很低),但是“Smith”作为名字是非常不常见的(所以重要性很高)。` `假如我们搜索“Will Smith”,则“Smith Jones”文档可能显示在更加匹配的``"Will Smith"``上,因为first_name:smith的得分已经胜过first_name:will加last_name:smith的总分。

处理该种类型查询的一种方式是简单的将first_name和last_name索引字段放入单个full_name字段中。当然,这只能在索引时间完成。

cross_field类型尝试通过采用term-centric方法在查询时解决这些问题。首先把查询字符串分解成当个项,然后在任何字段中查询每个项,就好像它们是一个大的字段。

查询就像这样:

body = {
  "query": {
    "multi_match" : {
      "query":      "Will Smith",
      "type":       "cross_fields",
      "fields":     [ "first_name", "last_name" ],
      "operator":   "and"
    }
  }
}

被执行为:

+(first_name:will last_name:will)
+(first_name:smith last_name:smith)

换一种说法,所有的项必须至少在匹配文档中一个字段中出现(比较the logic used for best_fields and most_fields)。

解决了两个问题中的一个。通过混合所有字段项的频率解决不同项匹配的问题,以便平衡差异。

在实践中,first_name:smith将被视为和last_name:smith具有相同的频率,加1。这将使得在first_name和last_name上的匹配具有可比较的分数,对于last_name具有微小的优势,因为它是最有可能包含simth的字段。

注意,cross_fields通常仅作用与得到1提升的短字符串字段。 否则增加,项频率和长度正常化有助于得分, 使得项统计的混合不再有任何意义。

假如你通过Validata API运行上面的查询,将返回这样的解释:

+blended(``"will"``, fields: [first_name, last_name])
+blended(``"smith"``, fields: [first_name, last_name])

也接受analyzer, boost, operator, minimum_should_match, lenient, zero_terms_querycutoff_frequency,作为match query的解释。

cross_field and analysis

cross_field类型只能在具有相同分析器的字段上以term-centric模式工作。具有相同分析器的字段如上述实例组合在一起。假如有多个组,则他们使用bool查询相结合。

例如,假如我们有相同分析器的first和last字段,增加一个同时使用edge_ngram分析器的first.edge和last.edge,该查询:

body = {
  "query": {
    "multi_match" : {
      "query":      "Jon",
      "type":       "cross_fields",
      "fields":     [
        "first", "first.edge",
        "last",  "last.edge"
      ]
    }
  }
}

可能被执行为:

 blended("jon", fields: [first, last])
| (
    blended("j",   fields: [first.edge, last.edge])
    blended("jo",  fields: [first.edge, last.edge])
    blended("jon", fields: [first.edge, last.edge])
)

换句话说,first和last可能被组合在一起并被当做一个字段来对待,同时first.edge和last.edge可能被组合在一起并当做一个字段来对待。

具有多个组是好的,当使用operator或者minimum_should_match关联的时候,它可能遭受和most_fields和best_fields相同的问题。

你可以容易的将该查询重写为两个独立的cross_fields查询与bool查询相结合,并将minimum_should_match参数应用于其中一个:

body = {
  "query": {
    "bool": {
      "should": [
        {
          "multi_match" : {
            "query":      "Will Smith",
            "type":       "cross_fields",
            "fields":     [ "first", "last" ],
            "minimum_should_match": "50%"1}
        },
        {
          "multi_match" : {
            "query":      "Will Smith",
            "type":       "cross_fields",
            "fields":     [ "*.edge" ]
          }
        }
      ]
    }
  }
}

【1】will或smith必须存在于first或last字段。

你可以通过在查询中指定analyzer参数强制把所有字段放入相同组中。

body = {
  "query": {
   "multi_match" : {
      "query":      "Jon",
      "type":       "cross_fields",
      "analyzer":   "standard",1"fields":     [ "first", "last", "*.edge" ]
    }
  }
}

【1】对所有字段使用standard分析器

将执行如下:

blended("will",  fields: [first, first.edge, last.edge, last])
blended("smith", fields: [first, first.edge, last.edge, last]) 
tie_breaker

默认情况,每一个per-term混合查询将使用组中任何字段的最佳分数,然后将这些分数相加,以得出最终分数。tie_breaker参数可以改变per-term混合查询的默认行为,它接受:

0.0      获取最好的分数(举例)first_name:will和last_name:will(default)

1.0      所有分数相加(举例)first_name:will和last_name:will

0.0 < n < 1.0  将单个最佳分数加上tie_breaker乘以其它每个匹配字段的分数。

重要:cross_fields and fuzziness

fuzziness参数不能被cross_fields类型使用。

参考:https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html

Elasticsearch 7.8Elasticsearch 的一个稳定版本,它是一个基于 Lucene 开源库的高性能、分布式的全文搜索引擎框架。下面是对 Elasticsearch 7.8 特性的简要概述: ### 1. 高性能搜索功能 - **分布式索引**:支持水平扩展,能够处理大量数据,并提供极快的查询速度。 - **全文检索**:提供强大的全文搜索能力,适用于各种文本分析任务。 - **实时数据处理**:允许快速地存储、检索、更新和删除文档。 ### 2. 强大的集群管理功能 - **动态分配资源**:能够自动调整节点的角色和任务分发,以优化性能和利用硬件资源。 - **高可用性**:通过复制和分片机制保证数据的可靠性和高可用性。 - **容错机制**:在节点故障的情况下能够自我恢复,保证服务连续运行。 ### 3. 灵活的数据模型 - **JSON 格式文档**:以 JSON 格式存储数据,结构灵活,易于理解和操作。 - **映射系统**:为每个字段指定数据类型和属性,如是否可搜索、是否可排序等。 - **动态映射**:可以在不重启集群的情况下修改文档类型,增强应用的适应性。 ### 4. 丰富的API和集成选项 - **HTTP API**:通过 HTTP 协议提供 RESTful 格式的接口,方便与其他系统集成。 - **插件体系**:有众多第三方提供的插件,扩展了 Elasticsearch 的功能,如用于监控、日志聚合、可视化等功能。 ### 5. 先进的安全特性 - **用户管理和授权**:支持基于角色的访问控制,可以精细控制用户对数据的操作权限。 - **加密通信**:默认开启 TLS 加密,保护数据传输安全。 ### 6. 持续改进和稳定性 - **版本迭代**:定期发布新版本,不断引入新的特性和改进现有功能,保持软件的先进性和可靠性。 ### 7. 社区和生态系统 - **活跃社区**:拥有大量的开发者和使用者组成的社区,提供丰富的文档、教程和支持。 - **广泛生态**:与多种数据平台、数据库、云服务商兼容,形成完整的数据处理链路。 ### 相关问题: 1. Elasticsearch 7.8 如何配置和部署? 2. 如何使用 Elasticsearch 进行复杂查询和数据分析? 3. Elasticsearch 和其他全文搜索引擎相比有何优势?
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值