1 基本搜索
- 指定返回的字段
在ES中,通过_source子句可以设定返回结果的字段。_source指向一个JSON数组,数组中的元素是希望返回的字段名称。
- 例如,通过source指定查询字段
GET /hotol/_search
{
"_source": ["title","price"]
}
- 结果计数
给前端传递搜索匹配结果的文档条数,即需要对搜索结果进行计数。ES提供了_count API功能,在该API中,用户提供query子句用于结果匹配,ES会返回匹配的文档条数。
GET /hotol/_count
- 结果分页
GET /hotol/_search
{
"from": 0,# 起始页码
"size": 20 # 当前页面显示记录数
}
在默认情况下,用户最多可以取得10 000个文档,即from为0时,size参数最大为10 000,如果请求超过该值,ES返回报错; 如果确实需要返回多于10 000条的数据,可以适当修改max_result_window的值。
PUT /hotol/_settings
{
"index":{
"max_result_window":20000
}
}
作为一个分布式搜索引擎,一个ES索引的数据分布在多个分片中,而这些分片又分配在不同的节点上。一个带有分页的搜索请求往往会跨越多个分片,每个分片必须在内存中构建一个长度为from+size的、按照得分排序的有序队列,用以存储命中的文档。然后这些分片对应的队列数据都会传递给协调节点,协调节点将各个队列的数据进行汇总,需要提供一个长度为number_of_shards*(from+size)的队列用以进行全局排序,然后再按照用户的请求从from位置开始查找,找到size个文档后进行返回。 ES不适合深翻页。什么是深翻页呢?简而言之就是请求的from值很大; 当深翻页的请求过多时会增加各个分片所在节点的内存和CPU消耗。尤其是协调节点,随着页码的增加和并发请求的增多,该节点需要对这些请求涉及的分片数据进行汇总和排序,过多的数据会导致协调节点资源耗尽而停止服务。 作为搜索引擎,ES更适合的场景是对数据进行搜索,而不是进行大规模的数据遍历。一般情况下,只需要返回前1000条数据即可,没有必要取到10 000条数据。如果确实有大规模数据遍历的需求,可以参考使用scroll模式或者考虑使用其他的存储引擎。
- 性能分析
ES提供了profile功能,该功能详细地列出了搜索时每一个步骤的耗时,可以帮助用户对DSL的性能进行剖析。开启profile功能只需要在一个正常的搜索请求的DSL中添加"profile":"true"即可。
GET /hotol/_search
{
"profile": true
}
一个搜索可能会跨越多个分片,所以使用shards数组放在profile子句中。每个shard子句中包含3个元素,分别是id、searches和aggregations。
- id表示分片的唯一标识,它的组成形式为[nodeID][indexName][shardID]
- searches以数组的形式存在,因为有的搜索请求会跨多个索引进行搜索。每一个search子元素即为在同一个索引中的子查询
- aggregations只有在进行聚合运算时才有内容
- 评分分析
在使用搜索引擎时,一般都会涉及排序功能。如果用户不指定按照某个字段进行升序或者降序排列,那么ES会使用自己的打分算法对文档进行排序。有时我们需要知道某个文档具体的打分详情,以便于对搜索DSL问题展开排查。ES提供了explain功能来帮助使用者查看搜索时的匹配详情
GET /hotol/_explain/001
{
"query": {
"term": {
"city": {
"value": "长春"
}
}
}
}
2 搜索匹配功能
2.1 查询所有文档
使用match_all查询文档时,ES不对文档进行打分计算,默认情况下给每个文档赋予1.0的得分。用户可以通过boost参数设定该分值。
GET /hotol/_search
{
"query": {
"match_all": {
"boost": 1
}
}
}
2.2 term级别查询
- term查询
term查询是结构化精准查询的主要查询方式,用于查询待查字段和查询值是否完全匹配
GET /hotol/_search
{
"query": {
"term": {
"price": 680.00
}
}
}
- terms查询
terms查询是term查询的扩展形式,用于查询一个或多个值与待查字段是否完全匹配
GET /hotol/_search
{
"query": {
"terms": {
"city": ["长春","沈阳"]
}
}
}
- range查询
range查询用于范围查询,一般是对数值型和日期型数据的查询。使用range进行范围查询时,用户可以按照需求中是否包含边界数值进行选项设置,可供组合的选项如下: gt:大于;·lt:小于;·gte:大于或等于;·lte:小于或等于。
- exists查询
可以用exists搜索。字段不为空的条件有:·值存在且不是null;·值不是空数组;·值是数组,但不是[null]。
GET /hotol/_search
{
"query": {
"exists": {
"field": "city"
}
}
}
2.3 布尔查询
布尔查询是常用的复合查询,它把多个子查询组合成一个布尔表达式,这些子查询之间的逻辑关系是“与”,即所有子查询的结果都为true时布尔查询的结果才为真。布尔查询还可以按照各个子查询的具体匹配程度对文档进行打分计算
- must查询
当查询中包含must查询时,相当于逻辑查询中的“与”查询。命中的文档必须匹配该子查询的结果,并且ES会将该子查询与文档的匹配程度值加入总得分里。must搜索包含一个数组,可以把其他的term级别的查询及布尔查询放入其中。
GET /hotol/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"city": {
"value": "长春"
}
}
},
{
"range": {
"price": {
"gte": 500,
"lte": 1000
}
}
}
]
}
}
}
- should查询
当查询中包含should查询时,表示当前查询为“或”查询。命中的文档可以匹配该查询中的一个或多个子查询的结果,并且ES会将该查询与文档的匹配程度加入总得分里。should查询包含一个数组,可以把其他的term级别的查询及布尔查询放入其中。
GET /hotol/_search
{
"query": {
"bool": {
"should": [
{
"term": {
"city": {
"value": "长春"
}
}
},
{
"range": {
"price": {
"gte": 500,
"lte": 1000
}
}
}
]
}
}
}
- must not查询
当查询中包含must not查询时,表示当前查询为“非”查询。命中的文档不能匹配该查询中的一个或多个子查询的结果,ES会将该查询与文档的匹配程度加入总得分里。must not查询包含一个数组,可以把其他term级别的查询及布尔查询放入其中。
GET /hotol/_search
{
"query": {
"bool": {
"must_not": [
{
"term": {
"city": {
"value": "长春"
}
}
},
{
"range": {
"price": {
"gte": 500,
"lte": 1000
}
}
}
]
}
}
}
2.4 filter查询
filter查询即过滤查询,该查询是布尔查询里非常独特的一种查询。其他布尔查询关注的是查询条件和文档的匹配程度,并按照匹配程度进行打分;而filter查询关注的是查询条件和文档是否匹配,不进行相关的打分计算,但是会对部分匹配结果进行缓存。
GET /hotol/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"city": {
"value": "长春"
}
}
},
{
"range": {
"price": {
"gte": 500,
"lte": 1000
}
}
}
]
}
}
}
2.5 Constant Score查询
如果不想让检索词频率TF(Term Frequency)对搜索结果排序有影响,只想过滤某个文本字段是否包含某个词,可以使用Constant Score将查询语句包装起来
GET /hotol/_search
{
"_source": ["title"],
"query": {
"constant_score": {
"filter": {
"match":{
"title":"富豪"
}
},
"boost": 1.2
}
}
}
2.6 function score查询
当使用ES进行搜索时,命中的文档默认按照相关度进行排序。有些场景下用户需要干预该“相关度”,此时就可以使用Function Score查询。使用时,用户必须定义一个查询以及一个或多个函数,这些函数为每个文档计算一个新分数
GET /hotol/_search
{
"_source": ["title"],
"query": {
"function_score": {
"query": {
"match":{
"title":"富豪"
}
},
"functions": [
{
"random_score": {}
}
],
"score_mode": "sum"
}
}
}
2.7 全文查询
不同于结构化查询,全文搜索首先对查询词进行分析,然后根据查询词的分词结果构建查询。这里所说的全文指的是文本类型数据(text类型),默认的数据形式是人类的自然语言,如对话内容、图书名称、商品介绍和酒店名称等。结构化搜索关注的是数据是否匹配,全文搜索关注的是匹配的程度;结构化搜索一般用于精确匹配,而全文搜索用于部分匹配。
- match查询
match查询是全文搜索的主要代表。对于最基本的math搜索来说,只要分词中的一个或者多个在文档中存在即可。
#默认情况下,match查询使用的是标准分词器,中文按单个文字拆分匹配
GET /hotol/_search
{
"_source": ["title"],
"query": {
"match": {
"title": "大富豪店"
}
}
}
- 设置查询词之间的匹配结果为“与”关系
GET /hotol/_search
{
"_source": ["title"],
"query": {
"match": {
"title": {
"query": "大富豪店",
"operator": "and"
}
}
}
}
- 采用minimum_should_match参数,该参数叫作最小匹配参数,其值为一个数值,意义为可以匹配上的词的个数。在一般情况下将其设置为一个百分数,因为在真实场景中并不能精确控制具体的匹配数量。以下示例设置最小匹配为80%的文档
GET /hotol/_search
{
"_source": ["title"],
"query": {
"match": {
"title": {
"query": "大富豪店",
"operator": "and",
"minimum_should_match": "80%"
}
}
}
}
- multi_match查询
用户需要在多个字段中查询关键词,除了使用布尔查询封装多个match查询之外,可替代的方案是使用multi_match。可以在multi_match的query子句中组织数据匹配规则,并在fields子句中指定需要搜索的字段列表。
- 搜索这个两个字段是否包含“店”
GET /hotol/_search
{
"_source": ["title"],
"query": {
"multi_match": {
"query": "店",
"fields": ["title","city"]
}
}
}
- match_phrase查询
match_phrase用于匹配短语,与match查询不同的是,match_phrase用于搜索确切的短语或邻近的词语。
GET /hotol/_search
{
"query": {
"match_phrase": {
"title": "酒店富豪"
}
}
}
2.8 基于地理位置查询
ES为用户提供了基于地理位置的搜索功能。它主要支持两种类型的地理查询:一种是地理点(geo_point),即经纬度查询,另一种是地理形状查询(geo_shape),即支持点、线、圆形和多边形查询等。 geo_point字段类型的查询方式有3种,分别为geo_distance查询、geo_bounding_box查询和geo_polygon。
- geo_distance查询方式 geo_distance查询方式需要用户指定一个坐标点,在指定距离该点的范围后,ES即可查询到相应的文档。假设北京天安门的经纬度为[116.4039,39.915143],以下为使用geo_distance查询所找到的天安门5km范围内的酒店
GET /hotol/_search
{
"query": {
"geo_distance": {
"distance": "5km",//设置距离范围5km
"location": { //设置中心经纬度
"lat":39.915143,
"lon":116.4039
}
}
}
}
- 查询对应的范围内的点
GET /hotol/_search
{
"query": {
"geo_polygon": {
"location":{
"points":[
{
"lat":39.915143,
"lon":116.4039
},{
"lat":39.925143,
"lon":116.4139
},{
"lat":39.935143,
"lon":116.4239
}
]
}
}
}
}
2.9 搜索建议
即在用户输入搜索关键词的过程中系统进行自动补全,用户可以根据自己的需求单击搜索建议的内容直接进行搜索。在搜索时,用户每输入一个字符,前端就需要向后端发送一次查询请求对匹配项进行查询,因此这种场景对后端响应速度的要求比较高。通过协助用户进行搜索,可以避免用户输入错误的关键词,引导用户使用更合适的关键词,提升用户的搜索体验和搜索效率。
- 添加字段对应的字段类型需要定义为completion类型
PUT /hotol/_mapping
{
"properties":{
"query_word":{ //自定义query_word字段
"type":"completion"
}
}
}
- 插入测试数据
POST /hotol/_doc/002
{
"title":"如家酒店",
"city":"长春",
"price":280.5,
"query_word":"如家快捷酒店"
}
- 查询前缀“如家”
GET /hotol/_search
{
"suggest": {
"hotel_sug": { //自定义搜索建议名称hotel_sug
"prefix": "如家",//搜索建议名称
"completion": {//搜索建议对应字段
"field": "query_word"
}
}
}
}
注意,ES提供的Completion Suggester功能使用的索引结构不是倒排索引,而是在内存中构建FST(Finite StateTransducers)。构建该数据结构是有比较大的内存存储成本的,因此在生产环境中向索引中添加数据时一定要关注ES节点的内存消耗,避免数据量过大造成ES节点内存耗尽从而影响集群服务。
3 按字段值排序
在默认情况下,ES对搜索结果是按照相关性降序排序的。有时需要按照某些字段的值进行升序或者降序排序 ES提供了sort子句可以对数据进行排序。使用sort子句一般是按照字段信息进行排序,不受相关性影响,而且打分步骤需要耗费一定的硬件资源和时间,因此默认情况下,不对文档进行打分。使用sort排序分为两种类别,一种是按照字段值的大小进行排序,另一种是按照给定地理坐标的距离远近进行排序。
3.1 按普通字段值排序
使用sort子句对字段值进行排序时需要指定排序的字段。ES默认是按照字段值进行升序排序,可以设置order参数为asc或desc,指定按照字段值进行升序或者降序排序。
GET /hotol/_search
{
"_source": ["title","price"],
"sort": [
{
"price": {
"order": "desc"
}
}
]
}
结果中可以看到,使用sort对搜索结果排序后,在每个文档的_source信息下面多出了一个sort信息,该信息中显示了当前文档排序字段的值。
- 使用sort对搜索结果按照多个字段进行排序
3.2 按地理距离排序
使用geo_distance查询,配合sort可以指定另一种排序规则,即按照文档坐标与指定坐标的距离对结果进行排序。使用时,需要在sort内部指定排序名称为geo_distanc,并指定目的地坐标。除了可以指定升序或者降序排列外,还可以指定排序结果中sort子句中的距离的计量单位,默认值为km即千米。在进行距离计算时,系统默认使用的算法为arc,该算法的特点是计算精准但是耗费时间较长,用户可以使用distance_type参数选择另一种计算速度快但经度略差的算法,名称为plane。
GET /hotol/_search
{
"query": {
"geo_distance": {
"distance": "5km",
"location": {
"lat":39.915143,
"lon":116.4039
}
}
},
"sort": [
{
"_geo_distance": {
"location": { //设置排序中心点坐标
"lat":39.915143,
"lon":116.4039
},
"order": "asc",//排序由近及远
"unit": "km",//排序的计量单位
"distance_type": "plane"//距离计算方法
}
}
]
}