问题

面试官:能说说es的深度分页的解决办法吗? 我:额,额,额 面试官:好今天面试就到这里吧!回去等待通知。

实验环境

  • ES版本:es6.4.2
  • 实验数据来自锥智项目Stage环境的zipkin-span索引
    1. GET /_cat/count/zipkin2:span-2022-03-29?v
    image.png

    常规ES分页测试

  1. ES分页写法
    • 指定fromsize即可。
    • from=0,size=10等价于SQL中limit 0,10 ```json GET /zipkin2:span-2022-03-29/_search { “from”: 0, “size”: 10 }
  1. 2. Java Transport Client API
  2. ```java
  3. SearchRequestBuilder searchRequestBuilder = transportClient.prepareSearch("zipkin2:span-2022-03-29")
  4. .setFrom(from)
  5. .setSize(size);
  6. SearchResponse response = searchRequestBuilder.get();

常规分页问题

6.4.2官方文档中点出:from+size>max_result_window 就不可以用from和size了。max_result_window默认值为10000 image.png

报错信息展示:
image.png

Scroll解决方法

  • 特点:

    • 不适合用户实时查询数据的场景
    • 适合数据从一个索引到另一个索引迁移查询场景

      原理

  • es帮我们生成一个数据的快照。

  • 每次查询是走的一个快照数据
  • es会帮我们维护一个游标,记录我们上次读到哪里了

image.png

使用方式

步骤1:设置每页大小,指定scroll的存活时间

  1. GET /zipkin2:span-2022-03-29/_search?scroll=1m
  2. {
  3. "size": 10
  4. }

执行结果:
image.png

这里会返回一个_scroll_id,用作下次查询的入参

步骤2:进行之后每页数据查询

  1. GET _search/scroll
  2. {
  3. "scroll":"1m",
  4. "scroll_id":"DnF1ZXJ5VGhlbkZldGNoBQAAAAABn6m7FkplSFdrWUhlVFZPVzdDYzQ2ZXI2cXcAAAAAAZ-pvRZKZUhXa1lIZVRWT1c3Q2M0NmVyNnF3AAAAAAGfqb8WSmVIV2tZSGVUVk9XN0NjNDZlcjZxdwAAAAABn6m-FkplSFdrWUhlVFZPVzdDYzQ2ZXI2cXcAAAAAAZ-pvBZKZUhXa1lIZVRWT1c3Q2M0NmVyNnF3"
  5. }
  • scroll_id:一直指定首次查询的scroll_id
  • scroll:暂时理解,是每次为scroll_id中正在使用的游标续命(这块我在后面解释)

    分片Scroll

    分片Scroll其实就是原本的一个Scroll分为多个Scroll。可以指定字段进行分片。多个分片的Scroll数据加起来是之前一个Scroll的数据。

    不分片的情况

    image.png

    分片的情况

    image.png

    每次查询中指定scroll的作用

    image.png
  • 作用:就是为当前使用的search context(游标) 续命。若不指定scroll,当前的search context(游标)就被释放了。

  • 以我实验的结果,推测一下大致的模型

    • 就是说,我原数据从不同的起点开始分成多个search context
    • 最开始使用第一个search context。
    • 若前面的search context过期啦,从接下来的search context开始走
    • 每个search context有个游标。

image.png

Search After解决方法

Scroll可以解决深度分页问题,但是对于用户实时查询的场景,Scroll的价值太大了。 image.png

  • 特点
    • 可以满足用户实时查询的场景
    • 数据实时变动的,可能同一个数据出现在不同页两次

      原理

      SearchAfter 其思想就是利用前一页的最后一条数据,来获取下一页数据。
  • 比如:我上一页的最后一条数据id=100,那我下一页就要从id>100查询。

    对应SQL深度分页

    ```plsql

    上一页最后一个数据的id =100

    select * from table_name where id>100 limit 10;
  1. <a name="QO1AC"></a>
  2. ### 使用方式
  3. 在我的数据集中,有个timestamp_millis时间戳字段。当然这里用时间戳不合适。因为有重复
  4. > 官方说一定要无重复字段:
  5. > ![image.png](https://cdn.nlark.com/yuque/0/2022/png/1609516/1648626052767-67e84c2d-bfaf-495f-bf29-f9ac78542896.png#clientId=u1b7a1699-f2b0-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=170&id=ub6287b58&margin=%5Bobject%20Object%5D&name=image.png&originHeight=212&originWidth=719&originalType=binary&ratio=1&rotation=0&showTitle=false&size=52705&status=done&style=none&taskId=u07c6c66e-c110-4a15-9d3b-e27e25d56c2&title=&width=575.2)
  6. <a name="GSAwP"></a>
  7. #### 步骤1: 第1页查询不用`scroll after`
  8. ```java
  9. {
  10. "size": 10,
  11. "sort": [
  12. {
  13. "timestamp_millis": {
  14. "order": "desc"
  15. }
  16. }
  17. ]
  18. }

查询结果:
image.png

步骤2:之后查询要按顺序一页一页的查询

  1. GET /zipkin2:span-2022-03-29/_search
  2. {
  3. "size": 10,
  4. "search_after": [1648598373627],#指定上次查询页中的最后一条结果的sort字段
  5. "sort": [
  6. {
  7. "timestamp_millis": {
  8. "order": "desc"
  9. }
  10. }
  11. ]
  12. }

注意事项

  • search_after字段一定是唯一的,最好是递增的
  • 由于数据实时插入,会出现2次同一页查询,数据不一致情况

    总结

  • scrollsearch_after都可以解决深度分页的问题

  • scroll基于快照,es为我们维护游标。
  • search_after依据唯一字段, 程序员自己控制
  • 两者均无法完成随机页的读取