寻找做网站的公司,模板演示网站,wordpress 归档 什么用,济南公司网站推广优化最大的目录 前言阅读对象阅读导航前置知识数据准备笔记正文一、ES高级查询Query DSL1.1 基本介绍1.2 简单查询之——match-all#xff08;匹配所有#xff09;1.2.1 返回源数据_source1.2.2 返回指定条数size1.2.3 分页查询fromsize1.2.4 指定字段排序sort 1.3 简单查询之——… 目录 前言阅读对象阅读导航前置知识数据准备笔记正文一、ES高级查询Query DSL1.1 基本介绍1.2 简单查询之——match-all匹配所有1.2.1 返回源数据_source1.2.2 返回指定条数size1.2.3 分页查询fromsize1.2.4 指定字段排序sort 1.3 简单查询之——Term-Level Queries术语级别查询精确匹配1.3.1 Term query术语查询词项查询1.3.2 Terms Query多术语查询1.3.3 exists query是否存在字段查询1.3.4 ids query——id数组查询1.3.5 range query范围查询1.3.6 prefix query前缀查询1.3.7 wildcard query通配符查询1.3.8 fuzzy query模糊查询 1.4 简单查询之——Full Text Queries全文检索1.4.1 match query匹配查询1.4.2 multi_match query 多字段查询1.4.3 match_phrase query短语查询1.4.4 query_string query1.4.6 simple_query_string 1.5 复杂查询之——bool query布尔查询1.6 highlight高亮1.6.1 自定义高亮html标签1.6.2 多字段高亮 二、ES 深度分页问题及针对不同需求下的解决方案2.1 什么是深度分页2.2 深度分页会带来什么问题2.3 深度分页问题的常见解决方案2.3.1 尝试避免使用深度分页2.3.2 滚动查询Scroll Search当前已不推荐2.3.3 PIT search_after 2.4 总结 学习总结感谢 前言
个人在学习的过程中感觉比较吃力的地方有如下
语法结构比较陌生没有中文文档 只能看英文其他博客也比较少介绍语法结构。比如说为什么查询中会出现query关键词
阅读对象
有ES入门基础且想进一步学习ES基本操作的朋友
阅读导航
系列上一篇文章《【ES专题】ElasticSearch快速入门》
官方文档地址ES官方使用文档-英文
前置知识
理解ES的核心概念最最重要的是【索引】和【文档】了解基本的索引、文档操作了解RESTFul风格
数据准备
为了方便后面的查询演示我们先在ES中创建/es_db索引并创建一些文档。
#指定ik分词器
PUT /es_db
{settings : {index : {analysis.analyzer.default.type: ik_max_word}}
}# 创建文档,指定id
PUT /es_db/_doc/1
{
name: 张三,
sex: 1,
age: 25,
address: 广州天河公园,
remark: java developer
}
PUT /es_db/_doc/2
{
name: 李四,
sex: 1,
age: 28,
address: 广州荔湾大厦,
remark: java assistant
}PUT /es_db/_doc/3
{
name: 王五,
sex: 0,
age: 26,
address: 广州白云山公园,
remark: php developer
}PUT /es_db/_doc/4
{
name: 赵六,
sex: 0,
age: 22,
address: 长沙橘子洲,
remark: python assistant
}PUT /es_db/_doc/5
{
name: 张龙,
sex: 0,
age: 19,
address: 长沙麓谷企业广场,
remark: java architect assistant
} PUT /es_db/_doc/6
{
name: 赵虎,
sex: 1,
age: 32,
address: 长沙麓谷兴工国际产业园,
remark: java architect
} PUT /es_db/_doc/7
{
name: 李虎,
sex: 1,
age: 32,
address: 广州番禺节能科技园,
remark: java architect
}PUT /es_db/_doc/8
{
name: 张星,
sex: 1,
age: 32,
address: 武汉东湖高新区未来智汇城,
remark: golang developer
}笔记正文
一、ES高级查询Query DSL
1.1 基本介绍
就跟Mysql有自己的查询语言一样ES作为一款优秀的分布式文档存储和【搜索引擎】它也有自己的查询语言。即Query DSL查询Domain Specific Language查询领域专用语言。Query DSL是利用RESTfull风格API传递JSON格式的请求体RequestBody数据与ES进行交互这种方式的丰富查询语法让ES检索变得更强大更简洁。 上面高亮处是否有似曾像是的感觉我第一眼觉得这不跟做http请求差不多嘛。基于ES是Java写的我严重怀疑… Elasticsearch 提供了基于 JSON 的完整查询 DSL查询领域特定语言来定义查询。在ES的Query DSL中大体可以分为两类叶查询子句我更喜欢叫简单查询以及复合查询子句复杂查询。
总的来说ES支持很多种查询方式所以在后面的举例中我不会全部都介绍一遍而只是把我们工作中【据说】可能比较常用的大概介绍一遍。毕竟我自己也没有生产使用经验
在DSL中所有的查询都以下面这种范式开头
比如下面的语法示例
GET /index_name/_doc/_search
{query: {json请求体数据}
}# 可以简写成下面的
GET /index_name/_search
{query: {json请求体数据}
}# 查询指定字段则是如下
GET /index_name/_search
{query: {field_name: {json请求体数据}}
}来个简单的使用示例
#无条件查询默认返回10条数据
GET /es_db/_search
{query:{match_all:{}}
}然后再给个返回图解给大家稍微看看字段是什么意思。
1.2 简单查询之——match-all匹配所有
语法格式如下
GET /index_name/_search
{query:{match_all:{}}
}值得注意的是使用match_all匹配所有文档默认只会返回10条数据。因为_search查询默认采用的是【分页查询】每页记录数size的默认值为10。如果想显示更多数据指定size。 简单的使用示例如下
GET /es_db/_search
等同于
GET /es_db/_search
{query: {match_all: {}}
}1.2.1 返回源数据_source
_source关键字: 是一个数组在数组中用来指定展示那些字段。语法格式我就不贴了直接看时使用示例就行了如下
# 返回指定字段
GET /es_db/_search
{query: {match_all: {}},_source: [name,address]
}#在查询中过滤
#不查看源数据仅查看元字段
{_source: false,query: {...}
}#只看以obj.开头的字段其实这里就是通配符
{_source: obj.*,query: {...}
}1.2.2 返回指定条数size
size关键字: 指定查询结果中返回指定条数。 默认返回值10条 学到这里的时候我有点懵为啥这里不以下划线开头_size啊太没规范了吧 GET /es_db/_search
{query: {match_all: {}},size: 100
}1.2.3 分页查询fromsize
size显示应该返回的结果数量默认是 10 from显示应该跳过的初始结果数量默认是 0 from 关键字用来指定起始返回位置和size关键字连用可实现分页效果 跟Mysql中的limit很像原理也是 GET /es_db/_search
{query: {match_all: {}},from: 0,size: 5
}1.2.4 指定字段排序sort 注意会让得分失效 GET /es_db/_search
{query: {match_all: {}},sort: [{age: desc}]
}#排序分页
GET /es_db/_search
{query: {match_all: {}},sort: [{age: desc}],from: 10,size: 5
}1.3 简单查询之——Term-Level Queries术语级别查询精确匹配
术语级别查询Term-Level Queries指的是搜索内容不经过文本分析直接用于文本匹配这个过程类似于数据库的SQL查询搜索的对象大多是索引的非text类型字段。Elasticsearch 中的一些术语级别查询示例包括 term、terms 和 range 查询。 不经过文本分析是指不对查询语句分词了输入什么就拿什么去【索引文档】中匹配 1.3.1 Term query术语查询词项查询
术语查询直接返回包含搜索内容的文档常用来查询索引中某个类型为keyword的文本字段类似于SQL的【】查询使用十分普遍。 注意最好不要对text字段使用term查询因为text字段会被分词这样做既没有意义还很有可能什么也查不到。 另外term大家不知道还熟悉否这不就是ES概念中的【词项】嘛所以我更喜欢称呼Term Query为【词项查询】。理论上来说ES所有文档的source皆为词项只不过有一种比较特殊的情况是text类型的字段会先被分词成多个【词项】写入倒排索引中。所以对text类型的字段做【词项查询】的时候要注意【分词】的情况了。查询字符串不分词但是文档记录在被写入的时候已经被分词了 接下来我们来三个查询给大家演示一下词项查询的效果。
查询一精确匹配查找age为28的记录
# 对bool日期数字结构化的文本可以利用term做精确匹配
# term 精确匹配
GET /es_db/_search
{query: {term: {age: {value: 28}}}
}根据我们前面的演示数据age28的数据即李四。查询结果如下 查询二对text字段查询
GET /es_db/_search
{query:{term: {address: {value: 广州白云}}}
}大家思考下上面的查询有数据嘛毕竟我们在准备数据中id3的王五地址就是广州白云山公园。 哈答案是没有的。因为我们的es_db索引使用的分词器为ik_max_word最细粒度的拆分。我们来看看这个分词器下的拆分结果 看没有我们查询的广州白云词项所以查找无数据返回。
查询三通过keyword对text字段精确查询
# 采用term精确查询, 查询字段映射类型为keyword
GET /es_db/_search
{query:{term: {address.keyword: {value: 广州白云山公园}}}
}结果如下
在ES中Term查询对输入不做分词。会将输入作为一个整体在倒排索引中查找准确的词项并且使用相关度算分公式为每个包含该词项的文档进行相关度算分。 可以通过Constant Score操作将查询转换成一个Filtering避免算分并利用缓存提高性能。
将Query 转成 Filter忽略TF-IDF计算避免相关性算分的开销Filter可以有效利用缓存
GET /es_db/_search
{query: {constant_score: {filter: {term: {address.keyword: 广州白云山公园}}}}
}term处理多值字段时数组term查询是包含不是等于。
POST /employee/_bulk
{index:{_id:1}}
{name:小明,interest:[跑步,篮球]}
{index:{_id:2}}
{name:小红,interest:[跳舞,画画]}
{index:{_id:3}}
{name:小丽,interest:[跳舞,唱歌,跑步]}POST /employee/_search
{query: {term: {interest.keyword: {value: 跑步}}}
}1.3.2 Terms Query多术语查询
Terms query用于在指定字段上匹配多个词项terms。它会精确匹配指定字段中包含的任何一个词项。
POST /es_db/_search
{query: {terms: {remark.keyword: [java assistant, java architect]}}
}
1.3.3 exists query是否存在字段查询
在Elasticsearch中可以使用exists进行查询以判断文档中是否存在对应的字段。 注意这里是判断【字段】是否存在而不是文档数据
#查询索引库中存在remarks字段的文档数据
GET /es_db/_search
{query: {exists: {field: remark}}
}1.3.4 ids query——id数组查询
ids关键字 : 值为数组类型,用来根据一组id获取多个对应的文档
GET /es_db/_search
{query: {ids: {values: [1,2]}}
}1.3.5 range query范围查询
range范围关键字。它的取值如下
gte 大于等于lte 小于等于gt 大于lt 小于now 当前时间
POST /es_db/_search
{query: {range: {age: {gte: 25,lte: 28}}}
}#日期范围比较
DELETE /product
POST /product/_bulk
{index:{_id:1}}
{price:100,date:2021-01-01,productId:XHDK-1293}
{index:{_id:2}}
{price:200,date:2022-01-01,productId:KDKE-5421}GET /product/_mappingGET /product/_search
{query: {range: {date: {gte: now-2y}}}
}1.3.6 prefix query前缀查询
它会对分词后的文档数据term进行前缀搜索。
它不会分析要搜索的字符串传入的前缀就是想要查找的前缀默认状态下前缀查询不做相关度分数计算它只是将所有匹配的文档返回然后赋予所有相关分数值为1。它的行为更像是一个过滤器而不是查询。两者实际的区别就是过滤器是可以被缓存的而前缀查询不行
prefix的原理需要遍历所有倒排索引并比较每个term是否以所指定的前缀开头
GET /es_db/_search
{query: {prefix: {address: {value: 广州}}}
}1.3.7 wildcard query通配符查询
通配符查询工作原理和prefix相同只不过它不是只比较开头它能支持更为复杂的匹配模式。 通配符大家都不陌生 GET /es_db/_search
{query: {wildcard: {address: {value: *白*}}}
}1.3.8 fuzzy query模糊查询
在实际的搜索中我们有时候会打错字从而导致搜索不到。在Elasticsearch中我们可以使用fuzziness属性来进行模糊查询从而达到搜索有错别字的情形。 fuzzy 查询会用到两个很重要的参数fuzzinessprefix_length
fuzziness表示输入的关键字通过几次操作可以转变成为ES库里面的对应field的字段 操作是指新增一个字符删除一个字符修改一个字符每次操作可以记做编辑距离为1;如【中文集团】到【中威集团】编辑距离就是1只需要修改一个字符如果fuzziness值在这里设置成2会把编辑距离为2的【东东集团】也查出来所谓距离可以理解为允许错误的字数该参数默认值为0即不开启模糊查询; fuzzy 模糊查询 最大模糊错误必须在0-2之间 prefix_length表示限制输入关键字和ES对应查询field的内容开头的前n个字符必须完全匹配不允许错别字匹配; 如这里等于1则表示开头的字必须匹配不匹配则不返回;默认值也是0;加大prefix_length的值可以提高效率和准确率。
GET /es_db/_search
{query: {fuzzy: {address: {value: 白运山,fuzziness: 1 }}}
}1.4 简单查询之——Full Text Queries全文检索
全文检索查询Full Text Queries和术语级别查询Term-Level Queries是 Elasticsearch 中搜索和检索数据的两种不同方法。 全文检索查询旨在基于相关性搜索和匹配文本数据。这些查询会对输入的文本进行分析将其拆分为词项单个单词并执行诸如分词、词干处理和标准化等操作。lasticsearch 中的一些全文检索查询示例包括match、match_phrase 和 multi_match 查询。
全文检索的关键特点
对输入的文本进行分析并根据分析后的词项进行搜索和匹配。全文检索查询会对输入的文本进行分析将其拆分为词项并基于这些词项进行搜索和匹配操作以相关性为基础进行搜索和匹配。全文检索查询使用相关性算法来确定文档与查询的匹配程度并按照相关性进行排序。相关性可以基于词项的频率、权重和其他因素来计算全文检索查询适用于包含自由文本数据的字段text类型例如文档的内容、文章的正文或产品描述等 对于match查询其底层逻辑的概述 分词首先输入的查询文本会被分词器进行分词。分词器会将文本拆分成一个个词项terms如单词、短语或特定字符。分词器通常根据特定的语言规则和配置进行操作。倒排索引ES使用倒排索引来加速搜索过程。倒排索引是一种数据结构它将词项映射到包含这些词项的文档。每个词项都有一个对应的倒排列表其中包含了包含该词项的所有文档的引用。匹配计算一旦查询被分词ES将根据查询的类型和参数计算文档与查询的匹配度。对于match查询ES将比较查询的词项与倒排索引中的词项并计算文档的相关性得分。相关性得分衡量了文档与查询的匹配程度。结果返回根据相关性得分ES将返回最匹配的文档作为搜索结果。搜索结果通常按照相关性得分进行排序以便最相关的文档排在前面。 1.4.1 match query匹配查询
match在匹配时会对所查找的关键词进行分词然后按分词匹配查找。 match支持以下参数
query : 指定匹配的值operator : 匹配条件类型 and : 条件分词后都要匹配or : 条件分词后有一个匹配即可默认or minmum_should_match : 最低匹配度即条件在倒排索引中最低的匹配度
#match 分词后or的效果
GET /es_db/_search
{query: {match: {address: 广州白云山公园}}
}# 分词后 and的效果
GET /es_db/_search
{query: {match: {address: {query: 广州白云山公园,operator: and}}}
}
minnum_should_match在match中的应用 当operator参数设置为or时minnum_should_match参数用来控制匹配的分词的最少数量。
# 最少匹配广州公园两个词
GET /es_db/_search
{query: {match: {address: {query: 广州公园,minimum_should_match: 2}}}
}1.4.2 multi_match query 多字段查询
可以根据字段类型决定是否使用分词查询得分最高的在前面
GET /es_db/_search
{query: {multi_match: {query: 长沙张龙,fields: [address,name]}}
}注意若字段类型分词将查询条件分词之后进行查询如果该字段不分词就会将查询条件作为整体进行查询。
1.4.3 match_phrase query短语查询
短语搜索(match phrase)会对搜索文本进行文本分析然后到索引中寻找搜索的每个分词并要求分词相邻你可以通过调整slop参数设置分词出现的【最大间隔距离】。match_phrase会将检索关键词分词。
下面两个查询将表现出不一样的结果这是i因为【相邻】两个字在作怪
# 下面分词之后得到广州、白云山、白云、云山
GET /es_db/_search
{query: {match_phrase: {address: 广州白云山}}
}# 下面分词之后得到广州、白云、云山
GET /es_db/_search
{query: {match_phrase: {address: 广州白云}}
}思考为什么查询广州白云山有数据广州白云没有数据 分析原因 【广州白云山】分词之后得到广州、白云山、白云、云山 【广州白云】分词之后得到广州、白云、云山 而match_phrase匹配的是相邻的词条所以查询广州白云山有结果但查询广州白云没有结果。 如何解决词条间隔的问题可以借助slop参数slop参数告诉match_phrase查询词条能够相隔多远时仍然将文档视为匹配。
#广州云山分词后相隔为2可以匹配到结果
GET /es_db/_search
{query: {match_phrase: {address: {query: 广州云山,slop: 2} }}
}
1.4.4 query_string query
允许我们在单个查询字符串中指定AND | OR | NOT条件同时也和 multi_match query 一样支持多字段搜索。和match类似但是match需要指定字段名query_string是在所有字段中搜索范围更广泛。 注意: 查询字段分词就将查询条件分词查询查询字段不分词将查询条件不分词查询 未指定字段查询
# AND 要求大写
GET /es_db/_search
{query: {query_string: {query: 赵六 AND 橘子洲}}
}指定单个字段查询
#Query String
GET /es_db/_search
{query: {query_string: {default_field: address,query: 白云山 OR 橘子洲}}
}指定多个字段查询
GET /es_db/_search
{query: {query_string: {fields: [name,address],query: 张三 OR (广州 AND 王五)}}
}1.4.6 simple_query_string
类似Query String但是会忽略错误的语法,同时只支持部分查询语法不支持AND OR NOT会当作字符串处理。支持部分逻辑
替代AND|替代OR-替代NOT
#simple_query_string 默认的operator是OR
GET /es_db/_search
{query: {simple_query_string: {fields: [name,address],query: 广州公园,default_operator: AND}}
}GET /es_db/_search
{query: {simple_query_string: {fields: [name,address],query: 广州 公园}}
}1.5 复杂查询之——bool query布尔查询 ES的复杂查询其实有很多种但这里只介绍这个了。感兴趣的同学自己看文档吧呜呜呜那个文档全英文的太难看懂了 布尔查询可以按照布尔逻辑条件组织多条查询语句只有符合整个布尔条件的文档才会被搜索出来。在布尔条件中可以包含两种不同的上下文。
搜索上下文(query context)使用搜索上下文时Elasticsearch需要计算每个文档与搜索条件的相关度得分这个得分的计算需使用一套复杂的计算公式有一定的性能开销带文本分析的全文检索的查询语句很适合放在搜索上下文中过滤上下文(filter context)使用过滤上下文时Elasticsearch只需要判断搜索条件跟文档数据是否匹配例如使用Term query判断一个值是否跟搜索内容一致使用Range query判断某数据是否位于某个区间等。过滤上下文的查询不需要进行相关度得分计算还可以使用缓存加快响应速度很多术语级查询语句都适合放在过滤上下文中
布尔查询一共支持4种组合类型
PUT /books
{settings: {number_of_replicas: 1,number_of_shards: 1},mappings: {properties: {id: {type: long},title: {type: text,analyzer: ik_max_word},language: {type: keyword},author: {type: keyword},price: {type: double},publish_time: {type: date,format: yyy-MM-dd},description: {type: text,analyzer: ik_max_word}}}
}POST /_bulk
{index:{_index:books,_id:1}}
{id:1, title:Java编程思想, language:java, author:Bruce Eckel, price:70.20, publish_time:2007-10-01, description:Java学习必读经典殿堂级著作赢得了全球程序员的广泛赞誉。}
{index:{_index:books,_id:2}}
{id:2,title:Java程序性能优化,language:java,author:葛一鸣,price:46.5,publish_time:2012-08-01,description:让你的Java程序更快、更稳定。深入剖析软件设计层面、代码层面、JVM虚拟机层面的优化方法}
{index:{_index:books,_id:3}}
{id:3,title:Python科学计算,language:python,author:张若愚,price:81.4,publish_time:2016-05-01,description:零基础学python光盘中作者独家整合开发winPython运行环境涵盖了Python各个扩展库}
{index:{_index:books,_id:4}}
{id:4, title:Python基础教程, language:python, author:Helant, price:54.50, publish_time:2014-03-01, description:经典的Python入门教程层次鲜明结构严谨内容翔实}
{index:{_index:books,_id:5}}
{id:5,title:JavaScript高级程序设计,language:javascript,author:Nicholas C. Zakas,price:66.4,publish_time:2012-10-01,description:JavaScript技术经典名著}GET /books/_search
{query: {bool: {must: [{match: {title: java编程}},{match: {description: 性能优化}}]}}
}GET /books/_search
{query: {bool: {should: [{match: {title: java编程}},{match: {description: 性能优化}}],minimum_should_match: 1}}
}
GET /books/_search
{query: {bool: {filter: [{term: {language: java}},{range: {publish_time: {gte: 2010-08-01}}}]}}
}1.6 highlight高亮
highlight关键字: 可以让符合条件的文档中的关键词高亮。 highlight相关属性
pre_tags 前缀标签post_tags 后缀标签tags_schema 设置为styled可以使用内置高亮样式require_field_match 多字段高亮需要设置为false
示例数据
#指定ik分词器
PUT /products
{settings : {index : {analysis.analyzer.default.type: ik_max_word}}
}PUT /products/_doc/1
{proId : 2,name : 牛仔男外套,desc : 牛仔外套男装春季衣服男春装夹克修身休闲男生潮牌工装潮流头号青年春秋棒球服男 7705浅蓝常规 XL,timestamp : 1576313264451,createTime : 2019-12-13 12:56:56
}PUT /products/_doc/2
{proId : 6,name : HLA海澜之家牛仔裤男,desc : HLA海澜之家牛仔裤男2019时尚有型舒适HKNAD3E109A 牛仔蓝(A9)175/82A(32),timestamp : 1576314265571,createTime : 2019-12-18 15:56:56
}搜索测试
GET /products/_search
{query: {term: {name: {value: 牛仔}}},highlight: {fields: {*:{}}}
}1.6.1 自定义高亮html标签
可以在highlight中使用pre_tags和post_tags来自定义高亮html标签
GET /products/_search
{query: {multi_match: {fields: [name,desc],query: 牛仔}},highlight: {post_tags: [/span], pre_tags: [span stylecolor:red],fields: {*:{}}}
}1.6.2 多字段高亮
GET /products/_search
{query: {term: {name: {value: 牛仔}}},highlight: {pre_tags: [font colorred],post_tags: [font/],require_field_match: false,fields: {name: {},desc: {}}}
}二、ES 深度分页问题及针对不同需求下的解决方案
说到分页问题这里有一个很有意思的东西想跟大家分享一下。 你们应该记得分页是怎么实现的吧无非就这样嘛 select * from table_a where col‘xxx’ limit 0, 100; select * from table_a where col‘xxx’ limit 101, 100; 但是不知道大家有没有仔细研究过这条sql的性能问题… 我直接说吧性能很差因为limit需要从表头开始遍历数据。也就是对于第2条sql的limit 101,200来说他仍然需要遍历完前面的100条才开始检索第101-200条。也正是因为这个原因其实非常推荐主键id不要用UUID的而是使用自增的bigint。这样就方便优化怎么优化呢【假设id是从0开始自增】的情况下上面的sql可以这么优化 select top 100 * from table_a where col‘xxx’ and id 0; select top 100 * from table_a where col‘xxx’ and id 100; 因为ES的from跟size一样是会去遍历的所以会有同样的问题。
2.1 什么是深度分页
分页问题是Elasticsearch中最常见的查询场景之一正常情况下分页代码如实下面这样的
# 查询第一页5条数据
GET /es_db/_search
{query: {match_all: {}},from: 0,size: 5
}输出结果如下图 但是如果我们查询的数据页数特别大当from size大于10000的时候就会出现问题如下图报错信息所示 ES通过参数index.max_result_window用来限制单次查询满足查询条件的结果窗口的大小其默认值为10000
2.2 深度分页会带来什么问题
ES分页查询流程大致如下
数据存储在各个分片中协调节点将查询请求转发给各个节点当各个节点执行搜索后将排序后的前N条数据返回给协调节点协调节点汇总各个分片返回的数据再次排序最终返回前N条数据给客户端这个流程会导致一个深度分页的问题也就是翻页越多性能越差甚至导致ES出现OOM
在分布式系统中对结果排序的成本随分页的深度成指数上升。
举个例子从10万名高考生中查询成绩为的10001-10100位的100名考生的信息效果图如下 从上面案例中不难看出每次有序的查询都会在每个分片中执行单独的查询然后进行数据的二次排序而这个二次排序的过程是发生在heap中的也就是说当你单次查询的数量越大那么堆内存中汇总的数据也就越多对内存的压力也就越大。这里的单次查询的数据量取决于你查询的是第几条数据而不是查询了几条数据比如你希望查询的是第10001-10100这一百条数据但是ES必须将前10100全部取出进行二次查询。因此如果查询的数据排序越靠后就越容易导致OOMOut Of Memory情况的发生频繁的深分页查询会导致频繁的FGC。 ES为了避免用户在不了解其内部原理的情况下而做出错误的操作设置了一个阈值即max_result_window其默认值为10000其作用是为了保护堆内存不被错误操作导致溢出。
2.3 深度分页问题的常见解决方案
2.3.1 尝试避免使用深度分页
很简单的道理既然解决不了问题那就解决提出问题的人。直接从业务上【尝试避免使用深度分页】。 解决深度分页问题最好的办法就是避免使用深度分页。谷歌、百度目前作为全球和国内做大的搜索引擎不约而同的在分页条中删除了“跳页”功能其目的就是为了避免用户使用深度分页检索。 看看上面的截图有没有发现他只有下一页或者只能选择最近10页而不能直接跳到指定页数。 这种做法本质上和ES中的max_result_window作用是一样的
2.3.2 滚动查询Scroll Search当前已不推荐
scroll滚动搜索是先搜索一批数据然后下次再搜索下一批数据以此类推直到搜索出全部的数据来。 scroll搜索会在第一次搜索的时候保存一个当时的视图快照之后只会基于该视图快照搜索数据如果在搜索期间数据发生了变更用户是看不到变更的数据的。因此滚动查询不适合实时性要求高的搜索场景官方已不推荐使用滚动查询进行深度分页查询因为无法保存索引状态
目前适合场景单个滚动搜索请求中检索大量结果即非“C端业务”场景 使用示例 1第一次进行scroll查询
#查询命令中新增scroll1m,说明采用游标查询保持游标查询窗口1分钟也就是本次快照的结果缓存起来的有效时间是1分钟。
GET /es_db/_search?scroll1m
{query: { match_all: {}},size: 2
}查询结果除了返回前2条记录还返回了一个游标ID值_scroll_id 2从第二次查询开始每次查询都要指定_scroll_id参数
# scroll_id 的值就是上一个请求中返回的 _scroll_id 的值
GET /_search/scroll
{scroll: 1m, scroll_id : FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFmNwcVdjblRxUzVhZXlicG9HeU02bWcAAAAAAABmzRY2YlV3Z0o5VVNTdWJobkE5Z3MtXzJB
}查询结果 多次根据scroll_id游标查询直到没有数据返回则结束查询。采用游标查询索引全量数据更安全高效限制了单次对内存的消耗。
正常来说scroll超过超时后搜索上下文会自动删除。然而保持scroll打开是有代价的因此一旦不再使用就应明确清除scroll上下文
DELETE /_search/scroll
{scroll_id : FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFmNwcVdjblRxUzVhZXlicG9HeU02bWcAAAAAAABmzRY2YlV3Z0o5VVNTdWJobkE5Z3MtXzJB
}注意事项
scroll滚动查询不适合实时性要求高的查询场景比较适合数据迁移的场景。scroll查询完毕后要手动清理掉 scroll_id 。虽然ES有自动清理机制但是 srcoll_id 的存在会耗费大量的资源来保存一份当前查询结果集映像并且会占用文件描述符。
官方建议ES7之后不再建议使用scroll API进行深度分页。如果要分页检索超过 Top 10,000 结果时推荐使用PIT search_after
2.3.3 PIT search_after
scroll API适用于高效的深度滚动但滚动上下文成本高昂不建议将其用于实时用户请求。而search_after参数通过提供一个活动光标来规避这个问题。这样可以使用上一页的结果来帮助检索下一页。 search_after 分页查询可以简单概括为如下几个步骤
1获取索引的pit 使用 search_after 需要具有相同查询和排序值的多个搜索请求。 如果在这些请求之间发生刷新结果的顺序可能会发生变化从而导致跨页面的结果不一致。 为防止出现这种情况可以创建一个时间点 (PIT) 以保留搜索中的当前索引状态。Point In TimePIT是 Elasticsearch 7.10 版本之后才有的新特性。
# 创建一个时间点(PIT)来保存搜索期间的当前索引状态
POST /es_db/_pit?keep_alive1m
#返回结果会返回一个PID的值
{id : 39K1AwEFZXNfZGIWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAWdkhjbE9YNVRTMUNDcWNQQVR2ZXYzdwAAAAAAAAA9jhZvaGpLSDlzVVMxbW5idG5DZ0xEUHFRAAEWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAA
}2 根据pit首次查询 根据pit查询的时候不用指定索引的名词毕竟前面创建PIT的时候已经指定过了
GET /_search
{query: {match_all: {}},pit: {id: 39K1AwEFZXNfZGIWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAWdkhjbE9YNVRTMUNDcWNQQVR2ZXYzdwAAAAAAAAA9jhZvaGpLSDlzVVMxbW5idG5DZ0xEUHFRAAEWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAA, keep_alive: 1m},size: 2, sort: [{_id: asc} ]
}返回结果
{pit_id : 39K1AwEFZXNfZGIWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAWdkhjbE9YNVRTMUNDcWNQQVR2ZXYzdwAAAAAAAAA7hRZvaGpLSDlzVVMxbW5idG5DZ0xEUHFRAAEWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAA,took : 16,timed_out : false,_shards : {total : 1,successful : 1,skipped : 0,failed : 0},hits : {total : {value : 5,relation : eq},max_score : null,hits : [{_index : es_db,_type : _doc,_id : 2,_score : null,_source : {name : 李四,sex : 1,age : 28,address : 广州荔湾大厦,remark : java assistant},sort : [2,0]},{_index : es_db,_type : _doc,_id : 3,_score : null,_source : {name : 王五,sex : 0,age : 26,address : 广州白云山公园,remark : php developer},sort : [3,1]}]}
}3根据search_after和pit进行翻页查询 要获得下一页结果请使用最后一次命中的排序值包括 tiebreaker作为 search_after 参数重新运行先前的搜索。 如果使用 PIT请在 pit.id 参数中使用最新的 PIT ID。 搜索的查询和排序参数必须保持不变。这就跟我一开始提到的mysql分页优化一样的思路
注意下面的search_after参数值
#search_after指定为上一次查询返回的sort值。
GET /_search
{query: {match_all: {}},pit: {id: 39K1AwEFZXNfZGIWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAWdkhjbE9YNVRTMUNDcWNQQVR2ZXYzdwAAAAAAAAA9jhZvaGpLSDlzVVMxbW5idG5DZ0xEUHFRAAEWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAA, keep_alive: 1m},size: 2, sort: [{_id: asc} ],search_after: [ 3]
}返回结果
{pit_id : 39K1AwEFZXNfZGIWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAWdkhjbE9YNVRTMUNDcWNQQVR2ZXYzdwAAAAAAAAA8wxZvaGpLSDlzVVMxbW5idG5DZ0xEUHFRAAEWZTN2N2Nrdk5RRjY3QjBma1h5aFRodwAA,took : 1,timed_out : false,_shards : {total : 1,successful : 1,skipped : 0,failed : 0},hits : {total : {value : 5,relation : eq},max_score : null,hits : [{_index : es_db,_type : _doc,_id : 4,_score : null,_source : {name : 赵六,sex : 0,age : 22,address : 长沙橘子洲,remark : python assistant},sort : [4]},{_index : es_db,_type : _doc,_id : 5,_score : null,_source : {name : 张龙,sex : 0,age : 19,address : 长沙麓谷企业广场,remark : java architect assistant},sort : [5]}]}
}2.4 总结 学习总结
学习了一些ES的简单查询和复杂查询
感谢
感谢【jc2182】网站出品的《ElasticSearch 教程》