在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) {