• elasticsearch深度分页问题


    一、深度分页方式from + size
    es 默认采用的分页方式是 from+ size 的形式,在深度分页的情况下,这种使用方式效率是非常低的,比如我们执行如下查询:

    GET /student/student/_search
    {
      "query":{
        "match_all": {}
      },
      "from":5000,
      "size":10
    }

    意味着 es 需要在各个分片上匹配排序并得到5010条数据,协调节点拿到这些数据再进行排序等处理,然后结果集中取最后10条数据返回。我们会发现这样的深度分页将会使得效率非常低,因为我只需要查询10条数据,而es则需要执行from+size条数据然后处理后返回。其次:es为了性能,限制了我们分页的深度,es目前支持的最大的 max_result_window = 10000;也就是说我们不能分页到10000条数据以上。 例如:

    from + size <= 10000所以这个分页深度依然能够执行。

    继续看上图,当size + from > 10000;es查询失败,并且提示:

    Result window is too large, from + size must be less than or equal to: [10000] but was [10001]

    接下来看还有一个很重要的提示:

    See the scroll api for a more efficient way to request large data sets. This limit can be set by changing the [index.max_result_window] index level setting

    有关请求大数据集的更有效方法,请参阅滚动api。这个限制可以通过改变[索引]来设置。哦呵,原来es给我们提供了另外的一个API scroll。难道这个 scroll 能解决深度分页问题?
    二、深度分页之scroll
    在es中如果我们分页要请求大数据集或者一次请求要获取较大的数据集,scroll都是一个非常好的解决方案。使用scroll滚动搜索,可以先搜索一批数据,然后下次再搜索一批数据,以此类推,直到搜索出全部的数据来scroll搜索会在第一次搜索的时候,保存一个当时的视图快照,之后只会基于该旧的视图快照提供数据搜索,如果这个期间数据变更,是不会让用户看到的。每次发送scroll请求,我们还需要指定一个scroll参数,指定一个时间窗口,每次搜索请求只要在这个时间窗口内能完成就可以了。一个滚屏搜索允许我们做一个初始阶段搜索并且持续批量从Elasticsearch里拉取结果直到没有结果剩下。这有点像传统数据库里的cursors(游标)。滚屏搜索会及时制作快照。这个快照不会包含任何在初始阶段搜索请求后对index做的修改。它通过将旧的数据文件保存在手边,所以可以保护index的样子看起来像搜索开始时的样子。这样将使得我们无法得到用户最近的更新行为。
    scroll的使用很简单,执行如下curl,每次请求两条。可以定制 scroll = 5m意味着该窗口过期时间为5分钟。

    GET /student/student/_search?scroll=5m
    {
      "query": {
        "match_all": {}
      },
      "size": 2
    }

    返回参数为:

    {
      "_scroll_id" : "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAC0YFmllUjV1QTIyU25XMHBTck1XNHpFWUEAAAAAAAAtGRZpZVI1dUEyMlNuVzBwU3JNVzR6RVlBAAAAAAAALRsWaWVSNXVBMjJTblcwcFNyTVc0ekVZQQAAAAAAAC0aFmllUjV1QTIyU25XMHBTck1XNHpFWUEAAAAAAAAtHBZpZVI1dUEyMlNuVzBwU3JNVzR6RVlB",
      "took" : 0,
      "timed_out" : false,
      "_shards" : {
        "total" : 5,
        "successful" : 5,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : 6,
        "max_score" : 1.0,
        "hits" : [
          {
            "_index" : "student",
            "_type" : "student",
            "_id" : "5",
            "_score" : 1.0,
            "_source" : {
              "name" : "fucheng",
              "age" : 23,
              "class" : "2-3"
            }
          },
          {
            "_index" : "student",
            "_type" : "student",
            "_id" : "2",
            "_score" : 1.0,
            "_source" : {
              "name" : "xiaoming",
              "age" : 25,
              "class" : "2-1"
            }
          }
        ]
      }
    }

    在返回结果中,有一个很重要的:_scroll_id
    在后面的请求中我们都要带着这个 scroll_id 去请求。现在student这个索引中共有6条数据,id分别为 1, 2, 3, 4, 5, 6。当我们使用 scroll 查询第4次的时候,返回结果应该为kong。这时我们就知道已经结果集已经匹配完了。继续执行3次结果如下三图所示。

     GET /_search/scroll
     {
       "scroll":"5m",
       "scroll_id":"DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAC0YFmllUjV1QTIyU25XMHBTck1XNHpFWUEAAAAAAAAtGRZpZVI1dUEyMlNuVzBwU3JNVzR6RVlBAAAAAAAALRsWaWVSNXVBMjJTblcwcFNyTVc0ekVZQQAAAAAAAC0aFmllUjV1QTIyU25XMHBTck1XNHpFWUEAAAAAAAAtHBZpZVI1dUEyMlNuVzBwU3JNVzR6RVlB"
     }

    由结果集我们可以发现最终确实分别得到了正确的结果集,并且正确的终止了scroll。
    三、search_after
    from + size的分页方式虽然是最灵活的分页方式,但是当分页深度达到一定程度将会产生深度分页的问题。scroll能够解决深度分页的问题,但是其无法实现实时查询,即当scroll_id生成后无法查询到之后数据的变更,因为其底层原理是生成数据的快照。这时 search_after应运而生。其是在es-5.X之后才提供的。
    search_after 是一种假分页方式,根据上一页的最后一条数据来确定下一页的位置,同时在分页请求的过程中,如果有索引数据的增删改查,这些变更也会实时的反映到游标上。为了找到每一页最后一条数据,每个文档必须有一个全局唯一值,官方推荐使用 _uid 作为全局唯一值,但是只要能表示其唯一性就可以。
    为了演示,我们需要给上文中的student索引增加一个uid字段表示其唯一性。
    执行如下查询:

    GET /student/student/_search
    {
      "query":{
        "match_all": {}
      },
      "size":2,
      "sort":[
        {
          "uid": "desc"
        }
      ]
    }

    结果集:

    {
      "took" : 1,
      "timed_out" : false,
      "_shards" : {
        "total" : 5,
        "successful" : 5,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : 6,
        "max_score" : null,
        "hits" : [
          {
            "_index" : "student",
            "_type" : "student",
            "_id" : "6",
            "_score" : null,
            "_source" : {
              "uid" : 1006,
              "name" : "dehua",
              "age" : 27,
              "class" : "3-1"
            },
            "sort" : [
              1006
            ]
          },
          {
            "_index" : "student",
            "_type" : "student",
            "_id" : "5",
            "_score" : null,
            "_source" : {
              "uid" : 1005,
              "name" : "fucheng",
              "age" : 23,
              "class" : "2-3"
            },
            "sort" : [
              1005
            ]
          }
        ]
      }
    }

    下一次分页,需要将上述分页结果集的最后一条数据的值带上。

    GET /student/student/_search
    {
      "query":{
        "match_all": {}
      },
      "size":2,
      "search_after":[1005],
      "sort":[
        {
          "uid": "desc"
        }
      ]
    }

    这样我们就使用search_after方式实现了分页查询。
    四、三种分页方式比较

    分页方式 性能 优点 缺点 场景
    from + size 灵活性好,实现简单 深度分页问题 数据量比较小,能容忍深度分页问题
    scroll 解决了深度分页问题

    无法反应数据的实时性(快照版本)

    维护成本高,需要维护一个 scroll_id

    海量数据的导出(比如笔者刚遇到的将es中20w的数据导入到excel)

    需要查询海量结果集的数据

    search_after

    性能最好

    不存在深度分页问题

    能够反映数据的实时变更

    实现复杂,需要有一个全局唯一的字段

    连续分页的实现会比较复杂,因为每一次查询都需要上次查询的结果

    海量数据的分页

    scroll:为了满足深度分页的场景,es 提供了 scroll 的方式进行分页读取。原理上是对某次查询生成一个游标 scroll_id , 后续的查询只需要根据这个游标去取数据,直到结果集中返回的 hits 字段为空,就表示遍历结束。scroll_id 的生成可以理解为建立了一个临时的历史快照,在此之后的增删改查等操作不会影响到这个快照的结果。scroll方式官方的建议并不是用于实时的请求,因为每一个 scroll_id 不仅会占用大量的资源(特别是排序的请求),而且是生成的历史快照,对于数据的变更不会反映到快照上。这种方式往往用于非实时处理大量数据的情况,比如要进行数据迁移或者索引变更之类的。
    search_after原理:为我们在sort中指明了唯一字段_uid,所以查询的数据整体肯定是有序的,在第二次查询时,同时将search_after指定的值作为查询条件(类似游标),指定从整个有序数据哪个位置继续查询。
    search_after缺陷:search_after并不能代替from+ size和scroll,它每第二次读取时,必须要指定查询地址(游标search_after),实际使用时,只能一页一页的向下翻,而不能跳页查询。

    郭慕荣博客园
  • 相关阅读:
    jQuery 选择器 与 事件
    JavaScript 语法
    Java 包(package)
    Java 接口
    java封装继承多态---继承篇
    Java数组的学习
    java基础周总结
    JAVA基础接口集合框架
    周总结
    Java基础
  • 原文地址:https://www.cnblogs.com/jelly12345/p/15714525.html
Copyright © 2020-2023  润新知