[这是我参与11月更文挑战的第22天,活动详情查看:2021最后一次更文挑战]
搜索请求用于:
- 在ElasticSearch 的数据流或者索引中获取数据;
- 在响应的hits或 search results中返回匹配文档。
搜索(_search)
我们可以使用 _search
API 搜索和聚合 存储在Elasticsearch 数据流或索引中的数据;查询请求API接收使用Query DSL编写的 查询语句。
测试数据
定义mapping:
PUT /book
{
"mappings" : {
"properties" : {
"author" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"info" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"name" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"price" : {
"type" : "long"
},
"publish" : {
"type" : "keyword"
},
"type" : {
"type" : "keyword"
}
}
}
}
复制代码
导入相关数据:
curl -XPOST localhost:9200/book/_bulk?pretty -H "Content-Type: application/x-ndjson" --data-binary @bookdata.json
复制代码
match
match
可以用于查询指定字段:
GET /book/_search?pretty
{
"track_total_hits": true,
"query": {
"match": {
"name": "光学教程"
}
}
}
复制代码
查询结果如下
{
...
"hits" : {
"total" : {
"value" : 635,
"relation" : "eq"
}
},
...
}
复制代码
其中,track_total_hits
默认为true,用于显示命中数。
match_all
match_all
参数用于查询所有的数据,例如
GET /book/_search?pretty
{
"query": {
"match_all": {}
}
}
复制代码
terminate_after与size
terminate_after
参数用于设置搜索的最大数据,当检索到指定数量后,返回size
大小的文档数。
GET /book/_search?q=name:光学教程&size=10&terminate_after=15
复制代码
分页
ElasticSearch中,支持对查询的结果集进行分页,需要设置以下两个参数:
from
:表示跳过的数据,默认为0size
:表示此次请求中,结果集展示的数据量
例如:
POST /book/_search
{
"query": {
"match": {
"type": "大学教材"
}
},
"from": 5,
"size": 10
}
复制代码
Elasticsearch提供了两种分页切换(下一页)方式:
滚动搜索API(scroll):不推荐使用,建议使用PITsearch_after
以及PIT
使用search_after的前提条件:
- 多个搜索请求具有相同
query
和sort
值; - 为了避免
reflash
导致的页面不一致,需要设置PIT保存当前索引的状态
什么是PIT
PIT的全称是“Point in time”,被称为时间点;它是一个轻量级的视图,可以查看启动时存在的数据状态。如果启用了 Elasticsearch 安全特性,必须具有目标数据流、索引或别名的read
权限。
但是时间点的设置,会阻止Elasticsearch合并段,因为这些段还在使用中。保持旧段处于活动状态意味着需要更多的磁盘空间和文件句柄。
例如:
# 添加时间点
POST /book/_pit?keep_alive=1m
# 深度分页
POST /_search
{
"query": {
"match": {
"type": "大学教材"
}
},
"pit":{
"id" : "27W0AwEEYm9vaxZzVUFpSG9tN1RHcU9uWkhBUTJTQUlBABZmMllUWTlHbFN6R3ZxYnp1M1pVWVpnAAAAAAAAAQpbFi1wMGo2WDdhUkhLZlVLYUxlZlF0ZGcAARZzVUFpSG9tN1RHcU9uWkhBUTJTQUlBAAA=",
"keep_alive": "1m"
},
"sort": [
{
"price": {
"order": "desc"
}
}
],
"size": 1
}
# 设置返回的 "sort" : [ XX]
# 设置 search_after、pit
POST /_search
{
"query": {
"match": {
"type": "大学教材"
}
},
"pit":{
"id" : "27W0AwEEYm9vaxZzVUFpSG9tN1RHcU9uWkhBUTJTQUlBABZmMllUWTlHbFN6R3ZxYnp1M1pVWVpnAAAAAAAAAQpbFi1wMGo2WDdhUkhLZlVLYUxlZlF0ZGcAARZzVUFpSG9tN1RHcU9uWkhBUTJTQUlBAAA=",
"keep_alive": "1m"
},
"sort": [
{
"price": {
"order": "desc"
}
}
],
"size": 1,
"search_after" :[ 60,9]
}
复制代码
查询完成后,最好手动删除PIT,避免影响段合并
DELETE /_pit
{
"id" : "27W0AwEEYm9vaxZzVUFpSG9tN1RHcU9uWkhBUTJTQUlBABZmMllUWTlHbFN6R3ZxYnp1M1pVWVpnAAAAAAAAAQpbFi1wMGo2WDdhUkhLZlVLYUxlZlF0ZGcAARZzVUFpSG9tN1RHcU9uWkhBUTJTQUlBAAA="
}
复制代码
默认情况下,不能使用
from
和size
翻阅超过 10,000 次点击,该值由index.max_result_window
参数决定。
查询指定字段
POST book/_search
{
"fields": ["type","name"],
"_source": false
}
复制代码
异步搜索(_async_search)
在实际使用中,存在需要长时间运行的查询,可能需要跨越多个远程的集群节点,因此预计不会在几毫秒内返回结果。在这种情况下,适合提交异步执行的搜索请求;还可以监视请求的进度,并在稍后阶段检索结果。
提交异步搜索
POST /book/_async_search?pretty
{
"track_total_hits": true,
"query": {
"match": {
"name": "大学英语语法"
}
}
}
复制代码
响应会包含正在执行的搜索的标识符,可以使用此 ID 稍后检索搜索的最终结果
查询异步进度
GET /_async_search/FmRldE8zREVEUzA2ZVpUeGs2ejJFUFEaMkZ5QTVrSTZSaVN3WlNFVmtlWHJsdzoxMDc=
复制代码
搜索结果如下:
{
"id" : "FmRldE8zREVEUzA2ZVpUeGs2ejJFUFEaMkZ5QTVrSTZSaVN3WlNFVmtlWHJsdzoxMDc=",
"is_partial" : true,
"is_running" : true,
"start_time_in_millis" : 1583945890986,
"expiration_time_in_millis" : 1584377890986,
"response" : {
"took" : 12144,
"timed_out" : false,
"num_reduce_phases" : 46,
"_shards" : {
"total" : 562,
"successful" : 188,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 456433,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"sale_date" : {
"buckets" : []
}
}
}
}
复制代码
- is_partial:执行查询时,
is_partial
始终设置为true
。查询结束时若为false,则表示查询失败 - is_running:表示异步查询是否正在执行
- expiration_time_in_millis:表示异步搜索的截止时间
- num_reduce_phases:表示查询结果集的数量
- successful:指示有多少分片已执行查询
- aggregations:部分聚合结果,来自已经完成查询执行的分片
检索搜索结果
GET /_async_search/status/FmRldE8zREVEUzA2ZVpUeGs2ejJFUFEaMkZ5QTVrSTZSaVN3WlNFVmtlWHJsdzoxMDc=
复制代码
删除异步搜索
可以使用删除异步搜索 API 按 ID 手动删除异步搜索:
- 如果搜索仍在运行,搜索请求将被取消;
- 否则,保存的搜索结果将被删除
DELETE /_async_search/FmRldE8zREVEUzA2ZVpUeGs2ejJFUFEaMkZ5QTVrSTZSaVN3WlNFVmtlWHJsdzoxMDc=
复制代码
折叠搜索结果
ES提供了参数collapse
用于对指定字段对数据结果集进行折叠
,不支持text
类型的数据。例如:
GET /book/_search?pretty
{
"query": {
"match_all": {
}
},
"_source": ["name","type","price"],
"collapse": {
"field": "type"
},
"sort": [
{
"price": {
"order": "desc"
}
}
],
"size": 1
}
复制代码
查询结果如下:
{
"took" : 0,
...
"hits" : [
{
"_index" : "book",
"_type" : "_doc",
"_id" : "379",
"_score" : null,
"_source" : {
"price" : 269,
"name" : "生物统计学和生物信息学最新进展",
"type" : "生物科学"
},
"fields" : {
"type" : [
"生物科学"
]
},
"sort" : [
269
]
}
]
}
}
复制代码
查询结果中存在多个生物科学
类型的数据,将该结果集折叠后只展示一个数据。该字段需要在mapping中定义,否则也无法生效,可以查询相应的映射:
GET /book/_mapping?pretty
复制代码
过滤搜索结果集
ElasticSearch 提供了两种方法用于过滤搜索得到的结果集:
- 带有
filter
子句的布尔查询
- 后过滤器:
post_filter
字句
布尔查询——filter
布尔查询的filter子句会在过滤器上下文中执行,会忽略评分并考虑缓存子句。
POST /book/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"publish": "高等教育出版社"
}
}
]
}
},
"aggs": {
"all_publish": {
"terms": {
"field": "publish",
"size": 10
}
}
},
"size": 1,
"_source": ["name","type","price"]
}
复制代码
post_filter字句
通过query字句可以查询数据,通过aggs字句可以获取聚合数据。而post_filter字句可以在查询后再对数据进行过滤,但是不会影响过聚合结果。
例如:
POST /book/_search
{
"query": {
"match": {
"type": "大学教材"
}
},
"aggs": {
"all_publish": {
"terms": {
"field": "publish",
"size": 10
}
}
},
"post_filter": {
"term": {
"type": "测试"
}
}
}
复制代码
查询结果如下,hits中并不包含字句,但是aggregations中已经包含了数据聚合的结果。
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 0,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"all_publish" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "高等教育出版社",
"doc_count" : 211
},
{
"key" : "科学出版社",
"doc_count" : 5
}
]
}
}
}
复制代码
当需要对搜索结果和聚合结果做不同的过滤时,才需要使用
post_filter
。post_filter
的特性是在 查询后 执行,过滤后会影响缓存等性能优化,所以只应当与聚合一起使用。
近实时搜索
需要先了解两个概念:
- 实时搜索:数据插入数据库后
立即
就可以搜索 - 近实时(Near Real Time,简称NRT)搜索:意味着文档在被索引后
几乎立即
可用于搜索。
ElasticSearch借助Lucene 中的段实现了近实时搜索,那什么是段(Segment)
呢?
段(Segment)
ElasticSearch索引由几个分片及副本组成,每个分片都是一个Lucene索引。
Lucene通过倒排索引处理数据,这个索引是不可变的。当添加新的文档时,如果每次都创建新的倒排索引,那么会消耗大量的资源。所以,在Lucene 中索引被分成更小的块,是一个小型的倒排索引,称为段(Segment)
。
reflash、合并与提交
当添加新的文档时,在打开、提交、关闭writer后,会创建一个新的段。向索引中添加新文档时,它们会被添加到下一个段中,以前的段永远不会被修改。
当Segment加入内存时已经可以被访问了,但是还没有被持久化到磁盘中,因此会在异常时丢失。
默认情况下,ElasticSearch 每隔1秒会用Buffer中的document新建一个 Segment,这个操作叫做刷新(refresh)。因为有一秒的间隔时间,所以ElasticSearch的搜索是近实时的。
如果每秒创建一个,那么Segment的数量必然是爆炸性增长,会在很短的时间内消耗掉所有的句柄。
在ElasticSearch中,有一个线程专门用于合并多个小Segment;在合并的同时,还会排除掉修改或删除的老版本的Segment。最后,会修改Commit Point,这时候数据才被持久化。
translog
对 Lucene 的更改后,只会在 Lucene 提交期间
持久保存到磁盘,这是一项相对昂贵的操作,因此无法在每次索引或删除操作后执行。在两次提交之间,虽然数据可以被查询到;但也只是存在于内存中,随时有可能因为异常导致数据丢失。在每一次对 Elasticsearch 进行操作时,会将数据记录到事务日志(translog)中,默认是每5秒记录
一次。
流程如下:
- 文档被索引后,会添加到内存缓冲区,同时会追加到translog当中
- 当reflash后,段列表会被写入到提交点中,缓存会清空但事务日志不会
- 步骤二重复N次后,事务日志会变得很大;这时索引会被flush,会有以下几个变化
- 段被
全量提交
,并清空了内存缓冲区 - 清空事务日志
- 段被
异常恢复:
- 当ElasticSearch启动后,它会从磁盘中读取最后一个提交点去恢复最新的段,然后会重新加载tranwslog中记录的未提交的变更。
_flush API
执行一个提交并且截断 translog 的行为在 Elasticsearch 被称作一次 flush。分片每30分钟被自动刷新
(flush),或者在 translog 太大的时候也会刷新。例如:
POST /book/_flush
复制代码
相关设置
- index.translog.sync_interval:设置日志写入间隔,默认是5S
- index.translog.durability
- request(默认),所有的请求都会被记录到trans log 并持久化到磁盘
- async,异步方式;默认间隔5秒后记录一次,可能丢失数据
- index.translog.flush_threshold_size:translog的最大阈值,如果达到这个界限那么会自动触发flush,默认是512mb。
关于近实时搜索,也可以阅读十张图带大家看懂 ES 原理,虽然不是最新的,但逻辑很清晰。