es滚动查询

Trying to create too many scroll contexts. Must be less than or equal to: [500]. This limit can be set by changing the [search.max_open_scroll_context] setti。
此报错为在查询es时,scrool超过es默认限制,通常可以通过直接执行下方👇设置临时合永久的数量。

put   /_cluster/settings
{
    "persistent": {
        "search.max_open_scroll_context": 5000
    },
    "transient": {
        "search.max_open_scroll_context": 5000
    }
}

一scrool作用

scroll 在 Elasticsearch 中用于处理大规模数据的分页查询。它能够在不重新计算每个分页的情况下,连续读取大量数据。传统的分页查询(使用 from 和 size 参数)在处理大数据集时性能较差,因为每次请求都需要重新排序和计算分页。scroll 通过在第一次查询时创建一个快照,并为后续请求维持上下文,从而提高了查询效率。
scroll 的主要作用:

  1. 处理大规模数据:适用于需要处理和检索大量数据的场景,例如数据迁移、全量索引重建、分析处理等。
  2. 保持快照:在第一次查询时创建一个数据快照,确保在整个滚动查询过程中数据的一致性,不会因为数据的更新而影响查询结果。
  3. 性能优化:避免了传统分页查询中的排序和分页计算开销,提高了性能。

二滚动停止

比如一共由1000条数据,size设置为1,这个查询语句中符合条件的有20条,它一共会查询1000次吗?
如果你的查询总共只有20条数据符合条件,而你设置的每次滚动查询的size为1,那么实际上不会进行1000次滚动查询。滚动查询会在没有更多符合条件的数据时终止。具体来说:

  1. 初始查询:第一次执行初始查询,返回第一条符合条件的数据,并生成一个scrollId。
  2. 滚动查询循环:
    • 每次滚动查询会返回下一条符合条件的数据。
    • 一旦返回的数据不再符合条件(即数据集为空),滚动查询就会停止。

具体流程:

  • 初始查询:查询到第一条符合条件的数据,并返回scrollId。
  • 滚动查询:使用scrollId进行滚动查询,获取下一条符合条件的数据,更新scrollId。
  • 终止条件:如果某次滚动查询返回的数据为空(即没有更多符合条件的数据),循环终止。

三清理

在使用 Elasticsearch 的滚动查询 (scroll search) 时,清理 scroll 的时候只需要提供最后一次的 scrollId 就可以。滚动查询是通过一系列的 scrollId 来维护会话状态的,但在清理的时候,只需要使用最新的 scrollId,这样可以释放所有相关资源。

以下是清理 scroll 的步骤:

  1. 开始滚动查询: 发送一个初始查询请求,并设置 scroll 参数(例如1分钟)。Elasticsearch 会返回一个 scrollId 和查询结果的第一批数据。
  2. 滚动查询: 使用返回的 scrollId 进行后续请求,每次请求都会返回新的 scrollId 和下一批数据。
  3. 清理 scroll: 当不再需要进行滚动查询时,可以使用最后一次返回的 scrollId 调用 _search/scroll API 并设置 scroll_id 来清理滚动会话。

只提供最后一次的 scrollId,Elasticsearch 会清理整个滚动会话的所有资源。这样做不仅简化了清理过程,也确保资源被正确释放

如果在滚动查询的循环处理中加了会有什么后果

在滚动查询的循环处理中,如果每次都重新设置 this.scroll = new Scroll(TimeValue.timeValueMinutes(1L));,会导致一些潜在问题,主要包括:

  1. 资源泄漏: 每次循环都创建一个新的 Scroll 对象可能导致多余的 scroll context 在服务器端被创建,而这些 context 可能不会被及时清理。这样会占用不必要的系统资源,可能导致 Elasticsearch 的性能下降,甚至耗尽资源。

  2. 无效的 Scroll Id: 每次重新创建一个新的 Scroll 对象并不会自动关联到之前的滚动查询上下文。这意味着你实际上没有正确地维护滚动会话,可能导致滚动查询失败或无法获取预期的数据。

  3. 潜在的性能问题: 多次创建不必要的 Scroll 对象会导致额外的开销,影响系统的整体性能。

四代码

滚动查询流程

  1. 设置滚动时间窗口:在初始查询时设置 scroll 对象,定义滚动上下文的存活时间为 5 分钟。
this.scroll = new Scroll(TimeValue.timeValueMinutes(5L));
  1. 初始查询:执行初始查询并获取第一个批次的数据。
SearchResponse response = this.execute();
  1. 处理初始查询结果:调用 scrollFunction.getResult(response) 方法处理查询结果。
if (Objects.nonNull(response)) {
    scrollFunction.getResult(response);
    this.scrollId = response.getScrollId();
  1. 滚动查询循环:在循环中使用 scrollId 执行滚动查询,获取下一批次的数据并处理。
while (searchResponse.getHits() != null && searchResponse.getHits().getHits().length > 0 && StringUtils.isNoneBlank(this.scrollId)) {
                SearchScrollRequest scrollRequest = new SearchScrollRequest(this.scrollId);
                //也会将时间重置为最初设置的时间
                scrollRequest.scroll(this.scroll); // 设置滚动上下文
                searchResponse = client.scroll(scrollRequest, RequestOptions.DEFAULT); // 执行滚动查询
                this.scrollId = searchResponse.getScrollId(); // 更新 scrollId
                if (searchResponse.getHits() != null && searchResponse.getHits().getHits().length > 0) {
                    scrollFunction.getResult(searchResponse); // 处理查询结果
                }
}
  1. 清理查询上下文:在所有数据处理完成后,清理滚动上下文以释放资源。
private void clearScrollContext() {
    if (this.scrollId != null && !"".equals(this.scrollId)) {
        ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
        clearScrollRequest.addScrollId(this.scrollId);

        try {
            if (this.client != null) {
                this.client.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
            } else {
                EsClient.getInstance().getClient().clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
            }
        } catch (IOException e) {
            logger.error("清除ES-ScrollId失败", e);
        }
    }
}

以下是完整的代码

import org.apache.http.HttpHost;
import org.elasticsearch.action.search.ClearScrollRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchScrollRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.search.Scroll;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Objects;

public class ElasticsearchScroll {

    private static final Logger logger = LoggerFactory.getLogger(ElasticsearchScroll.class);
    private RestHighLevelClient client;
    private Scroll scroll;
    private String scrollId;

    public ElasticsearchScroll(RestHighLevelClient client) {
        this.client = client;
    }

    public void executeScrollQuery(String indexName, SearchSourceBuilder searchSourceBuilder, ScrollFunction<SearchResponse> scrollFunction) {
    	//首次执行
        this.scroll = new Scroll(TimeValue.timeValueMinutes(1L)); // 设置滚动时间窗口
        SearchRequest searchRequest = new SearchRequest(indexName);
        searchRequest.scroll(scroll); // 设置滚动上下文
        searchRequest.source(searchSourceBuilder); // 设置查询条件

        try {
            // 执行初始查询
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
            processScrollResponse(searchResponse, scrollFunction);
        } catch (IOException e) {
            logger.error("Error executing initial search request", e);
        } finally {
            // 确保在最后清除滚动上下文
            clearScrollContext();
        }
    }

    private void processScrollResponse(SearchResponse searchResponse, ScrollFunction<SearchResponse> scrollFunction) throws IOException {
        if (Objects.nonNull(searchResponse)) {
            scrollFunction.getResult(searchResponse); // 处理查询结果
            this.scrollId = searchResponse.getScrollId(); // 获取 scrollId

            // 当查询结果不为空时,继续滚动查询
            while (searchResponse.getHits() != null && searchResponse.getHits().getHits().length > 0 && StringUtils.isNoneBlank(this.scrollId)) {
                SearchScrollRequest scrollRequest = new SearchScrollRequest(this.scrollId);
                //也会将时间重置为最初设置的时间
                scrollRequest.scroll(this.scroll); // 设置滚动上下文
                searchResponse = client.scroll(scrollRequest, RequestOptions.DEFAULT); // 执行滚动查询
                this.scrollId = searchResponse.getScrollId(); // 更新 scrollId
                if (searchResponse.getHits() != null && searchResponse.getHits().getHits().length > 0) {
                    scrollFunction.getResult(searchResponse); // 处理查询结果
                }
            }
        }
    }

private void clearScrollContext() {
    if (this.scrollId != null && !"".equals(this.scrollId)) {
        ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
        clearScrollRequest.addScrollId(this.scrollId);

        try {
            if (this.client != null) {
                this.client.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
            } else {
                EsClient.getInstance().getClient().clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
            }
        } catch (IOException e) {
            logger.error("清除ES-ScrollId失败", e);
        }
    }
}

    @FunctionalInterface
    public interface ScrollFunction<T> {
        void getResult(T response);
    }

    public static void main(String[] args) {
        RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(new HttpHost("localhost", 9200, "http")));

        try {
            ElasticsearchScroll scrollExample = new ElasticsearchScroll(client);

            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            searchSourceBuilder.query(/* your query here */);
            searchSourceBuilder.size(2000); // 设置每次查询的大小

            scrollExample.executeScrollQuery("your_index", searchSourceBuilder, searchResponse -> {
                // 处理查询结果
                System.out.println("Processing batch...");
            });
        } finally {
            try {
                client.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

如果先进行创建一个对象EsOperater build = esOperateBuilder.indexes(indexName).build();
然后在执行滚动查询executeScrollQuery,那么可能导致线程不一致,可以这么改以下:

while (searchResponse.getHits() != null && searchResponse.getHits().getHits().length > 0 && StringUtils.isNoneBlank(this.scrollId)) {
                SearchScrollRequest scrollRequest = new SearchScrollRequest(this.scrollId);
                //也会将时间重置为最初设置的时间
                scrollRequest.scroll(this.scroll); // 设置滚动上下文
                searchResponse = client.scroll(scrollRequest, RequestOptions.DEFAULT); // 执行滚动查询
                //先进行清理为null
				this.scroll = null;
           	 	this.scrollId = null;
           	 	
           	 	//重新设置
				// 设定滚动时间间隔
                this.scroll = new Scroll(TimeValue.timeValueMinutes(10L));
                this.scrollId = searchResponse.getScrollId(); // 更新 scrollId
                if (searchResponse.getHits() != null && searchResponse.getHits().getHits().length > 0) {
                    scrollFunction.getResult(searchResponse); // 处理查询结果
                }
            }

但是这样会导致一个问题
在循环中每次都重新创建一个新的 Scroll 对象可能会导致产生很多的 scroll 上下文,这可能会超过 Elasticsearch 的默认限制,并且清理时只清理最后一个 scrollId 会导致无法清理所有的 scroll 上下文。

  • 25
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值