Elasticsearch系列---搜索执行过程及scroll游标查询

概要

本篇主要介绍一下分布式环境中搜索的两阶段执行过程。

两阶段搜索过程

回顾我们之前的CRUD操作,因为只对单个文档进行处理,文档的唯一性很容易确定,并且很容易知道是此文档在哪个node,哪个shard中。

但搜索比CRUD复杂,符合搜索条件的文档,可能散落在各个node、各个shard中,我们需要找到匹配的文档,并且把从各个node,各个shard返回的结果进行汇总、排序,组成一个最终的结果排序列表,才算完成一个搜索过程。我们将按两阶段的方式对这个过程进行讲解。

查询阶段

假定我们的ES集群有三个node,number_of_primary_shards为3,replica shard为1,我们执行一个这样的查询请求:

GET /music/children/_search
{
  "from": 980,
  "size": 20
}

查询阶段的过程示意图如下:

  1. Java客户端发起查询请求,接受请求的node-1成为Coordinate Node(协调者),该node会创建一个priority queue,长度为from + size即1000。
  2. Coordinate Node将请求分发到所有的primary shard或replica shard中,每个shard在本地创建一个同样大小的priority queue,长度也为from + size,用于存储该shard执行查询的结果。
  3. 每个shard将各自priority queue的元素返回给Coordinate Node,元素内只包含文档的ID和排序值(如_score),Coordinate Node将合并所有的元素到自己的priority queue中,并完成排序动作,最终根据from、size值对结果进行截取。

补充说明:

  1. 哪个node接收客户端的请求,该node就会成为Coordinate Node。
  2. Coordinate Node转发请求时,会根据负载均衡算法分配到同一分片的primary shard或replica shard上,为什么说replica值设置得大一些可以增加系统吞吐量的原理就在这里,Coordinate Node的查询请求负载均衡算法会轮询所有的可用shard,并发场景时就会有更多的硬件资源(CPU、内存,IO)会参与其中,系统整体的吞吐量就能提升。
  3. 此查询过程Coordinate Node得到是轻量级的元素信息,只包含文档ID和_score这些信息,这样可以减轻网络负载,因为分页过程中,大部分的数据是会丢弃掉的。

取回阶段

在完成了查询阶段后,此时Coordinate Node已经得到查询的列表,但列表内的元素只有文档ID和_score信息,并无实际的_source内容,取回阶段就是根据文档ID,取到完整的文档对象的过程。如下图所示:

  1. Coordinate Node根据from、size信息截取要取回文档的ID,如{"from": 980, "size": 20},则取第981到第1000这20条数据,其余丢弃,from/size为空则默认取前10条,向其他shard发出mget请求。
  2. shard接收到请求后,根据_source参数(可选)加载文档信息,返回给Coordinate Node。
  3. 一旦所有的shard都返回了结果,Coordinate Node将结果返回给客户端。

前面几篇有提到deep paging的问题,我们在这里又复习一遍,使用from和size进行分页时,传递信息给Coordinate Node的每个shard,都创建了一个from + size长度的队列,并且Coordinate Node需要对所有传过来的数据进行排序,工作量为number_of_shards * (from + size),然后从里面挑出size数量的文档,如果from值特别大,那么会带来极大的硬件资源浪费,鉴于此原因,强烈建议不要使用深分页。

不过深分页操作很少符合人的行为,翻几页还看不到想要的结果,人的第一反应是换一个搜索条件,只有机器人或爬虫才这么不知疲倦地一直翻页直到服务器崩溃。

preference设置

查询时使用preference参数,可以影响哪些shard可以用来执行搜索操作,6.1.0版本后,许多参数值已声明为弃用,我们挑几个目前还在使用的简单介绍一下:

  • _only_local:只搜索当前node中的shard
  • _local:优先搜索当前node中的shard,搜不到再去其他的shard
  • _prefer_nodes:abc,xyz:优先从指定的abc/xyz节点上搜索,如果两个节点都有存在数据的shard,随机从里面挑一个节点执行搜索
  • _only_nodes:abc,xyz,...:只在符合通配abc、xyz名称的节点上搜索,如果多个节点都有存在数据的shard,随机从里面挑一个节点执行搜索
  • _shards:2,3:指定shard进行搜索,这个条件如与其他条件搭配使用,此条件要写在前面,如_shards:2,3|_local
  • 自定义字符串:一般用sessionid或userid

bouncing results问题

假如两个文档有相同的字段值,并且时间戳也一样,如果按时间戳字段来排序,由于请求是在所有可用的shard上轮询的,可能存在一种情况:这两个文档记录在不同的shard之间保存的顺序不相同。结果就是同一个条件的查询,如果执行多次,分配在primary shard得到的是一种顺序,分配在replica shard又是另一个顺序,这个就是所谓的bouncing results问题。

如何避免:让同一个用户始终使用同一个shard,就可以避免这种问题,常见的做法是preference设置为sessionid或userid,如:

GET /music/children/_search?preference=10086
{
  "from": 980,
  "size": 20
}

超时问题

我们回顾查询阶段和取回阶段,必须所有的操作都完成了,才给客户端返回结果,如果中途有shard在执行特别重的任务,导致查询很慢怎么办?会拖慢整个集群吗?

如果是高并发场景,那极有可能,因为某一个节点慢,整个查询请求堆积,拖死集群都有可能。

为了防止这一情况,我们使用timeout参数,告诉shard允许处理数据的最大时间,时间一到,执行关门动作,能有多少数据返回多少数据,剩下的不要了,这样可以确保集群是稳定运行的,如下图所示:

routing

在设计大规模数据搜索时,我们为了实现数据集中性,索引时会按一定规则将数据进行存储,比如订单数据,我们会按userid为route key,每个userid的订单数据,都放在同一个shard上,既然存储时使用了route key,那么搜索时同样使用route key,可以让查询只搜索相关的shard,如:

GET /music/children/_search?routing=10086
{
  "from": 980,
  "size": 20
}

这样由于精准到具体的shard,可以极大的缩小搜索范围,数据量越大,效果越明显。

搜索类型

默认的搜索类型是query_then_fetch,我们还可以选择dfs_query_then_fetch,这个有预查询阶段,可以从所有相关shard中获取词频来计算全局词频,可以提升revelance sort精准度。

scroll游标查询

如果我们要把大批量的数据从ES集群中取出,用来执行一些计算,一次性取完肯定不合适,IO压力过大,性能容易出问题,分页查询又容易造成deep paging的问题。一般推荐使用scroll查询,一批一批的查,直到所有数据都查询完。

原理

  • scroll查询会先做查询初始化,然后再批量地拉取结果,有点像数据库的cursor。
  • scroll查询会取某个时间点的快照数据,查询初始化后索引上的数据发生了变化,快照数据还是原来的,有点像数据库的索引视图。
  • scroll查询用字段_doc排序,去掉了全局排序,性能比较高。
  • scroll查询要设置过期时间,每次搜索在这个时间内完成即可。

示例

我们假定每次取10条数据,时间窗口为1秒
请求如下:

GET /music/children/_search?scroll=1s
{
  "size": 10
}

响应如下(结果有删减):

{
  "_scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAABJQFkExczF1dXM3VHB1RFNpVDR4RkxPb1EAAAAAAAASUhZBMXMxdXVzN1RwdURTaVQ0eEZMT29RAAAAAAAAElMWQTFzMXV1czdUcHVEU2lUNHhGTE9vUQAAAAAAABJUFkExczF1dXM3VHB1RFNpVDR4RkxPb1EAAAAAAAASURZBMXMxdXVzN1RwdURTaVQ0eEZMT29R",
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 4,
    "max_score": 1,
    "hits": [
      {
        "_index": "music",
        "_type": "children",
        "_id": "2",
        "_score": 1,
        "_source": {
          "name": "wake me, shark me",
          "content": "don't let me sleep too late, gonna get up brightly early in the morning",
          "language": "english",
          "length": "55",
          "likes": 0,
          "author": "John Smith"
        }
      }
    ]
  }
}

注意那个scroll_id,下次再查询时,只要带上这个就行了

GET /_search/scroll
{
    "scroll": "1s",
    "scroll_id" : "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAABJQFkExczF1dXM3VHB1RFNpVDR4RkxPb1EAAAAAAAASUhZBMXMxdXVzN1RwdURTaVQ0eEZMT29RAAAAAAAAElMWQTFzMXV1czdUcHVEU2lUNHhGTE9vUQAAAAAAABJUFkExczF1dXM3VHB1RFNpVDR4RkxPb1EAAAAAAAASURZBMXMxdXVzN1RwdURTaVQ0eEZMT29R"
}

每次的查询,都把最新的scroll_id带上,直到数据查询完成为止。

scroll查询看起来像分页,但使用场景不一样,分页主要是按页展示数据,主要受众是人,scroll一批一批的获取数据,主要受众一般是数据分析的系统,是给系统用的。
性能也不同,前面我们了解后,分页查询随着页数的加深,压力越来越大,而scroll是基于_doc排序的数据处理,特别适用于大批量数据的获取分析。

小结

本篇详细介绍了查询的两阶段过程,以及能够影响查询行为的一些参数设置,历经多个版本迭代,有些preference参数已经不用了,了解一下就行,另外介绍了bouncing results产生的原理及规避办法,最后介绍了一下大批量数据查询利器scroll的简单用法。

专注Java高并发、分布式架构,更多技术干货分享与心得,请关注公众号:Java架构社区
可以扫左边二维码添加好友,邀请你加入Java架构社区微信群共同探讨技术

原文地址:https://www.cnblogs.com/huangying2124/p/12208276.html

时间: 2024-11-11 21:54:33

Elasticsearch系列---搜索执行过程及scroll游标查询的相关文章

代码分析系列 内存执行过程

1.内存执行过程:(六:01:32:08-->) package primary; public class Main {    public static void main(String args[]){        Main m= new Main();        int day = 15;        Birthday b1 = new Birthday(27,11,1988);        Birthday b2 = new Birthday(23,04,2013);    

python随笔系列--import执行过程简单论证

结论:模块在一个python解释器(一次生命周期)中,一个模块只被引入一次验证过程 ins01 site-packages]# echo 'print(11111)' > kai.py ins01 site-packages]# echo -e 'import kai\nprint(2222)' > yun.py ins01 ~]# python3 >>> import yun 1111111 22222 >>> import kai #这里没有执行kai里

[Elasticsearch] 全文搜索 (一) - 基础概念和match查询

全文搜索(Full Text Search) 现在我们已经讨论了搜索结构化数据的一些简单用例,是时候开始探索全文搜索了 - 如何在全文字段中搜索来找到最相关的文档. 对于全文搜索而言,最重要的两个方面是: 相关度(Relevance) 查询的结果按照它们对查询本身的相关度进行排序的能力,相关度可以通过TF/IDF,参见什么是相关度,地理位置的邻近程度(Proximity to a Geo-location),模糊相似性(Fuzzy Similarity)或者其它算法进行计算. 解析(Analys

PHP内核解密系列:zend_execute的执行过程

PHP内核解密系列:zend_execute的执行过程 解释器引擎最终执行op的函数是zend_execute,实际上zend_execute是一个函数指针,在引擎初始化的时候zend_execute默认指向了execute,这个execute定义在{PHPSRC}/Zend/zend_vm_execute.h: ZEND_API void execute(zend_op_array *op_array TSRMLS_DC) { zend_execute_data *execute_data;

[jvm解析系列][十三]字节码指令小节,从字节码看JVM的栈解释器执行过程。

众所周知,JVM以前一直采用的是解释执行,但是后来在历代的版本更迭中也加入了编译执行.所以总的来说JVM是包含了解释执行和编译执行.这一部分不属于JVM的范畴了,已经属于编译了,大多数都是进行词法分析之类的,以后有时间会补充. 同时大家都知道现在大体上分为两种指令集架构,第一种就是基于栈的第二种是基于寄存器的,简单点说,基于寄存器的架构速度更快,但是可移植性不强,但是基于栈的指令集架构虽然慢,但是可移植性很强,大家都知道java本身就是依靠可移植性出名的,所以无可争议的使用了栈的指令集架构.(也

[Elasticsearch] 分布式搜索

分布式搜索 本文翻译自Elasticsearch官方指南的Distributed Search Execution一章. 在继续之前,我们将绕一段路来谈谈在分布式环境中,搜索是怎样运行的.和在分布式文档存储(Distributed Document Store)中讨论的基本CRUD操作相比,这个过程会更加复杂一些. 一个CRUD操作会处理一个文档,该文档有唯一的_index,_type和路由值(Routing Value,它默认情况下就是文档的_id)组合.这意味着我们可以知道该文档被保存在集群

Elasticsearch 数据搜索篇·【入门级干货】

ES即简单又复杂,你可以快速的实现全文检索,又需要了解复杂的REST API.本篇就通过一些简单的搜索命令,帮助你理解ES的相关应用.虽然不能让你理解ES的原理设计,但是可以帮助你理解ES,探寻更多的特性. 其他相关的内容参考:Elasticsearch官方文档翻译 样例数据 为了更好的使用和理解ES,没有点样例数据还是不好模拟的.这里提供了一份官网上的数据,accounts.json.如果需要的话,也可以去这个网址玩玩,它可以帮助你自定义写随机的JSON数据. 首先开启你的ES,然后执行下面的

SQL执行过程

一般来说,数据库处理SQL都会经过三个过程:分析.执行.返回结果,比如COGNOS ReportNet通过拖放式完成表现层后,还是会自动生成SQL,然后将SQL传递到ORACLE进行处理. 1.分析 分析是处理SQL语句的第一步,它是SQL语句处理过程较为重要的一步,它又包含几个方面: (1)语法分析,oracel是采用数据库常用的自底向上的分析方法,包含检查语法规范,命名规范,它是处理SQL语句中最消耗时间且代价最高的步骤,主要表现在绑定变量和存储过程等方面: A.绑定变量:这也是为什么使用在

Elasticsearch 数据搜索篇·【入门级干货】===转

ES即简单又复杂,你可以快速的实现全文检索,又需要了解复杂的REST API.本篇就通过一些简单的搜索命令,帮助你理解ES的相关应用.虽然不能让你理解ES的原理设计,但是可以帮助你理解ES,探寻更多的特性. 其他相关的内容参考:Elasticsearch官方文档翻译 样例数据 为了更好的使用和理解ES,没有点样例数据还是不好模拟的.这里提供了一份官网上的数据,accounts.json.如果需要的话,也可以去这个网址玩玩,它可以帮助你自定义写随机的JSON数据. 首先开启你的ES,然后执行下面的