ElasticSearch:剖析query_and_fetch和query_then_fetch的区别

在ElasticSearch实际使用中遇到了一个问题,就是在数据量很大的情况下做聚合查询(aggregation)会导致内存溢出。当时看了文档,猜测修改search_type能避免内存溢出。实际测试发现,在数据量相同的情况下,search_type设置为query_and_fetch的聚合查询不会导致内存溢出,而默认的query_then_fetch则会内存溢出。本文就从源码层面分析这两种searc
摘要由CSDN通过智能技术生成

在ElasticSearch实际使用中遇到了一个问题,就是在数据量很大的情况下做聚合查询(aggregation)会导致内存溢出。当时看了文档,猜测修改search_type能避免内存溢出。实际测试发现,在数据量相同的情况下,search_type设置为query_and_fetch的聚合查询不会导致内存溢出,而默认的query_then_fetch则会内存溢出。本文就从源码层面分析这两种search_type的区别。

搜索请求处理过程

当一个搜索请求发送到节点之后,节点首先判断出这是一个搜索请求,然后将这个请求传递给TransportSearchAction。这个类负责处理所有的搜索请求。

TransportSearchAction负责根据搜索请求中的search_type将本次的搜索请求传递给对应的类。核心代码如下:

if (searchRequest.searchType() == DFS_QUERY_THEN_FETCH) {
    dfsQueryThenFetchAction.execute(searchRequest, listener);
} else if (searchRequest.searchType() == SearchType.QUERY_THEN_FETCH) {
    queryThenFetchAction.execute(searchRequest, listener);
} else if (searchRequest.searchType() == SearchType.DFS_QUERY_AND_FETCH) {
    dfsQueryAndFetchAction.execute(searchRequest, listener);
} else if (searchRequest.searchType() == SearchType.QUERY_AND_FETCH) {
    queryAndFetchAction.execute(searchRequest, listener);
} else if (searchRequest.searchType() == SearchType.SCAN) {
    scanAction.execute(searchRequest, listener);
} else if (searchRequest.searchType() == SearchType.COUNT) {
    countAction.execute(searchRequest, listener);
}

query_and_fetch执行过程

query_and_fetch搜索对应的类为TransportSearchQueryAndFetchAction。它在执行的时候会启动一个异步任务,对应的代码如下:

@Override
protected void doExecute(SearchRequest searchRequest, ActionListener<SearchResponse> listener) {
    new AsyncAction(searchRequest, listener).start();
}

AsyncAction的入口方法是start,定义在它的基类BaseAsyncAction中。

BaseAsyncAction中的start方法首先确定有哪些分片需要处理,然后给每个需要处理的分片都启动一个异步的搜索任务,对应的关键代码如下:

for (final ShardIterator shardIt : shardsIts) {
    ...
    performFirstPhase(shardIndex, shardIt, shard);
    ...
}

performFirstPhase的作用就是生成一个内部的分片搜索请求,这种请求只针对一个分片,然后调用了一个子类的方法sendExecuteFirstPhase,让子类选择的处理方式。对应的代码如下:

sendExecuteFirstPhase(node, firstRequest, new SearchServiceListener<FirstResult>() {
    @Override
    public void onResult(FirstResult result) {
        onFirstPhaseResult(shardIndex, shard, result, shardIt);
    }
    ...
});

onFirstPhaseResult主要作用是调用子类的moveToSecondPhase。这个方法在executeFetchPhase之后才执行的,因此在其后面再介绍。

sendExecuteFirstPhase方法是抽象的。在搜索模式为query_and_fetch时,对分片请求的处理方式是调用sendExecuteFetch。子类对它的实现代码如下:

@Override
protected void sendExecuteFirstPhase(DiscoveryNode node, ShardSearchRequest request, SearchServiceListener<QueryFetchSearchResult> listener) {
    searchService.sendExecuteFetch(node, request, listener);
}

sendExecuteFetch定义在SearchServiceTransportAction中,它会将分片搜索请求转发到对应的节点上。当然,如果对应的节点是自己这个节点,就不转发了,直接执行executeFetchPhase

executeFetchPhase的执行过程

executeFetchPhase中,首先初始化一个SearchContext,然后调用queryPhase,然后调用fetchPhase,最后清理SearchContext。对应的关键代码如下:

public QueryFetchSearchResult executeFetchPhase(ShardSearchRequest request) throws ElasticsearchException {
    // 初始化SearchContext
    final SearchContext context = createAndPutContext(request);
    ...

    try {
        // 执行queryPhase
        ...
        queryPhase.execute(context);
        ...

        // 执行fetchPhase
        ...
        fetchPhase.execute(context);
        ...

        if (context.scroll() == null) {
           freeContext(context.id());
       }

       ...

        // 返回结果
        return new QueryFetchSearchResult(context.queryResult(), context.fetchResult());
    } catch (Throwable e) {
      
  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值