elasticsearch源码分析-08Serch查询流程

elasticsearch源码解析笔记 1.2.3 集群状态 集群状态是全局信息,包括内容路由和配置信息,内容路由描述了哪个分片位于哪个节点 主节点负责维护集群状态,从数据节点接收更新,将这些更新广播到集群的其他节点,让每个节点上的集群状态保持最新。ES2.0之后,更新的集群状态是增量的并且是压缩过的。 1.2.4 集群扩容 当扩容集群,添加节点时,分片会均匀地分配到各个节点 分片分配过程中除了让节点间分片分配均匀,还要保证主副分片不在同一节点,避免单个节点故障引起数据丢失 疑问:副分片可以分配在同一个节点吗??????? 当主节点出现异常,集群 阅读详情

Serch查询流程

查询请求Rest路由注册也是在actionModule中

//查询操作
registerHandler.accept(new RestSearchAction());

@Override
public List<Route> routes() {
    return unmodifiableList(asList(
        new Route(GET, "/_search"),
        new Route(POST, "/_search"),
        new Route(GET, "/{index}/_search"),
        new Route(POST, "/{index}/_search"),
        // Deprecated typed endpoints.
        new Route(GET, "/{index}/{type}/_search"),
        new Route(POST, "/{index}/{type}/_search")));
}

和Get查询一样,请求会经过转发dispatchRequest,查询路由对应的handler处理类,然后执行处理

@Override
    public void dispatchRequest(RestRequest request, RestChannel channel, ThreadContext threadContext) {
        //小图标请求处理
        if (request.rawPath().equals("/favicon.ico")) {
            handleFavicon(request.method(), request.uri(), channel);
            return;
        }
        try {
            //处理rest请求
            tryAllHandlers(request, channel, threadContext);
        } catch (Exception e) {
            try {
                channel.sendResponse(new BytesRestResponse(channel, e));
            } catch (Exception inner) {
                inner.addSuppressed(e);
                logger.error(() ->
                    new ParameterizedMessage("failed to send failure response for uri [{}]", request.uri()), inner);
            }
        }
    }

调用BaseRestHandler的handleRequest方法

@Override
    public final void handleRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception {
        // prepare the request for execution; has the side effect of touching the request parameters
        //处理请求
        final RestChannelConsumer action = prepareRequest(request, client);

        // validate unconsumed params, but we must exclude params used to format the response
        // use a sorted set so the unconsumed parameters appear in a reliable sorted order
        final SortedSet<String> unconsumedParams =
            request.unconsumedParams().stream().filter(p -> !responseParams().contains(p)).collect(Collectors.toCollection(TreeSet::new));

        // validate the non-response params
        if (!unconsumedParams.isEmpty()) {
            final Set<String> candidateParams = new HashSet<>();
            candidateParams.addAll(request.consumedParams());
            candidateParams.addAll(responseParams());
            throw new IllegalArgumentException(unrecognized(request, unconsumedParams, candidateParams, "parameter"));
        }

        if (request.hasContent() && request.isContentConsumed() == false) {
            throw new IllegalArgumentException("request [" + request.method() + " " + request.path() + "] does not support having a body");
        }

        usageCount.increment();
        // execute the action
        action.accept(channel);
    }

首先构造请求request对象,RestSearchAction对该方法进行了重写

@Override
    public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
        //创建查询请求对象
        SearchRequest searchRequest = new SearchRequest();
        IntConsumer setSize = size -> searchRequest.source().size(size);
        request.withContentOrSourceParamParserOrNull(parser ->
            parseSearchRequest(searchRequest, request, parser, setSize));

        return channel -> {
            RestCancellableNodeClient cancelClient = new RestCancellableNodeClient(client, request.getHttpChannel());
            cancelClient.execute(SearchAction.INSTANCE, searchRequest, new RestStatusToXContentListener<>(channel));
        };
    }

首先根据请求的request解析成查询请求

public static void parseSearchRequest(SearchRequest searchRequest, RestRequest request,
                                          XContentParser requestContentParser,
                                          IntConsumer setSize) throws IOException {
        //查询源包含哪些字段,为空查询所有字段
        if (searchRequest.source() == null) {
            searchRequest.source(new SearchSourceBuilder());
        }
        //查询索引
        searchRequest.indices(Strings.splitStringByCommaToArray(request.param("index")));
        if (requestContentParser != null) {
            searchRequest.source().parseXContent(requestContentParser, true);
        }
        //协调节点的保护,从每个shard获取的数据最大数据量
        final int batchedReduceSize = request.paramAsInt("batched_reduce_size", searchRequest.getBatchedReduceSize());
        searchRequest.setBatchedReduceSize(batchedReduceSize);
        //预过滤分片数量
        if (request.hasParam("pre_filter_shard_size")) {
            searchRequest.setPreFilterShardSize(request.paramAsInt("pre_filter_shard_size", SearchRequest.DEFAULT_PRE_FILTER_SHARD_SIZE));
        }
        //分片并发查询数量
        if (request.hasParam("max_concurrent_shard_requests")) {
            // only set if we have the parameter since we auto adjust the max concurrency on the coordinator
            // based on the number of nodes in the cluster
            final int maxConcurrentShardRequests = request.paramAsInt("max_concurrent_shard_requests",
                searchRequest.getMaxConcurrentShardRequests());
            searchRequest.setMaxConcurrentShardRequests(maxConcurrentShardRequests);
        }
        //允许部分搜索结果
        if (request.hasParam("allow_partial_search_results")) {
            // only set if we have the parameter passed to override the cluster-level default
            searchRequest.allowPartialSearchResults(request.paramAsBoolean("allow_partial_search_results", null));
        }

        // do not allow 'query_and_fetch' or 'dfs_query_and_fetch' search types
        // from the REST layer. these modes are an internal optimization and should
        // not be specified explicitly by the user.
        //搜索类型,默认query_then_fetch
        String searchType = request.param("search_type");
        //下面两种查询已经被废弃
        if ("query_and_fetch".equals(searchType) ||
                "dfs_query_and_fetch".equals(searchType)) {
            throw new IllegalArgumentException("Unsupported search type [" + searchType + "]");
        } else {
            searchRequest.searchType(searchType);
        }
        parseSearchSource(searchRequest.source(), request, setSize);
        //查询缓存
        searchRequest.requestCache(request.paramAsBoolean("request_cache", searchRequest.requestCache()));
        //滚动查询
        String scroll = request.param("scroll");
        if (scroll != null) {
            searchRequest.scroll(new Scroll(parseTimeValue(scroll, null, "scroll")));
        }
        //type字段已废弃
        if (request.hasParam("type")) {
            deprecationLogger.deprecatedAndMaybeLog("search_with_types", TYPES_DEPRECATION_MESSAGE);
            searchRequest.types(Strings.splitStringByCommaToArray(request.param("type")));
        }
        searchRequest.routing(request.param("routing"));
        searchRequest.preference(request.param("preference"));
        searchRequest.indicesOptions(IndicesOptions.fromRequest(request, searchRequest.indicesOptions()));
        searchRequest.setCcsMinimizeRoundtrips(request.paramAsBoolean("ccs_minimize_roundtrips", searchRequest.isCcsMinimizeRoundtrips()));

        checkRestTotalHits(request, searchRequest);
    }

private static void parseSearchSource(final SearchSourceBuilder searchSourceBuilder, RestRequest request, IntConsumer setSize) {
        QueryBuilder queryBuilder = RestActions.urlParamsToQueryBuilder(request);
        if (queryBuilder != null) {
            searchSourceBuilder.query(queryBuilder);
        }
        //分页查询
        int from = request.paramAsInt("from", -1);
        if (from != -1) {
            searchSourceBuilder.from(from);
        }
        int size = request.paramAsInt("size", -1);
        if (size != -1) {
            setSize.accept(size);
        }
        //特定文档如何与查询匹配的详细信息和解释
        if (request.hasParam("explain")) {
            searchSourceBuilder.explain(request.paramAsBoolean("explain", null));
        }
        //带版本号查询
        if (request.hasParam("version")) {
            searchSourceBuilder.version(request.paramAsBoolean("version", null));
        }
        //是否返回seqNo和primaryTerm
        if (request.hasParam("seq_no_primary_term")) {
            searchSourceBuilder.seqNoAndPrimaryTerm(request.paramAsBoolean("seq_no_primary_term", null));
        }
        //超时时间
        if (request.hasParam("timeout")) {
            searchSourceBuilder.timeout(request.paramAsTime("timeout", null));
        }
        //收集后终止搜索
        if (request.hasParam("terminate_after")) {
            int terminateAfter = request.paramAsInt("terminate_after",
                    SearchContext.DEFAULT_TERMINATE_AFTER);
            if (terminateAfter < 0) {
                throw new IllegalArgumentException("terminateAfter must be > 0");
            } else if (terminateAfter > 0) {
                searchSourceBuilder.terminateAfter(terminateAfter);
            }
        }
        //指示应如何获取存储的字段
        StoredFieldsContext storedFieldsContext =
            StoredFieldsContext.fromRestRequest(SearchSourceBuilder.STORED_FIELDS_FIELD.getPreferredName(), request);
        if (storedFieldsContext != null) {
            searchSourceBuilder.storedFields(storedFieldsContext);
        }
        //从doc_value字段查询
        String sDocValueFields = request.param("docvalue_fields");
        if (sDocValueFields != null) {
            if (Strings.hasText(sDocValueFields)) {
                String[] sFields = Strings.splitStringByCommaToArray(sDocValueFields);
                for (String field : sFields) {
                    searchSourceBuilder.docValueField(field, null);
                }
            }
        }
        //从_source获取结果
        FetchSourceContext fetchSourceContext = FetchSourceContext.parseFromRestRequest(request);
        if (fetchSourceContext != null) {
            searchSourceBuilder.fetchSource(fetchSourceContext);
        }
        //在排序时应用,并控制是否也会跟踪分数
        if (request.hasParam("track_scores")) {
            searchSourceBuilder.trackScores(request.paramAsBoolean("track_scores", false));
        }

        //如果值未设置,则返回应跟踪的总命中数或 null
        if (request.hasParam("track_total_hits")) {
            if (Booleans.isBoolean(request.param("track_total_hits"))) {
                searchSourceBuilder.trackTotalHits(
                    request.paramAsBoolean("track_total_hits", true)
                );
            } else {
                searchSourceBuilder.trackTotalHitsUpTo(
                    request.paramAsInt("track_total_hits", SearchContext.DEFAULT_TRACK_TOTAL_HITS_UP_TO)
                );
            }
        }
        //排序字段
        String sSorts = request.param("sort");
        if (sSorts != null) {
            String[] sorts = Strings.splitStringByCommaToArray(sSorts);
            for (String sort : sorts) {
                int delimiter = sort.lastIndexOf(":");
                if (delimiter != -1) {
                    String sortField = sort.substring(0, delimiter);
                    String reverse = sort.substring(delimiter + 1);
                    if ("asc".equals(reverse)) {
                        searchSourceBuilder.sort(sortField, SortOrder.ASC);
                    } else if ("desc".equals(reverse)) {
                        searchSourceBuilder.sort(sortField, SortOrder.DESC);
                    }
                } else {
                    searchSourceBuilder.sort(sort);
                }
            }
        }
        //此请求将汇总的统计信息组
        String sStats = request.param("stats");
        if (sStats != null) {
            searchSourceBuilder.stats(Arrays.asList(Strings.splitStringByCommaToArray(sStats)));
        }
        //推荐词
        String suggestField = request.param("suggest_field");
        if (suggestField != null) {
            String suggestText = request.param("suggest_text", request.param("q"));
            int suggestSize = request.paramAsInt("suggest_size", 5);
            String suggestMode = request.param("suggest_mode");
            searchSourceBuilder.suggest(new SuggestBuilder().addSuggestion(suggestField,
                    termSuggestion(suggestField)
                        .text(suggestText).size(suggestSize)
                        .suggestMode(SuggestMode.resolve(suggestMode))));
        }
    }

创建rest client开始发送请求

return channel -> {
            RestCancellableNodeClient cancelClient = new RestCancellableNodeClient(client, request.getHttpChannel());
            cancelClient.execute(SearchAction.INSTANCE, searchRequest, new RestStatusToXContentListener<>(channel));
        };

执行execute方法

public <    Request extends ActionRequest,
                Response extends ActionResponse
            > Task executeLocally(ActionType<Response> action, Request request, ActionListener<Response> listener) {
        //获取transportAction开始执行
        return transportAction(action).execute(request, listener);
    }

最终调用到TransportSearchAction的doExecute方法
search操作会发送请求到索引下的所有分片,相同分片只会发送到主分片或副本分片

protected void doExecute(Task task, SearchRequest searchRequest, ActionListener<SearchResponse> listener) {
        //服务启动相对时间
        final long relativeStartNanos = System.nanoTime();
        final SearchTimeProvider timeProvider =
            new SearchTimeProvider(searchRequest.getOrCreateAbsoluteStartMillis(), relativeStartNanos, System::nanoTime);
        ActionListener<SearchSourceBuilder> rewriteListener = ActionListener.wrap(source -> {
            if (source != searchRequest.source()) {
                // only set it if it changed - we don't allow null values to be set but it might be already null. this way we catch
                // situations when source is rewritten to null due to a bug
                searchRequest.source(source);
            }
            //对其他集群的索引和当前集群的索引分组,合并本集群shard列表和远程集群的shard
            final ClusterState clusterState = clusterService.state();
            final Map<String, OriginalIndices> remoteClusterIndices = remoteClusterService.groupIndices(searchRequest.indicesOptions(),
                searchRequest.indices(), idx -> indexNameExpressionResolver.hasIndexOrAlias(idx, clusterState));
            OriginalIndices localIndices = remoteClusterIndices.remove(RemoteClusterAware.LOCAL_CLUSTER_GROUP_KEY);
            //执行本集群搜索
            if (remoteClusterIndices.isEmpty()) {
                executeLocalSearch(task, timeProvider, searchRequest, localIndices, clusterState, listener);
            } else {
                //最小化查询次数
                if (shouldMinimizeRoundtrips(searchRequest)) {
                    ccsRemoteReduce(searchRequest, localIndices, remoteClusterIndices, timeProvider,
                            searchService.aggReduceContextBuilder(searchRequest),
                            remoteClusterService, threadPool, listener,
                            (r, l) -> executeLocalSearch(task, timeProvider, r, localIndices, clusterState, l));
                } else {
                    AtomicInteger skippedClusters = new AtomicInteger(0);
                    //收集所有需要查询的分片
                    collectSearchShards(searchRequest.indicesOptions(), searchRequest.preference(), searchRequest.routing(),
                        skippedClusters, remoteClusterIndices, remoteClusterService, threadPool,
                        ActionListener.wrap(
                            //查询分片返回的结果
                            searchShardsResponses -> {
                                List<SearchShardIterator> remoteShardIterators = new ArrayList<>();
                                Map<String, AliasFilter> remoteAliasFilters = new HashMap<>();
                                BiFunction<String, String, DiscoveryNode> clusterNodeLookup = processRemoteShards(
                                    searchShardsResponses, remoteClusterIndices, remoteShardIterators, remoteAliasFilters);
                                //查询集群数
                                int localClusters = localIndices == null ? 0 : 1;
                                int totalClusters = remoteClusterIndices.size() + localClusters;
                                int successfulClusters = searchShardsResponses.size() + localClusters;
                                //执行查询
                                executeSearch((SearchTask) task, timeProvider, searchRequest, localIndices,
                                    remoteShardIterators, clusterNodeLookup, clusterState, remoteAliasFilters, listener,
                                    new SearchResponse.Clusters(totalClusters, successfulClusters, skippedClusters.get()));
                            },
                            listener::onFailure));
                }
            }
        }, listener::onFailure);
        if (searchRequest.source() == null) {
            rewriteListener.onResponse(searchRequest.source());
        } else {
            Rewriteable.rewriteAndFetch(searchRequest.source(), searchService.getRewriteContext(timeProvider::getAbsoluteStartMillis),
                rewriteListener);
        }
    }

这里收集本集群和跨集群及其他集群需要查询的索引,这里我们主要是分析本集群搜索

ES中查询主要包括两个方式:

  • QUERY_THEN_FETCH:默认搜索方式, 先向所有的 shard 发出请求, 各分片只返回文档 id和排
    名相关的信息及文档的打分然后按照各分片返回的文档的分数进行重新排序和排名, 取前
    size 个文档。然后根据文档 id 去相关的 shard 取 document。 数据量是准确的,数据排名不准确
  • DFS_QUERY_THEN_FETCH:在上面的查询方式之前多个一个DFS的过程,也就是在进行查询之前, 先对所有分片发送请求, 把所有分片中的词频和文档频率等打分依据全部汇总到一块, 再执行后面的操作,数据排名准确,但是性能比上面性能要差
private void executeSearch(SearchTask task, SearchTimeProvider timeProvider, SearchRequest searchRequest,
                               OriginalIndices localIndices, List<SearchShardIterator> remoteShardIterators,
                               BiFunction<String, String, DiscoveryNode> remoteConnections, ClusterState clusterState,
                               Map<String, AliasFilter> remoteAliasMap, ActionListener<SearchResponse> listener,
                               SearchResponse.Clusters clusters) {

        clusterState.blocks().globalBlockedRaiseException(ClusterBlockLevel.READ);
        // TODO: I think startTime() should become part of ActionRequest and that should be used both for index name
        // date math expressions and $now in scripts. This way all apis will deal with now in the same way instead
        // of just for the _search api
        final Index[] indices = resolveLocalIndices(localIndices, searchRequest.indicesOptions(), clusterState, timeProvider);
        Map<String, AliasFilter> aliasFilter = buildPerIndexAliasFilter(searchRequest, clusterState, indices, remoteAliasMap);
        Map<String, Set<String>> routingMap = indexNameExpressionResolver.resolveSearchRouting(clusterState, searchRequest.routing(),
            searchRequest.indices());
        routingMap = routingMap == null ? Collections.emptyMap() : Collections.unmodifiableMap(routingMap);
        String[] concreteIndices = new String[indices.length];
        for (int i = 0; i < indices.length; i++) {
            concreteIndices[i] = indices[i].getName();
        }
        //每个节点查询次数
        Map<String, Long> nodeSearchCounts = searchTransportService.getPendingSearchRequests();
        GroupShardsIterator<ShardIterator> localShardsIterator = clusterService.operationRouting().searchShards(clusterState,
                concreteIndices, routingMap, searchRequest.preference(), searchService.getResponseCollectorService(), nodeSearchCounts);
        //合并需要查询的shard分片
        GroupShardsIterator<SearchShardIterator> shardIterators = mergeShardsIterators(localShardsIterator, localIndices,
            searchRequest.getLocalClusterAlias(), remoteShardIterators);
        //检查需要查询分片数量是否超过阈值
        failIfOverShardCountLimit(clusterService, shardIterators.size());

        Map<String, Float> concreteIndexBoosts = resolveIndexBoosts(searchRequest, clusterState);

        // optimize search type for cases where there is only one shard group to search on
        //针对只有一个分片组要搜索的情况优化搜索类型
        if (shardIterators.size() == 1) {
            // if we only have one group, then we always want Q_T_F, no need for DFS, and no need to do THEN since we hit one shard
            searchRequest.searchType(QUERY_THEN_FETCH);
        }
        if (searchRequest.allowPartialSearchResults() == null) {
           // No user preference defined in search request - apply cluster service default
            //允许部分搜索结果返回
            searchRequest.allowPartialSearchResults(searchService.defaultAllowPartialSearchResults());
        }
        if (searchRequest.isSuggestOnly()) {
            // disable request cache if we have only suggest
            // 如果我们只有建议,则禁用请求缓存
            searchRequest.requestCache(false);
            switch (searchRequest.searchType()) {
                case DFS_QUERY_THEN_FETCH:
                    // convert to Q_T_F if we have only suggest
                    searchRequest.searchType(QUERY_THEN_FETCH);
                    break;
            }
        }

        final DiscoveryNodes nodes = clusterState.nodes();
        BiFunction<String, String, Transport.Connection> connectionLookup = buildConnectionLookup(searchRequest.getLocalClusterAlias(),
            nodes::get, remoteConnections, searchTransportService::getConnection);
        //提前过滤分片
        boolean preFilterSearchShards = shouldPreFilterSearchShards(clusterState, searchRequest, indices, shardIterators.size());
        //异步查询
        searchAsyncAction(task, searchRequest, shardIterators, timeProvider, connectionLookup, clusterState,
            Collections.unmodifiableMap(aliasFilter), concreteIndexBoosts, routingMap, listener, preFilterSearchShards, clusters).start();
    }

根据两个搜索方式创建不同的action

AbstractSearchAsyncAction<? extends SearchPhaseResult> searchAsyncAction;
            switch (searchRequest.searchType()) {
                case DFS_QUERY_THEN_FETCH:
                    searchAsyncAction = new SearchDfsQueryThenFetchAsyncAction(logger, searchTransportService, connectionLookup,
                        aliasFilter, concreteIndexBoosts, indexRoutings, searchPhaseController, executor, searchRequest, listener,
                        shardIterators, timeProvider, clusterState, task, clusters);
                    break;
                case QUERY_THEN_FETCH:
                    searchAsyncAction = new SearchQueryThenFetchAsyncAction(logger, searchTransportService, connectionLookup,
                        aliasFilter, concreteIndexBoosts, indexRoutings, searchPhaseController, executor, searchRequest, /*查询后的回调*/listener,
                        shardIterators, timeProvider, clusterState, task, clusters);
                    break;
                default:
                    throw new IllegalStateException("Unknown search type: [" + searchRequest.searchType() + "]");
            }
            return searchAsyncAction;

创建action后开始执行

public final void start() {
    if (getNumShards() == 0) {
        //no search shards to search on, bail with empty response
        //(it happens with search across _all with no indices around and consistent with broadcast operations)
        int trackTotalHitsUpTo = request.source() == null ? SearchContext.DEFAULT_TRACK_TOTAL_HITS_UP_TO :
        request.source().trackTotalHitsUpTo() == null ? SearchContext.DEFAULT_TRACK_TOTAL_HITS_UP_TO :
        request.source().trackTotalHitsUpTo();
        // total hits is null in the response if the tracking of total hits is disabled
        boolean withTotalHits = trackTotalHitsUpTo != SearchContext.TRACK_TOTAL_HITS_DISABLED;
        listener.onResponse(new SearchResponse(InternalSearchResponse.empty(withTotalHits), null, 0, 0, 0, buildTookInMillis(),
                                               ShardSearchFailure.EMPTY_ARRAY, clusters));
        return;
    }
    //执行搜索
    executePhase(this);
}

private void executePhase(SearchPhase phase) {
    try {
        phase.run();
    } catch (Exception e) {
        if (logger.isDebugEnabled()) {
            logger.debug(new ParameterizedMessage("Failed to execute [{}] while moving to [{}] phase", request, phase.getName()), e);
        }
        onPhaseFailure(phase, "", e);
    }
}

遍历所有分片发送请求,并设置回调函数接收分片返回结果

public final void run() {
        for (final SearchShardIterator iterator : toSkipShardsIts) {
            assert iterator.skip();
            skipShard(iterator);
        }
        if (shardsIts.size() > 0) {
            ...
            for (int index = 0; index < shardsIts.size(); index++) {
                //分片路由信息
                final SearchShardIterator shardRoutings = shardsIts.get(index);
                assert shardRoutings.skip() == false;
                //分片执行
                performPhaseOnShard(index, shardRoutings, shardRoutings.nextOrNull());
            }
        }
    }

private void performPhaseOnShard(final int shardIndex, final SearchShardIterator shardIt, final ShardRouting shard) {
        if (shard == null) {
            fork(() -> onShardFailure(shardIndex, null, null, shardIt, new NoShardAvailableActionException(shardIt.shardId())));
        } else {
            //节流并发请求
            final PendingExecutions pendingExecutions = throttleConcurrentRequests ?
                pendingExecutionsPerNode.computeIfAbsent(shard.currentNodeId(), n -> new PendingExecutions(maxConcurrentRequestsPerNode))
                : null;
            Runnable r = () -> {
                final Thread thread = Thread.currentThread();
                try {
                    //执行查询
                    executePhaseOnShard(shardIt, shard,
                        new SearchActionListener<Result>(shardIt.newSearchShardTarget(shard.currentNodeId()), shardIndex) {
                            @Override
                            public void innerOnResponse(Result result) {
                                try {
                                    //分片返回结果
                                    onShardResult(result, shardIt);
                                } finally {
                                    executeNext(pendingExecutions, thread);
                                }
                            }

                            @Override
                            public void onFailure(Exception t) {
                                try {
                                    onShardFailure(shardIndex, shard, shard.currentNodeId(), shardIt, t);
                                } finally {
                                    executeNext(pendingExecutions, thread);
                                }
                            }
                        });
                } catch (final Exception e) {
                    try {                    
                        fork(() -> onShardFailure(shardIndex, shard, shard.currentNodeId(), shardIt, e));
                    } finally {
                        executeNext(pendingExecutions, thread);
                    }
                }
            };
            //是否限流
            if (throttleConcurrentRequests) {
                pendingExecutions.tryRun(r);
            } else {
                r.run();
            }
        }
    }

默认为QUERY_THEN_FETCH方式,调用SearchQueryThenFetchAsyncAction的executePhaseOnShard方法

protected void executePhaseOnShard(final SearchShardIterator shardIt, final ShardRouting shard,
                                       final SearchActionListener<SearchPhaseResult> listener) {
        //封装请求
        ShardSearchRequest request = rewriteShardSearchRequest(super.buildShardSearchRequest(shardIt));
        getSearchTransport().sendExecuteQuery(getConnection(shardIt.getClusterAlias(), shard.currentNodeId()),
            request, getTask(), listener);
    }

获取连接然后发送action为indices:data/read/search[phase/query]的RPC请求

public void sendExecuteQuery(Transport.Connection connection, final ShardSearchRequest request, SearchTask task,
                                 final SearchActionListener<SearchPhaseResult> listener) {
        // we optimize this and expect a QueryFetchSearchResult if we only have a single shard in the search request
        // this used to be the QUERY_AND_FETCH which doesn't exist anymore.
        final boolean fetchDocuments = request.numberOfShards() == 1;
        Writeable.Reader<SearchPhaseResult> reader = fetchDocuments ? QueryFetchSearchResult::new : QuerySearchResult::new;

        final ActionListener handler = responseWrapper.apply(connection, listener);
        //发送查询
        transportService.sendChildRequest(connection, QUERY_ACTION_NAME, request, task,
                new ConnectionCountingHandler<>(handler, reader, clientConnections, connection.getNode().getId()));
    }
  • 数据节点

数据节点对应的RPC注册的处理handler

//注册分片查询处理
        transportService.registerRequestHandler(QUERY_ACTION_NAME, ThreadPool.Names.SAME, ShardSearchRequest::new,
            (request, channel, task) -> {
            //执行查询
                searchService.executeQueryPhase(request, (SearchShardTask) task,
                    /*查询后回调*/
                    new ChannelActionListener<>(channel, QUERY_ACTION_NAME, request));
            });

数据节点接收RPC请求执行query阶段

public void executeQueryPhase(ShardSearchRequest request, SearchShardTask task, ActionListener<SearchPhaseResult> listener) {
        assert request.canReturnNullResponseIfMatchNoDocs() == false || request.numberOfShards() > 1
            : "empty responses require more than one shard";
        //索引服务
        IndexService indexService = indicesService.indexServiceSafe(request.shardId().getIndex());
        //索引分片
        IndexShard shard = indexService.getShard(request.shardId().id());
        rewriteAndFetchShardRequest(shard, request, new ActionListener<ShardSearchRequest>() {
            @Override
            public void onResponse(ShardSearchRequest orig) {
                //如果没有查询到doc可以返回null
                if (orig.canReturnNullResponseIfMatchNoDocs()) {                
                    ShardSearchRequest canMatchRequest = new ShardSearchRequest(orig);
                    try (Engine.Searcher searcher = shard.acquireCanMatchSearcher()) {
                        QueryShardContext context = indexService.newQueryShardContext(canMatchRequest.shardId().id(), searcher,
                            canMatchRequest::nowInMillis, canMatchRequest.getClusterAlias());
                        Rewriteable.rewrite(canMatchRequest.getRewriteable(), context, true);
                    } catch (Exception exc) {
                        listener.onFailure(exc);
                        return;
                    }
                    if (canRewriteToMatchNone(canMatchRequest.source())
                            && canMatchRequest.source().query() instanceof MatchNoneQueryBuilder) {
                        assert canMatchRequest.scroll() == null : "must always create search context for scroll requests";
                        listener.onResponse(QuerySearchResult.nullInstance());
                        return;
                    }
                }
                // fork the execution in the search thread pool
                //在搜索线程池中执行
                runAsync(shard, () -> executeQueryPhase(orig, task), listener);
            }

            @Override
            public void onFailure(Exception exc) {
                listener.onFailure(exc);
            }
        });
    }

执行search操作

private SearchPhaseResult executeQueryPhase(ShardSearchRequest request, SearchShardTask task) throws Exception {
        final SearchContext context = createAndPutContext(request, task);
        context.incRef();
        try {
            final long afterQueryTime;
            try (SearchOperationListenerExecutor executor = new SearchOperationListenerExecutor(context)) {
                contextProcessing(context);
                //执行查询阶段
                loadOrExecuteQueryPhase(request, context);
                if (context.queryResult().hasSearchContext() == false && context.scrollContext() == null) {
                    freeContext(context.id());
                } else {
                    contextProcessedSuccessfully(context);
                }
                afterQueryTime = executor.success();
            }
            //如果只有一个分片,执行fetch阶段
            if (request.numberOfShards() == 1) {
                return executeFetchPhase(context, afterQueryTime);
            }
            return context.queryResult();
        } catch (Exception e) {
            // execution exception can happen while loading the cache, strip it
            if (e instanceof ExecutionException) {
                e = (e.getCause() == null || e.getCause() instanceof Exception) ?
                    (Exception) e.getCause() : new ElasticsearchException(e.getCause());
            }
            logger.trace("Query phase failed", e);
            processFailure(context, e);
            throw e;
        } finally {
            cleanContext(context);
        }
    }

创建查询上下文对象SearchContext跟踪整个search流程,并且查询后的结果也会填充到SearchContext中

private void loadOrExecuteQueryPhase(final ShardSearchRequest request, final SearchContext context) throws Exception {
        final boolean canCache = indicesService.canCache(request, context);
        context.getQueryShardContext().freezeContext();
        if (canCache) {
            indicesService.loadIntoContext(request, context, queryPhase);
        } else {
            //执行查询
            queryPhase.execute(context);
        }
    }

public void execute(SearchContext searchContext) throws QueryPhaseExecutionException {
        if (searchContext.hasOnlySuggest()) {
            suggestPhase.execute(searchContext);
            searchContext.queryResult().topDocs(new TopDocsAndMaxScore(
                    new TopDocs(new TotalHits(0, TotalHits.Relation.EQUAL_TO), Lucene.EMPTY_SCORE_DOCS), Float.NaN),
                new DocValueFormat[0]);
            return;
        }

        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("{}", new SearchContextSourcePrinter(searchContext));
        }

        //尽可能晚地预处理聚合。在 DFS_Q_T_F 的情况下
        // 请求,preProcess 在 DFS 阶段调用,这就是我们预处理它们的原因
        //这里是为了确保它发生在QUERY阶段
        aggregationPhase.preProcess(searchContext);
        //lucene搜索
        boolean rescore = executeInternal(searchContext);

        if (rescore) { // only if we do a regular search
            //重新计算打分
            rescorePhase.execute(searchContext);
        }
        //纠错
        suggestPhase.execute(searchContext);
        //聚合
        aggregationPhase.execute(searchContext);

        if (searchContext.getProfilers() != null) {
            ProfileShardResult shardResults = SearchProfileShardResults
                .buildShardResults(searchContext.getProfilers());
            searchContext.queryResult().profileResults(shardResults);
        }
    }

这里可以看到首先会执行lucene的查询,然后对查询结果进行打分、执行聚合逻辑,然后生成SearchPhaseResult返回

数据节点查询数据后返回协调节点,我们继续回到executePhaseOnShard方法执行回调

//执行查询
executePhaseOnShard(shardIt, shard,
                    new SearchActionListener<Result>(shardIt.newSearchShardTarget(shard.currentNodeId()), shardIndex) {
                        @Override
                        public void innerOnResponse(Result result) {
                            try {
                                //分片返回结果
                                onShardResult(result, shardIt);
                            } finally {
                                executeNext(pendingExecutions, thread);
                            }
                        }

                        @Override
                        public void onFailure(Exception t) {
                            try {
                                onShardFailure(shardIndex, shard, shard.currentNodeId(), shardIt, t);
                            } finally {
                                executeNext(pendingExecutions, thread);
                            }
                        }
                    });

保存分片查询返回的数据到results数组中并进行计数

protected void onShardResult(Result result, SearchShardIterator shardIt) {
        assert result.getShardIndex() != -1 : "shard index is not set";
        assert result.getSearchShardTarget() != null : "search shard target must not be null";
        successfulOps.incrementAndGet();
        results.consumeResult(result);
        hasShardResponse.set(true);
        if (logger.isTraceEnabled()) {
            logger.trace("got first-phase result from {}", result != null ? result.getSearchShardTarget() : null);
        }
       
        AtomicArray<ShardSearchFailure> shardFailures = this.shardFailures.get();
        if (shardFailures != null) {
            shardFailures.set(result.getShardIndex(), null);
        }
      
        successfulShardExecution(shardIt);
    }

判断所有分片查询是否都已经返回,如果都已经返回则执行下一阶段及fetch阶段

private void successfulShardExecution(SearchShardIterator shardsIt) {
        final int remainingOpsOnIterator;
        if (shardsIt.skip()) {
            remainingOpsOnIterator = shardsIt.remaining();
        } else {
            remainingOpsOnIterator = shardsIt.remaining() + 1;
        }
        final int xTotalOps = totalOps.addAndGet(remainingOpsOnIterator);
        //所有分片都已经返回
        if (xTotalOps == expectedTotalOps) {
            onPhaseDone();
        } else if (xTotalOps > expectedTotalOps) {
            throw new AssertionError("unexpected higher total ops [" + xTotalOps + "] compared to expected ["
                + expectedTotalOps + "]");
        }
    }

final void onPhaseDone() {  // as a tribute to @kimchy aka. finishHim()
        executeNextPhase(this, getNextPhase(results, this));
    }

@Override
    protected SearchPhase getNextPhase(final SearchPhaseResults<SearchPhaseResult> results, final SearchPhaseContext context) {
        //获取数据阶段
        return new FetchSearchPhase(results, searchPhaseController, context, clusterState());
    }

FetchSearchPhase(SearchPhaseResults<SearchPhaseResult> resultConsumer,
                     SearchPhaseController searchPhaseController,
                     SearchPhaseContext context,
                     ClusterState clusterState) {
        this(resultConsumer, searchPhaseController, context, clusterState,
            //最后收尾阶段
            (response, scrollId) -> new ExpandSearchPhase(context, response, scrollId));
    }
  • fetch阶段
@Override
    public void run() {
        context.execute(new AbstractRunnable() {
            @Override
            protected void doRun() throws Exception {
                // we do the heavy lifting in this inner run method where we reduce aggs etc. that's why we fork this phase
                // off immediately instead of forking when we send back the response to the user since there we only need
                // to merge together the fetched results which is a linear operation.
                innerRun();
            }

            @Override
            public void onFailure(Exception e) {
                context.onPhaseFailure(FetchSearchPhase.this, "", e);
            }
        });
    }

    private void innerRun() throws IOException {
        //查询分片数量
        final int numShards = context.getNumShards();
        //是否是滚动查询
        final boolean isScrollSearch = context.getRequest().scroll() != null;
        //search阶段返回结果
        final List<SearchPhaseResult> phaseResults = queryResults.asList();
        final String scrollId;
        //如果是滚动查询则生成scrollId
        if (isScrollSearch) {
            final boolean includeContextUUID = clusterState.nodes().getMinNodeVersion().onOrAfter(Version.V_7_7_0);
            scrollId = TransportSearchHelper.buildScrollId(queryResults, includeContextUUID);
        } else {
            scrollId = null;
        }
        final SearchPhaseController.ReducedQueryPhase reducedQueryPhase = resultConsumer.reduce();
        final boolean queryAndFetchOptimization = queryResults.length() == 1;
        //fetch结束后执行任务
        final Runnable finishPhase = ()
            -> moveToNextPhase(searchPhaseController, scrollId, reducedQueryPhase, queryAndFetchOptimization ?
            queryResults : fetchResults);
        if (queryAndFetchOptimization) {
            assert phaseResults.isEmpty() || phaseResults.get(0).fetchResult() != null : "phaseResults empty [" + phaseResults.isEmpty()
                + "], single result: " +  phaseResults.get(0).fetchResult();
            // query AND fetch optimization
            finishPhase.run();
        } else {
            ScoreDoc[] scoreDocs = reducedQueryPhase.sortedTopDocs.scoreDocs;
            final IntArrayList[] docIdsToLoad = searchPhaseController.fillDocIdsToLoad(numShards, scoreDocs);
            // no docs to fetch -- sidestep everything and return
            //无文档执行fetch
            if (scoreDocs.length == 0) {
                // we have to release contexts here to free up resources
                phaseResults.stream()
                    .map(SearchPhaseResult::queryResult)
                    .forEach(this::releaseIrrelevantSearchContext);
                finishPhase.run();
            } else {
                final ScoreDoc[] lastEmittedDocPerShard = isScrollSearch ?
                    searchPhaseController.getLastEmittedDocPerShard(reducedQueryPhase, numShards)
                    : null;
                final CountedCollector<FetchSearchResult> counter = new CountedCollector<>(r -> fetchResults.set(r.getShardIndex(), r),
                    docIdsToLoad.length, // we count down every shard in the result no matter if we got any results or not
                    finishPhase, context);
                for (int i = 0; i < docIdsToLoad.length; i++) {
                    IntArrayList entry = docIdsToLoad[i];
                    SearchPhaseResult queryResult = queryResults.get(i);
                    if (entry == null) { // no results for this shard ID
                        if (queryResult != null) {
                            // if we got some hits from this shard we have to release the context there
                            // we do this as we go since it will free up resources and passing on the request on the
                            // transport layer is cheap.
                            releaseIrrelevantSearchContext(queryResult.queryResult());
                            progressListener.notifyFetchResult(i);
                        }
                        // in any case we count down this result since we don't talk to this shard anymore
                        counter.countDown();
                    } else {
                        SearchShardTarget searchShardTarget = queryResult.getSearchShardTarget();
                        Transport.Connection connection = context.getConnection(searchShardTarget.getClusterAlias(),
                            searchShardTarget.getNodeId());
                        //创建fetch请求
                        ShardFetchSearchRequest fetchSearchRequest = createFetchRequest(queryResult.queryResult().getContextId(), i, entry,
                            lastEmittedDocPerShard, searchShardTarget.getOriginalIndices());
                        //执行fetch
                        executeFetch(i, searchShardTarget, counter, fetchSearchRequest, queryResult.queryResult(),
                            connection);
                    }
                }
            }
        }
    }

创建fetch请求,执行fetch

private void executeFetch(final int shardIndex, final SearchShardTarget shardTarget,
                              final CountedCollector<FetchSearchResult> counter,
                              final ShardFetchSearchRequest fetchSearchRequest, final QuerySearchResult querySearchResult,
                              final Transport.Connection connection) {
        //发送fetch请求
        context.getSearchTransport().sendExecuteFetch(connection, fetchSearchRequest, context.getTask(),
            new SearchActionListener<FetchSearchResult>(shardTarget, shardIndex) {
                @Override
                public void innerOnResponse(FetchSearchResult result) {
                    try {
                        progressListener.notifyFetchResult(shardIndex);
                        counter.onResult(result);
                    } catch (Exception e) {
                        context.onPhaseFailure(FetchSearchPhase.this, "", e);
                    }
                }

                @Override
                public void onFailure(Exception e) {
                    try {
                        logger.debug(
                            () -> new ParameterizedMessage("[{}] Failed to execute fetch phase", fetchSearchRequest.contextId()), e);
                        progressListener.notifyFetchFailure(shardIndex, shardTarget, e);
                        counter.onFailure(shardIndex, shardTarget, e);
                    } finally {
                        
                        releaseIrrelevantSearchContext(querySearchResult);
                    }
                }
            });
    }

发送RPC为indices:data/read/search[phase/fetch/id]的fetch请求

public void sendExecuteFetch(Transport.Connection connection, final ShardFetchSearchRequest request, SearchTask task,
                                 final SearchActionListener<FetchSearchResult> listener) {
        sendExecuteFetch(connection, FETCH_ID_ACTION_NAME, request, task, listener);
    }

数据节点注册的handler

//fetch请求
        transportService.registerRequestHandler(FETCH_ID_ACTION_NAME, ThreadPool.Names.SAME, true, true, ShardFetchSearchRequest::new,
            (request, channel, task) -> {
                //执行请求
                searchService.executeFetchPhase(request, (SearchShardTask) task,
                    new ChannelActionListener<>(channel, FETCH_ID_ACTION_NAME, request));
            });
  • 数据节点执行fetch阶段
public void executeFetchPhase(ShardFetchRequest request, SearchShardTask task, ActionListener<FetchSearchResult> listener) {
        runAsync(request.contextId(), () -> {
            final SearchContext context = findContext(request.contextId(), request);
            context.incRef();
            try {
                context.setTask(task);
                contextProcessing(context);
                if (request.lastEmittedDoc() != null) {
                    context.scrollContext().lastEmittedDoc = request.lastEmittedDoc();
                }
                context.docIdsToLoad(request.docIds(), 0, request.docIdsSize());
                try (SearchOperationListenerExecutor executor = new SearchOperationListenerExecutor(context, true, System.nanoTime())) {
                    //fetch查询
                    fetchPhase.execute(context);
                    if (fetchPhaseShouldFreeContext(context)) {
                        freeContext(request.contextId());
                    } else {
                        contextProcessedSuccessfully(context);
                    }
                    executor.success();
                }
                //返回结果
                return context.fetchResult();
            } catch (Exception e) {
                logger.trace("Fetch phase failed", e);
                processFailure(context, e);
                throw e;
            } finally {
                cleanContext(context);
            }
        }, listener);
    }

遍历文档id集合,查询详细信息

@Override
    public void execute(SearchContext context) {

        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("{}", new SearchContextSourcePrinter(context));
        }

        final FieldsVisitor fieldsVisitor;
        Map<String, Set<String>> storedToRequestedFields = new HashMap<>();
        StoredFieldsContext storedFieldsContext = context.storedFieldsContext();

        if (storedFieldsContext == null) {
            // no fields specified, default to return source if no explicit indication
            if (!context.hasScriptFields() && !context.hasFetchSourceContext()) {
                context.fetchSourceContext(new FetchSourceContext(true));
            }
            fieldsVisitor = new FieldsVisitor(context.sourceRequested());
        } else if (storedFieldsContext.fetchFields() == false) {
            // disable stored fields entirely
            fieldsVisitor = null;
        } else {
            for (String fieldNameOrPattern : context.storedFieldsContext().fieldNames()) {
                if (fieldNameOrPattern.equals(SourceFieldMapper.NAME)) {
                    FetchSourceContext fetchSourceContext = context.hasFetchSourceContext() ? context.fetchSourceContext()
                        : FetchSourceContext.FETCH_SOURCE;
                    context.fetchSourceContext(new FetchSourceContext(true, fetchSourceContext.includes(), fetchSourceContext.excludes()));
                    continue;
                }

                Collection<String> fieldNames = context.mapperService().simpleMatchToFullName(fieldNameOrPattern);
                for (String fieldName : fieldNames) {
                    MappedFieldType fieldType = context.smartNameFieldType(fieldName);
                    if (fieldType == null) {
                        // Only fail if we know it is a object field, missing paths / fields shouldn't fail.
                        if (context.getObjectMapper(fieldName) != null) {
                            throw new IllegalArgumentException("field [" + fieldName + "] isn't a leaf field");
                        }
                    } else {
                        String storedField = fieldType.name();
                        Set<String> requestedFields = storedToRequestedFields.computeIfAbsent(
                            storedField, key -> new HashSet<>());
                        requestedFields.add(fieldName);
                    }
                }
            }
            boolean loadSource = context.sourceRequested();
            if (storedToRequestedFields.isEmpty()) {
                // empty list specified, default to disable _source if no explicit indication
                fieldsVisitor = new FieldsVisitor(loadSource);
            } else {
                fieldsVisitor = new CustomFieldsVisitor(storedToRequestedFields.keySet(), loadSource);
            }
        }

        try {
            SearchHit[] hits = new SearchHit[context.docIdsToLoadSize()];
            FetchSubPhase.HitContext hitContext = new FetchSubPhase.HitContext();
            for (int index = 0; index < context.docIdsToLoadSize(); index++) {
                if (context.isCancelled()) {
                    throw new TaskCancelledException("cancelled");
                }
                //文档id
                int docId = context.docIdsToLoad()[context.docIdsToLoadFrom() + index];
                int readerIndex = ReaderUtil.subIndex(docId, context.searcher().getIndexReader().leaves());
                LeafReaderContext subReaderContext = context.searcher().getIndexReader().leaves().get(readerIndex);
                int subDocId = docId - subReaderContext.docBase;

                final SearchHit searchHit;
                int rootDocId = findRootDocumentIfNested(context, subReaderContext, subDocId);
                if (rootDocId != -1) {
                    searchHit = createNestedSearchHit(context, docId, subDocId, rootDocId,
                        storedToRequestedFields, subReaderContext);
                } else {
                    searchHit = createSearchHit(context, fieldsVisitor, docId, subDocId,
                        storedToRequestedFields, subReaderContext);
                }

                hits[index] = searchHit;
                hitContext.reset(searchHit, subReaderContext, subDocId, context.searcher());
                //执行各种元数据获取
                for (FetchSubPhase fetchSubPhase : fetchSubPhases) {
                    fetchSubPhase.hitExecute(context, hitContext);
                }
            }
            if (context.isCancelled()) {
                throw new TaskCancelledException("cancelled");
            }
            //执行各种元数据获取
            for (FetchSubPhase fetchSubPhase : fetchSubPhases) {
                fetchSubPhase.hitsExecute(context, hits);
                if (context.isCancelled()) {
                    throw new TaskCancelledException("cancelled");
                }
            }

            TotalHits totalHits = context.queryResult().getTotalHits();
            context.fetchResult().hits(new SearchHits(hits, totalHits, context.queryResult().getMaxScore()));
        } catch (IOException e) {
            throw ExceptionsHelper.convertToElastic(e);
        }
    }

private SearchHit createSearchHit(SearchContext context,
                                      FieldsVisitor fieldsVisitor,
                                      int docId,
                                      int subDocId,
                                      Map<String, Set<String>> storedToRequestedFields,
                                      LeafReaderContext subReaderContext) {
        DocumentMapper documentMapper = context.mapperService().documentMapper();
        Text typeText = documentMapper.typeText();
        if (fieldsVisitor == null) {
            return new SearchHit(docId, null, typeText, null, null);
        }
        //查询lucene保存的字段
        Map<String, DocumentField> searchFields = getSearchFields(context, fieldsVisitor, subDocId,
            storedToRequestedFields, subReaderContext);

        Map<String, DocumentField> metaFields = new HashMap<>();
        Map<String, DocumentField> documentFields = new HashMap<>();
        SearchHit.splitFieldsByMetadata(searchFields, documentFields, metaFields);

        SearchHit searchHit = new SearchHit(docId, fieldsVisitor.uid().id(), typeText, documentFields, metaFields);
        // Set _source if requested.
        SourceLookup sourceLookup = context.lookup().source();
        sourceLookup.setSegmentAndDocument(subReaderContext, subDocId);
        if (fieldsVisitor.source() != null) {
            sourceLookup.setSource(fieldsVisitor.source());
        }
        return searchHit;
    }

数据节点fetch数据后返回,协调节点。继续回到executeFetch方法的回调函数

 @Override
public void innerOnResponse(FetchSearchResult result) {
    try {
        progressListener.notifyFetchResult(shardIndex);
        counter.onResult(result);
    } catch (Exception e) {
        context.onPhaseFailure(FetchSearchPhase.this, "", e);
    }
}

将返回的数据填充到fetchResults中,最后执行finish阶段

final Runnable finishPhase = ()
            -> moveToNextPhase(searchPhaseController, scrollId, reducedQueryPhase, queryAndFetchOptimization ?
            queryResults : fetchResults);

 private void moveToNextPhase(SearchPhaseController searchPhaseController,
                                 String scrollId, SearchPhaseController.ReducedQueryPhase reducedQueryPhase,
                                 AtomicArray<? extends SearchPhaseResult> fetchResultsArr) {
        final InternalSearchResponse internalResponse = searchPhaseController.merge(context.getRequest().scroll() != null,
            reducedQueryPhase, fetchResultsArr.asList(), fetchResultsArr::get);
        context.executeNextPhase(this, nextPhaseFactory.apply(internalResponse, scrollId));
    }

执行ExpandSearchPhase的run方法

@Override
    public void run() {
        if (isCollapseRequest() && searchResponse.hits().getHits().length > 0) {
            SearchRequest searchRequest = context.getRequest();
            CollapseBuilder collapseBuilder = searchRequest.source().collapse();
            final List<InnerHitBuilder> innerHitBuilders = collapseBuilder.getInnerHits();
            MultiSearchRequest multiRequest = new MultiSearchRequest();
            if (collapseBuilder.getMaxConcurrentGroupRequests() > 0) {
                multiRequest.maxConcurrentSearchRequests(collapseBuilder.getMaxConcurrentGroupRequests());
            }
            for (SearchHit hit : searchResponse.hits().getHits()) {
                BoolQueryBuilder groupQuery = new BoolQueryBuilder();
                Object collapseValue = hit.field(collapseBuilder.getField()).getValue();
                if (collapseValue != null) {
                    groupQuery.filter(QueryBuilders.matchQuery(collapseBuilder.getField(), collapseValue));
                } else {
                    groupQuery.mustNot(QueryBuilders.existsQuery(collapseBuilder.getField()));
                }
                QueryBuilder origQuery = searchRequest.source().query();
                if (origQuery != null) {
                    groupQuery.must(origQuery);
                }
                for (InnerHitBuilder innerHitBuilder : innerHitBuilders) {
                    CollapseBuilder innerCollapseBuilder = innerHitBuilder.getInnerCollapseBuilder();
                    SearchSourceBuilder sourceBuilder = buildExpandSearchSourceBuilder(innerHitBuilder, innerCollapseBuilder)
                        .query(groupQuery)
                        .postFilter(searchRequest.source().postFilter());
                    SearchRequest groupRequest = new SearchRequest(searchRequest);
                    groupRequest.source(sourceBuilder);
                    multiRequest.add(groupRequest);
                }
            }
            context.getSearchTransport().sendExecuteMultiSearch(multiRequest, context.getTask(),
                ActionListener.wrap(response -> {
                    Iterator<MultiSearchResponse.Item> it = response.iterator();
                    for (SearchHit hit : searchResponse.hits.getHits()) {
                        for (InnerHitBuilder innerHitBuilder : innerHitBuilders) {
                            MultiSearchResponse.Item item = it.next();
                            if (item.isFailure()) {
                                context.onPhaseFailure(this, "failed to expand hits", item.getFailure());
                                return;
                            }
                            SearchHits innerHits = item.getResponse().getHits();
                            if (hit.getInnerHits() == null) {
                                hit.setInnerHits(new HashMap<>(innerHitBuilders.size()));
                            }
                            hit.getInnerHits().put(innerHitBuilder.getName(), innerHits);
                        }
                    }
                    context.sendSearchResponse(searchResponse, scrollId);
                }, context::onFailure)
            );
        } else {
            //返回查询结果
            context.sendSearchResponse(searchResponse, scrollId);
        }
    }

返回查询结果

@Override
    public void sendSearchResponse(InternalSearchResponse internalSearchResponse, String scrollId) {
        ShardSearchFailure[] failures = buildShardFailures();
        Boolean allowPartialResults = request.allowPartialSearchResults();
        assert allowPartialResults != null : "SearchRequest missing setting for allowPartialSearchResults";
        if (allowPartialResults == false && failures.length > 0){
            raisePhaseFailure(new SearchPhaseExecutionException("", "Shard failures", null, failures));
        } else {
            listener.onResponse(buildSearchResponse(internalSearchResponse, scrollId, failures));
        }
    }

这里的listener就是new RestStatusToXContentListener<>(channel)

@Override
    public RestResponse buildResponse(Response response, XContentBuilder builder) throws Exception {
        assert response.isFragment() == false; //would be nice if we could make default methods final
        response.toXContent(builder, channel.request());
        RestResponse restResponse = new BytesRestResponse(response.status(), builder);
        if (RestStatus.CREATED == restResponse.status()) {
            final String location = extractLocation.apply(response);
            if (location != null) {
                restResponse.addHeader("Location", location);
            }
        }
        return restResponse;
    }
    
    @Override
    public final RestResponse buildResponse(Response response) throws Exception {
        return buildResponse(response, channel.newBuilder());
    }

	protected final void processResponse(Response response) throws Exception {
        channel.sendResponse(buildResponse(response));
    }

最后通过channel将结果返回客户端

【Elasticsearch源码】查询源码分析(一) 1 前言 前言分析过ES的写入流程源码,详情见【Elasticsearch源码】写入源码分析。 Elasticsearch(ES)的查询接口具有分布式的数据检索、聚合分析能力,数据检索能力用于支持全文检索、日志分析等场景,如Github平台上的代码搜索、基于ES的各类日志分析服务等;聚合分析能力用于支持指标分析、APM等场景,如监控场景、应用的日活/留存分析等。 本文基于6.7.1版本,主要分... 阅读详情

相关推荐

【Elasticsearch源码】写入源码分析(一)

1 前言 Elasticsearch(ES)是一个基于Lucene的分布式存储和搜索分析系统,本文希望从源码的角度分析ES在保证数据的可靠性、实时性和一致性前提下,其写入的具体流程。 写入也是整个ES系统里面,最主要的流程之一,便于更好的理解ES的内部原理和逻辑,关于ES数据存储结构请参考:【Elasticsearch】原理-Elasticsearch数据存储结构与写入流程。 2 写入基本流程...

wudingmei1023 1万+

【Elasticsearch源码】 检索分析

带着疑问学源码,第二篇:Elasticsearch 搜索 代码分析基于:https://github.com/jiankunking/elasticsearch Elasticsearch 7.10.2+ 目的 在看源码之前先梳理一下,自己对于检索流程疑惑的点: 当索引是按照日期拆分之后,在使用-* 检索,会不会通过索引层面的时间配置直接跳过无关索引?使用*会对性能造成多大的影响? 源码分析 第二部分是代码分析的过程,不想看的朋友可以跳过直接看第三部分总结。 分析的话,咱们就以_search操作为主线.

勿忘初心 4548

[源码解析] elastic search 8.0.0 的启动过程(一)

启动的时候刚开始没有做啥,真正开始的执行的入口 org.elasticsearch.cli.Command#execute 1、创建 Environment 会利用到elasticsearch home中conf的配置文件,必填项 在创建Environment之前先准备 核心的关键是获取命令行传入进去的path.home的值,放到setting里。因为在创建Environment的时候,要依据该参数将里面的 config、plugins、data、logs、bin、lib、modules

weixin_39394909的博客 1966

ElasticSearch 源码深度剖析与实战指南

ElasticSearch是一个基于Apache Lucene构建的开源搜索引擎。它提供了一个分布式、多用户能力的全文搜索引擎,基于RESTful Web接口。ElasticSearch的分布式特性使其能够处理PB级别的数据,并能够达到实时搜索的能力。ElasticSearch是一个功能强大的搜索引擎,其分布式架构、倒排索引、查询优化技巧和集群管理监控功能使其成为了处理大数据搜索和分析的理想选择。本文深入探讨了ElasticSearch的核心知识点,并提供了实战代码示例。

赵KK的博客 1960

[源码解析] elastic search 8.0.0 的启动过程(四)

本文继续分析启动流程 1、ShardLimitValidator 集群中每个node上分片的数量最大默认是1000 2、MetadataCreateIndexService 主要负责处理创建索引 3、ActionModule 里面有一个入参IndexScopedSettings 表示索引层面一些内置的参数值,共计120个,里面有很多参数是经常接触的。 我们选几个典型的。比如 index.search.slowlog.threshold.fetch.trace -> {Set.

weixin_39394909的博客 383

Elasticsearch源码解析:核心模块解读

1. 背景介绍 1.1 Elasticsearch 的前世今生 Elasticsearch,简称为 ES,是一个开源的分布式搜索和分析引擎,以其强大的全文搜索能力和实时数据分析性能而闻名。它基于 Apache Lucene 库构建,提供了一个 RESTful API 来进行交互

AGI×大数据,开启智能时代的认知跃迁;解码AGI,赋能数据驱动的智能革命。 412

ElasticSearch源码走读——结构总览

开源ElasticSearch项目的代码结构总览

qq_42801937的博客 3245

Axure rp9 中继器新建表格

Axure rp9 中继器新建表格

weixin_49295490的博客 1652

Elasticsearch-7.8.0 创建索引底层源码分析(1)

1、前言 本专栏系列基于 elasticsearch-7.8 版本分析 陆续打算推出的博文列表如下: 1、创建索引底层源码分析 2、更新索引底层源码分析 3、删除索引底层源码分析 4、elasticsearch中的线程池实现 5、elasticsearch启动过程源码流程分析 6、high level rest client请求流程分析 7、elastic search数据副本模型、读写模型 8、lucene学习总结系列 全文检索的基本原理 lucene的总体结构 lucene的索引文件格式..

dkjhl的博客 1628

如何更好的理解PFC(功率因数校正)

此电路虽然简单,可以在前期设计的无PFC功能的设备上,简单的增加一个合适的电感(适当的选取L和C的值),从而达到具有PFC的作用,但是这种简单的、低成本的无源PFC输出纹波较大,滤波电容两端的直流电压也较低,电流畸变的校正及功率因数补偿的能力都很差,而且L的绕制及铁芯的质量控制不好,会对图像及伴音产生严重的干扰,只能是对于前期无PFC设备使之能进入市场的临时措施。)在t4~t5期间尽管二极管左边电压在逐步上升,但是由于二极管右边的Uc放电缓慢右边的电压Uc仍旧大于左边,二极管仍旧反偏截止。

U_YAO的博客 948

【Maxwell16.0】实战指南:从DXF导入到MotorCAD联合仿真的关键技巧

本文详细解析了Maxwell 16.0与MotorCAD进行联合仿真的核心流程与关键技巧。重点阐述了从DXF模型导入、几何修复、一极一槽模型剪裁,到坐标系对齐、MotorCAD参数匹配及结果验证的全过程,旨在帮助工程师高效打通联合仿真链路,规避常见错误,提升电机设计效率。

weixin_29062865的博客 713

科普文:Elasticsearch系列之【从源码详解Elasticsearch启动过程】

从上面的步骤可以看出Elasticsearch的单节点启动过程还是很复杂的,而且文章只是列出了大概的启动步骤,还有很多细节没有深挖,比如节点和集群的相互发现与加入,节点间的数据同步,集群master是如何选举的等。细节还需各位读者深读源码。

为无为,事无事,味无味。 1842

AIPex:AI + 自然语言驱动的浏览器自动化扩展

AIPex是一款革命性的Chrome扩展,通过AI技术实现自然语言驱动的浏览器自动化。它支持30多种自动化工具,包括智能标签页管理(分组、切换、批量操作)、页面内容提取(文本、链接、元数据)、交互元素操作(点击、滚动)以及表单智能填写等功能。用户只需用自然语言下达指令,即可快速完成复杂的浏览器操作,大幅提升工作效率。AIPex特别适合需要处理大量重复性网页操作的用户,如数据分析师、产品经理等。

blues_C的博客 1122

Elasticsearch 的源码解析

Elasticsearch 是一个基于 Lucene 的搜索引擎。它的核心功能包括:全文检索、结构化搜索、分析和存储。 Elasticsearch 的源码主要由 Java 编写,使用 Maven 进行管理和构建。它的核心模块包括: core: 这是 Elasticsearch 的核心模块,包含了 Lucene 的核心功能、分布式系统相关的类和整个系统的配置。 search: 这个模块提供了搜索...

weixin_35754962的博客 528

AutoCAD中圆弧折线化方法与实现

在AutoCAD中,圆弧折线化是将圆弧曲线转换为由一系列线段组成的折线曲线的过程。本文将介绍如何使用LISP编写代码来实现圆弧折线化,并提供相应的源代码。上述示例代码将生成一个以坐标原点为圆心、起点为(5.0, 0.0, 0.0)、终点为(0.0, 5.0, 0.0)、半径为5.0、角度为90度的圆弧的折线表示。通过使用上述的LISP代码,你可以在AutoCAD中实现将圆弧转换为折线的功能。你可以根据需要修改代码中的参数来适应不同的圆弧转换需求。函数,并传入圆弧的起点、终点、圆心、半径和角度参数。

VcuOcaml的博客 1417

麒麟服务器操作系统QT系列软件工具手册

对Qt提供的开发工具进行了一系列的优化之后,在日常的修复bug和维护框架的过程中,桌面研发对Qt的基础软件库也进行了部分优化工作,包括qtbase、qtwayland、qtscript、qt4-x11等Qt基础组件,涉及的Qt框架版本主要涉及到5.9和5.12两个版本。qt 的翻译,是由 ts 文件生成 qm 文件然后进行翻译,国产操作系统上qt 的翻译是由单独的qttranslations包中进行维护,其次部分汉化问题也是qt组件维护工作的一部分,qt的翻译机制如图4-7所示。

太极淘的博客 542

【Elasticsearch】ES机器学习源码分析

它是一种数据框分析,通过分析每个数据点与其他数据点的接近程度及其周围点簇的密度来识别数据集中的异常点。以 customer_full_name(顾客名称)为实体聚合每个顾客所有订单的总金额 sum/avg(taxful_total_price),得到一个每个顾客的平均消费金额和总消费金额,也就是 transfer 得到的是一般意义的样本数据而不是 datafeed 一样的时序数据。这是一种监督的途径。在这种模式下,用户不用训练机器学习什么是异常,期望得到的是什么,什么是对的,什么是错误的。

这个人很懒什么都没有~ 1万+

【以太网硬件六】1000base-T物理层编码究竟是怎么编的?

详述1000base-T物理层4D-PAM5的编码过程

highman110的博客 5323

elasticsearch 8.x - 源码调试

elasticsearch 使用 xpack 认证, 在文件。运行命令后,远程debug端点会自动连接到这个容器。在debug源码 之前,可以看下项目中的。这个编译时间比较长,下各种依赖包,安心等待。连接成功,现在就可以调试了, 尝试访问。文件,里面有怎么调试代码的操作步骤。然后,点击启动这个debug。

面朝大海,春暖花开 568

20、七鳃鳗繁殖与早期生活史:从水质到养殖的全面解析

本文全面解析了七鳃鳗的繁殖与早期生活史,重点探讨了水质对幼体生存的影响、养殖密度与生长关系、变态过程的时间与大小特征,以及人工繁殖在濒危物种恢复和入侵物种控制中的应用。同时,文章分析了大规模养殖面临的挖掘基质需求、空间限制、清洁管理、疾病防控及微生物环境等挑战,并强调实验室养殖对科学研究和种群管理的重要意义。随着技术进步,未来有望扩展更多物种的繁殖研究,揭示七鳃鳗生物学的进化奥秘,实现资源的可持续利用。

l6m7n8的博客 115

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

ElasticSearch 8.X 源码导入idea并配置环境启动调试(mac环境)

主要是用于自己记录配置流程。

a925907195的专栏 2152

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

elasticsearch源码分析-01服务启动

1.服务启动 服务启动入口类为Elasticsearch /** * Main entry point for starting elasticsearch */ public static void main(final String[] args) throws Exception { LogConfigurator.registerErrorListener(); final Elasticsearch elasticsearch = new Elasticsearch();

GeekerJava的博客 1127

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

idea debug elasticsearch8.6.2 源码

该版本默认开启了权限,访问localhost:9200需要输入账号密码。打开idea es代码,配置debug参数,ok后运行。2、idea打开项目工程,设置gradle和jdk。1、下载源码 8.6.2 zip包 并解压。然后cmd bug模式启动源码。4、确定后等待初次构建完成。已经成功进idea断点中。5、cmd进入源码目录。在 idea打上断点。

DavidSoCool的博客 1086

(四)elasticsearch 源码之索引流程分析

本文简单描述了es索引流程,包括了http请求是如何解析的,如何确定分片的。但是仍有许多不足,比如没有讨论远程节点是如何处理的,lucene执行引擎的细节,后面博客会继续探讨这些课题。

大叶子不小的博客 1310

Elasticsearch源码分析-关于action

ES源码分析-关于action ES源码版本 7.10 启动过程中的action 比如我们有个搜索请求: curl -X GET "localhost:9200/blogs/_search?pretty" -H 'Content-Type: application/json' -d' { "query": { "function_score": { "query": { "match": { "content": "elasticsearch"

lucasma的博客 809
上一篇: elasticsearch源码分析-07GET查询流程
下一篇: lucene学习-FST源码分析
胖柯G
博客等级 码龄10年 453粉丝 57原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值