Elasticsearch 搜索(上)
Elasticsearch 搜索(上)
搜索简介
Elasticsearch 支持多种搜索:
- 精确搜索(词项搜索):搜索数值、日期、IP 或字符串的精确值或范围。
- 全文搜索:搜索非结构化文本数据并查找与查询项最匹配的文档。
- 向量搜索:存储向量,并使用 ANN 或 KNN 搜索来查找相似的向量,从而支持 语义搜索 等场景。
可以使用 _search API
来搜索和聚合 Elasticsearch 数据流或索引中的数据。API 的 query
请求采用 DSL 语义来进行查询。
Elasticsearch 支持两种搜索方式:URI Query 和 Request Body Query(DSL)
URI Query 示例
GET /kibana_sample_data_ecommerce/_search?q=customer_first_name:Eddie
GET /kibana*/_search?q=customer_first_name:Eddie
GET /_all/_search?q=customer_first_name:Eddie
Request Body Query(DSL)示例
POST /kibana_sample_data_ecommerce/_search
{
"query": {
"match_all": {}
}
}
当文档存储在 Elasticsearch 中时,它会在 1 秒内近乎实时地被索引和完全搜索。
Elasticsearch 基于 Lucene 开发,并引入了分段搜索的概念。分段类似于倒排索引,但 Lucene 中的单词 index
表示“段的集合加上提交点”。提交后,将向提交点添加新分段并清除缓冲区。
位于 Elasticsearch 和磁盘之间的是文件系统缓存。内存中索引缓冲区的文档会被写入新的分段,然后写入文件系统缓存,然后才刷新到磁盘。
Lucene 允许写入和打开新分段,使其包含的文档对搜索可见,而无需执行完全提交。这是一个比提交到磁盘要轻松得多的过程,并且可以频繁地完成而不会降低性能。
在 Elasticsearch 中,写入和打开新分段的这一过程称为刷新。刷新使自上次刷新以来对索引执行的所有操作都可用于搜索。
默认情况下,Elasticsearch 每秒定期刷新一次索引,但仅限于在过去 30 秒内收到一个或多个搜索请求的索引。这就是我们说 Elasticsearch 具有近实时搜索能力的原因:文档更改不会立即对搜索可见,但会在此时间范围内变得可见。
排序
在 Elasticsearch 中,默认排序是**按照相关性的评分(_score
)**进行降序排序。_score
是浮点数类型,_score
评分越高,相关性越高。评分模型的选择可以通过 similarity
参数在映射中指定。
在 5.4 版本以前,默认的相关性算法是 TF-IDF。TF 是词频(term frequency),IDF 是逆文档频率(inverse document frequency)。一个简短的解释是,一个词条出现在某个文档中的次数越多,它就越相关;但是,如果该词条出现在不同的文档的次数越多,它就越不相关。5.4 版本以后,默认的相关性算法 BM25。
此外,也可以通过 sort
自定排序规则,如:按照字段的值排序、多级排序、多值字段排序、基于 geo(地理位置)排序以及自定义脚本排序。
排序示例
单字段排序
POST /kibana_sample_data_ecommerce/_search
{
"size": 5,
"query": {
"match_all": {}
},
"sort": [
{"order_date": {"order": "desc"}}
]
}
多字段排序
POST /kibana_sample_data_ecommerce/_search
{
"size": 5,
"query": {
"match_all": {}
},
"sort": [
{"order_date": {"order": "desc"}},
{"_doc":{"order": "asc"}},
{"_score":{ "order": "desc"}}
]
}
详情参考:Sort search results
分页
默认情况下,Elasticsearch 搜索会返回前 10 个匹配的匹配项。
Elasticsearch 支持三种分页查询方式。
- from + size
- search after
- scroll
from + size
可以使用 from
和 size
参数分别指定起始页和每页记录数。
当一个查询:from = 990, size = 10,会在每个分片上先获取 1000 个文档。然后,通过协调节点聚合所有结果。最后,再通过排序选取前 1000 个文档。
页数越深,占用内存越多。为了避免深分页问题,ES 默认限定最多搜索 10000 个文档,可以通过 index.max_result_window
进行设置。
from + size 分页查询示例
POST /kibana_sample_data_ecommerce/_search
{
"from": 2,
"size": 5,
"query": {
"match_all": {}
}
}
scroll
scroll 搜索方式类似于 RDBMS 中的游标,只允许向下翻页。每次下一页查询后,使用返回结果的 scroll id 来作为下一次翻页的标记。
scroll 在搜索初始化阶段会生成快照,后续数据的变化无法及时体现在查询结果,因此更加适合一次性批量查询或非实时数据的分页查询。
启用游标查询时,需要注意设定期望的过期时间(scroll = 1m),以降低维持游标查询窗口所需消耗的资源。
注意:Elasticsearch 官方不再建议使用 scroll 查询方式进行深分页,而是推荐使用
search_after
和时间点(PIT)一起使用。
scroll 分页查询示例
POST /kibana_sample_data_ecommerce/_search?scroll=1m
{
"size": 3,
"query": {
"match": {
"currency": "EUR"
}
}
}
响应结果
{
"_scroll_id": "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAmTkWRTMzNmxBYmZUbUdsdFNqMnJoTl84Zw==",
"took": 0,
"timed_out": false,
"_shards": {
// 略
},
"hits": {
"total": {
"value": 4675,
"relation": "eq"
},
"max_score": 1,
"hits": [] // 略
}
}
search after
search after 搜索方式不支持指定页数,只能向下翻页;并且需要指定 sort,并保证值是唯一的。然后,可以反复使用上次结果中最后一个文档的 sort 值进行查询。
search after 实现的思路同 scroll 方式基本一致,通过记录上一次分页的位置标识,来进行下一次分页查询。相比于 scroll 方式,它的优点是可以实时获取数据的变化,解决了查询快照导致的查询结果延迟问题。
search after 分页查询示例
第一次查询
POST /kibana_sample_data_ecommerce/_search
{
"size": 5,
"query": {
"match_all": {}
},
"sort": [
{"order_date": {"order": "desc"}}
]
}
响应结果
{
"took": 2609,
"timed_out": false,
"_shards": {
// 略
},
"hits": {
"total": {
"value": 4675,
"relation": "eq"
},
"max_score": null,
"hits": [
// 略多条记录
// 最后一条记录
{
// 略
"sort": [1642893235000]
}
]
}
}
从上次查询的响应中获取 sort
值,然后将 sort 值插入 search after 数组:
POST /kibana_sample_data_ecommerce/_search
{
"size": 5,
"query": {
"match_all": {}
},
"search_after": [
1642893235000
],
"sort": [
{
"order_date": {
"order": "desc"
}
}
]
}
限定字段
默认情况下,搜索响应中的每个点击都包含 _source
,该字段保存了原始文本的 JSON 对象。有两种推荐的方法可以从搜索查询中检索所选字段:
折叠搜索结果
Elasticsearch 中,可以通过 collapse 对搜索结果进行分组,且每个分组只显示该分组的一个代表文档。
collapse 查询示例
POST /kibana_sample_data_ecommerce/_search
{
"size": 10,
"query": {
"match_all": {}
},
"collapse": {
"field": "day_of_week"
}
}
响应结果:
{
"took": 106,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 4675,
"relation": "eq"
},
"max_score": null,
"hits": [
{
"_index": "kibana_sample_data_ecommerce",
"_type": "_doc",
"_id": "yZUtBX4BU8KXl1YJRBrH",
"_score": 1,
"fields": {
"day_of_week": ["Monday"]
}
},
{
"_index": "kibana_sample_data_ecommerce",
"_type": "_doc",
"_id": "ypUtBX4BU8KXl1YJRBrH",
"_score": 1,
"fields": {
"day_of_week": ["Sunday"]
}
},
{
"_index": "kibana_sample_data_ecommerce",
"_type": "_doc",
"_id": "1JUtBX4BU8KXl1YJRBrH",
"_score": 1,
"fields": {
"day_of_week": ["Tuesday"]
}
},
{
"_index": "kibana_sample_data_ecommerce",
"_type": "_doc",
"_id": "1ZUtBX4BU8KXl1YJRBrH",
"_score": 1,
"fields": {
"day_of_week": ["Wednesday"]
}
},
{
"_index": "kibana_sample_data_ecommerce",
"_type": "_doc",
"_id": "2JUtBX4BU8KXl1YJRBrH",
"_score": 1,
"fields": {
"day_of_week": ["Saturday"]
}
},
{
"_index": "kibana_sample_data_ecommerce",
"_type": "_doc",
"_id": "2ZUtBX4BU8KXl1YJRBrH",
"_score": 1,
"fields": {
"day_of_week": ["Thursday"]
}
},
{
"_index": "kibana_sample_data_ecommerce",
"_type": "_doc",
"_id": "35UtBX4BU8KXl1YJRBrI",
"_score": 1,
"fields": {
"day_of_week": ["Friday"]
}
}
]
}
}
过滤搜索结果
使用带有 filter
子句的布尔查询,可以过滤搜索和聚合的结果。
使用 post_filter
可以过滤搜索的结果,但不能过滤聚合结果。
filter 示例
POST /kibana_sample_data_ecommerce/_search
{
"size": 10,
"query": {
"bool": {
"filter": {
"range": {
"taxful_total_price": {
"gte": 0,
"lte": 10
}
}
}
}
}
}
高亮
Elasticsearch 的高亮(highlight)可以从搜索结果中的一个或多个字段中获取突出显示的摘要,以便向用户显示查询匹配的位置。当请求突出显示(即高亮)时,响应结果的 highlight
字段中包括高亮的字段和高亮的片段。Elasticsearch 默认会用 <em></em>
标签标记关键字。
Elasticsearch 提供了三种高亮器,分别是默认的 highlighter 高亮器、postings-highlighter 高亮器 和 fast-vector-highlighter 高亮器。
高亮结果示例
POST /kibana_sample_data_ecommerce/_search
{
"size": 10,
"query": {
"match_all": {}
},
"highlight": {
"fields": {
"user": {
"pre_tags": [
"<strong>"
],
"post_tags": [
"</strong>"
]
}
}
}
}
详情参考:Highlighting
分片路由搜索
Elasticsearch 可以在多个节点上的多个分片中存储索引数据的副本。在运行搜索请求时,Elasticsearch 会选择包含索引数据副本的节点,并将搜索请求转发到该节点的分片。此过程称为路由。
默认情况下,Elasticsearch 使用自适应副本选择来路由搜索请求。默认情况下,自适应副本选择从所有符合条件的节点和分片中进行选择。如果要限制符合搜索请求条件的节点和分片集,可以使用 preference
查询参数。
详情参考:Search shard routing
查询规则
Elasticsearch 允许自定义查询规则来进行搜索。
搜索模板
搜索模板是可以使用不同变量运行的存储搜索。
详情参考:Search templates