• 解决 Elastic Search 的深分页问题


    Elastic Search 为了避免深分页,不允许使用分页(from + size)查询 10000 条以后的数据,因此如果要查询第 10000 条以后的数据,要使用 Elastic Search 提供的 scroll 游标 来查询

    1. 为什么不能使用 from + size 进行深分页查询?

    之所以 Elastic Search 不支持使用 from + size 来查询 10000 条以后的数据,是因为假设取的页数较大时(深分页),像是请求第 20 页,Elastic Search 不得不取出所有分片上的第 1 页到第 20 页的所有文档,并做排序,最终再取出 from 后的 size 条结果作爲最终的返回值

    假设你有 16 个分片,则需要在 coordinate node 彙总到 shards * (from + size) 条记录,即需要 16 * (20 + 10) 记录后做一次全局排序,而当索引非常非常大(千万或亿)时,是无法使用 from + size 做深分页的,分页越深则越容易 Out Of Memory,即使你运气很好没有发生 Out Of Memory,也会非常消耗 CPU 和内存资源

    为了保护机器,Elastic Search 使用 index.max_result_window:10000 这个设定作爲保护措施 ,即默认 from + size 不能超过 10000,虽然这个参数可以动态修改,也可以在配置文件配置,但是最好不要这么做,应该改用 Elastic Search 提供的 scroll 方法来取得数据

    2. scroll 游标原理

    可以把 scroll 理解爲关係型数据库里的 cursor,因此,scroll 并不适合用来做实时搜索,而更适用于后台批处理任务,比如群发,使用 scroll 可以增加性能的原因,是因为如果做深分页,每次搜索都必须重新排序,非常浪费,而使用 scroll 就是一次把要用的数据都排完了,分批取出,因此比使用 from + size 还好

    scroll 具体分爲初始化和遍历两步

    • 初始化时将所有符合搜索条件的搜索结果缓存起来,可以想象成快照
    • 在遍历时,从这个快照里取数据

    也就是说,在初始化后对索引插入、删除、更新数据都不会影响遍历结果

     

    3. 具体实例

    初始化 - 请求

    GET my_index/_search?scroll=1m
    {
        "query":{
            "range":{
                "createTime": {
                    "gte": 1522229999999
                }
            }
        },
        "size": 1000
    }
    • 注意要在URL中的search后加上 scroll=1m,不能写在 request body 中,其中 1m 表示这个游标要保持开启 1 分钟
    • 可以指定 size 大小,就是每次回传几笔数据,当回传到没有数据时,仍会返回 200 成功,只是 hits 裡的 hits 会是空 list
    • 在初始化时除了回传 _scroll_id,也会回传前 100 笔(假设 size = 100)的数据
    • request body 和一般搜索一样,因此可以说在初始化的过程中,除了加上 scroll 设置游标开启时间之外,其他的都跟一般的搜寻没有两样(要设置查询条件,也会回传前 size 笔的数据)

    初始化 - 返回结果

    {
        "_scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAfv5-FjNOamF0Mk1aUUhpUnU5ZWNMaHJocWcAAAAAAH7-gBYzTmphdDJNWlFIaVJ1OWVjTGhyaHFnAAAAAAB-_n8WM05qYXQyTVpRSGlSdTllY0xocmhxZwAAAAAAdsJxFmVkZTBJalJWUmp5UmI3V0FYc2lQbVEAAAAAAHbCcBZlZGUwSWpSVlJqeVJiN1dBWHNpUG1R",
        "took": 2,
        "timed_out": false,
        "_shards": {
            "total": 5,
            "successful": 5,
            "skipped": 0,
            "failed": 0
        },
        "hits": {
            "total": 84,
            "max_score": 1,
            "hits": [
                {
                    "_index": "video1522821719",
                    "_type": "doc",
                    "_id": "84056",
                    "_score": 1,
                    "_source": {
                        "title": "三个院子",
                        "createTime": 1522239744000
                    }
                }
                ....99 data
            ]
        }
    }

    遍历数据 - 请求

    使用初始化返回的 _scroll_id 来进行请求,每一次请求都会继续返回初始化中未读完数据,并且会返回一个 _scroll_id,这个 _scroll_id 可能会改变,因此每一次请求应该带上上一次请求返回的 _scroll_id,且每次发送 scroll 请求时,都要在请求参数带上 scroll=1m,重新刷新这个 scroll 的开启时间,以防不小心超时导致数据取得不完整

    另外要注意一个小细节,返回的结果中的字段返回的是 _scroll_id,但是放在请求裡的字段则是 scroll_id,两者拼写上有不同

    GET _search/scroll?scroll=1m
    {
        "scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAdsMqFmVkZTBJalJWUmp5UmI3V0FYc2lQbVEAAAAAAHbDKRZlZGUwSWpSVlJqeVJiN1dBWHNpUG1RAAAAAABpX2sWclBEekhiRVpSRktHWXFudnVaQ3dIQQAAAAAAaV9qFnJQRHpIYkVaUkZLR1lxbnZ1WkN3SEEAAAAAAGlfaRZyUER6SGJFWlJGS0dZcW52dVpDd0hB"
    }

    遍历数据 - 返回结果

    如果没有数据了,就会回传空的 hits,可以用这个判断是否遍历完成了数据

    {
        "_scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAdsMqFmVkZTBJalJWUmp5UmI3V0FYc2lQbVEAAAAAAHbDKRZlZGUwSWpSVlJqeVJiN1dBWHNpUG1RAAAAAABpX2sWclBEekhiRVpSRktHWXFudnVaQ3dIQQAAAAAAaV9qFnJQRHpIYkVaUkZLR1lxbnZ1WkN3SEEAAAAAAGlfaRZyUER6SGJFWlJGS0dZcW52dVpDd0hB",
        "took": 2,
        "timed_out": false,
        "_shards": {
            "total": 5,
            "successful": 5,
            "skipped": 0,
            "failed": 0
        },
        "hits": {
            "total": 84,
            "max_score": null,
            "hits": []
        }
    }

     

    4. 优化 scroll 查询

    在一般场景下,scroll 通常用来取得需要排序过后的大笔数据,但是有时候数据之间的排序性对我们而言是没有关系的,只要所有数据都能取出来就好,这时能够对 scroll 进行优化

    请求初始化

    使用 _doc 去 sort 得出来的结果,这个执行的效率最快,但是数据就不会有排序,适合用在只想取得所有数据的场景

    GET my_index/_search?scroll=1m
    {
        "query": {
            "match_all" : {}
        },
        "sort": ["_doc"]
    }

    清除 scroll

    虽然我们在设置开启 scroll 时,设置了一个 scroll 的存活时间,但是如果能够在使用完顺手关闭,可以提早释放资源,降低 ES 的负担

    DELETE _search/scroll
    {
        "scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAdsMqFmVkZTBJalJWUmp5UmI3V0FYc2lQbVEAAAAAAHbDKRZlZGUwSWpSVlJqeVJiN1dBWHNpUG1RAAAAAABpX2sWclBEekhiRVpSRktHWXFudnVaQ3dIQQAAAAAAaV9qFnJQRHpIYkVaUkZLR1lxbnZ1WkN3SEEAAAAAAGlfaRZyUER6SGJFWlJGS0dZcW52dVpDd0hB"
    }

     

    5. scroll查询脚本

    为了方便大家使用 scroll 进行深分页查询,我这边写了个bash脚本,提供给大家使用

    注意,因为 Elastic Search 改版的关系,本脚本仅适用于 Elastic Search 7.x 以上

    因脚本需要,记得提前安装 jq 套件

    • mac 环境使用 brew install jq 安装
    • centos 环境使用 sudo yum install -y jq 安装
    • ubuntu 环境使用 sudo apt install jq 安装

    使用方法

    ./es_scroll

    脚本 es_scroll.sh 内容如下

    #!/bin/bash
    
    # 本脚本仅适用于 Elastic Search 7.x 以上
    
    # 存放文件路径
    FILE="/home/temp"
    
    if [ -f $FILE ]; then
       rm $FILE
    fi
    touch "$FILE"
    
    # Elastic Search 访问地址
    HOST=""
    
    # Elastic Search index
    INDEX=""
    
    function scroll() {
        # 可以根据业务逻辑自行修改 query dsl
        search_dsl='
            {
                "query": {
                    "match_all": {}
                },
                "size": 100
            }
            '
    
        json=$(curl -s -XGET "$HOST/$INDEX/_search?scroll=1m" -H "Content-Type: application/json" -d "$search_dsl")
        scroll_id=$(echo  $json | jq -c -r "._scroll_id")
        hits=$(echo  $json | jq -c ".hits.hits")
    
        length=$(echo  $hits | jq length)
    
        for i in $(seq $length)
        do
            index=$i-1
            hit=$(echo  $hits | jq -c ".[$index]._source")
            echo  $hit >> $FILE
        done
    
        scroll_dsl='{"scroll_id": "'"$scroll_id"'"}'
    
        while true
        do
            hits=$(curl -s -XGET "$HOST/_search/scroll?scroll=1m" -H "Content-Type: application/json" -d "$scroll_dsl" | jq -c ".hits.hits")
    
            if [ "$hits" == "" ];
            then
                echo  "exit"
                exit
            fi
    
            length=$(echo  $hits | jq length)
    
            for i in $(seq $length)
            do
                index=$i-1
                hit=$(echo  $hits | jq -c ".[$index]._source")
                echo  $hit >> $FILE
            done
        done
    }
    
    scroll

    原文出自https://zhuanlan.zhihu.com/p/109068603 

  • 相关阅读:
    物理层和数据链路层
    Python :Dive Into Python 中文版 读后感_第五章__类与对象
    关于路由器的2种接法
    关于设计模式的一些理解和感悟
    C# 字符串加密
    Sql server 查询锁表和接锁表
    关于中文参数编码的一些文章推荐
    写与悟
    http 返回的状态码以及含义
    js 操作<input type="hidden" runnat="server" id="month">服务器控件
  • 原文地址:https://www.cnblogs.com/cangqinglang/p/15633057.html
Copyright © 2020-2023  润新知