ElasticSearch源码-04-search流程
2025-01-22 08:19:30    1.6k 字   
This post is also available in English and alternative languages.

ElasticSearch版本:6.5.0
纸上谈兵,不定时补充、更新,看到哪里写到哪里。文中图片较多、较大(已经过压缩),打不开多刷新几次。
目前仅涉及ElasticSearch内部,不涉及Lucene。


1. Elasticsearch Search流程

Elasticsearch的检索分为 Get、Search,这两种检索差异较大。

  • Get:必须指定 _index、_type、_id,根据文档id检索文档。
  • Search:不指定 _id,根据关键字 \ 词 检索文档。

2. 标题简称

Elasticsearch 检索分为两个阶段:Query阶段Fetch阶段,这两个阶段,发起请求方,都是"协调节点(Coordinating Nodes)

为了方便理解,在小标题中会有一些简写,这里说明下,防止误解。

CN:协调节点(Coordinating Nodes)。

CNQ:协调节点-query阶段

CNF:协调节点-fetch阶段

DN:数据节点(Data node)

DNQ:数据节点-query阶段

DNF:数据节点-fetch阶段


3. 协调节点前期处理

前期“协调节点”接收用户请求流程(Netty4HttpRequestHandler#channelRead0),可以参阅:03-es源码-索引数据 ,这里跳过 直接开始。

RestSearchAction中,注册了如下rest请求:

1
2
3
4
5
6
controller.registerHandler(GET, "/_search", this);
controller.registerHandler(POST, "/_search", this);
controller.registerHandler(GET, "/{index}/_search", this);
controller.registerHandler(POST, "/{index}/_search", this);
controller.registerHandler(GET, "/{index}/{type}/_search", this);
controller.registerHandler(POST, "/{index}/{type}/_search", this);

老规矩,在 ActionModule 类中对 RestSearchAction → SearchAction → TransportSearchAction 进行了映射。


3.1. 1、CN-RestSearchAction#prepareRequest

RestSearchAction#prepareRequest方法,对rest请求进行解析,包装成 SearchRequest。

CN-RestSearchAction-prepareRequest


3.2. 2、CN-TransportSearchAction#doExecute

  • 校验source参数,source就是query语句

  • 获取当前集群状态

  • 对集群进行分组,按照rest请求中的索引

    elasticsearch search 可以跨集群查询,所以对rest请求中的索引,按照集群名称进行分组。

  • remoteClusterIndices为空,则是本地集群;否则调用远程集群。

modules-cross-cluster-search

CN-TransportSearchAction-doExecute


3.3. 3、CN-TransportSearchAction#executeSearch

代码较长,分两部分

  • L297:集群状态检查
  • L301:解析索引
  • L303:解析每个索引对应的路由
  • 311:根据routingMap查找出本次请求的所有目标分片
  • L316:检查操作分片数量是否超出限制,对应参数’action.search.shard_count.limit’
  • L323:只有一个分片时,searchType=QUERY_THEN_FETCH,

CN-TransportSearchAction-executeSearch

  • L331:查询缓存
  • L357:查询前是否分片过滤(预过滤)

CN-TransportSearchAction-executeSearch2

  • 需要过滤分片的话,走过滤分片逻辑

  • 然后根据searchType,构建不同对象

返回的对象 AbstractSearchAsyncAction,该类继承了 InitialSearchPhase

CN-TransportSearchAction-executeSearch3

4. 协调节点-Query阶段

4.1. 1、CNQ-AbstractSearchAsyncAction#start

This is the main entry point for a search. This method starts the search execution of the initial phase.

这是搜索的主要入口点。此方法启动初始阶段的搜索执行。

CNQ-AbstractSearchAsyncAction-start


4.2. 2、CNQ-InitialSearchPhase#run

  • L158:分发请求,到每个分片上执行查询

遍历所有shard,发送请求。请求时基于shard的,如果请求中有多个shard位于同一节点,则向其发送多次请求,并不会合并请求。

shardsIts 是本次搜索涉及的所有分片。shardRoutings.nextOrNull() 方法是从某个分片的所有副本中选择一个。

CNQ-InitialSearchPhase-run


4.3. 3、CNQ-InitialSearchPhase#performPhaseOnShard

给分片发送query阶段请求

  • L217:执行成功后,进行Fetch处理
  • L222:执行失败后的处理

CNQ-InitialSearchPhase-performPhaseOnShard


4.4. 4、CNQ-SearchQueryThenFetchAsyncAction#executePhaseOnShard

在跳转 SearchTransportService#sendExecuteQuery 方法中,L152行方法参数列表中 指定了 queryActionName。

1
QUERY_ACTION_NAME = "indices:data/read/search[phase/query]";

数据节点收到协调节点发送的请求,会根据这个 queryActionName,进行不同的业务处理。

“协调节点” query阶段的请求流程这里就差不多结束了。

CNQ-SearchQueryThenFetchAsyncAction#executePhaseOnShard


5. 数据节点-Query

5.1. 1、DNQ-SearchTransportService#registerRequestHandler

数据节点对Query、Fetch请求的处理入口在该方法。

上面提到过,“协调节点” 发送query请求是,会传递一个 queryActionName 参数。

在 SearchTransportService#registerRequestHandler 方法中,会对不同类型的 queryActionName,注册不同的处理入口。

以上面的请求为例,QUERY_ACTION_NAME = “indices:data/read/search[phase/query]”,对应L355行,处理入口。

DN-SearchTransportService-registerRequestHandler


5.2. 2、DNQ-SearchService#executeQueryPhase

executeQueryPhase方法,是在 ActionListener#onResponse 中回调的,debug好几次都没有进去。

查询业务在 loadOrExecuteQueryPhase 方法中。

注意:L387 ~ L389,如果查询只涉及一个分片,数据节点在Query阶段后,直接进行Fetch

DN-SearchService-executeQueryPhase


5.3. 3、DNQ-SearchService#loadOrExecuteQueryPhase

canCache方法 是否走缓存;对应 配置:index.requests.cache.enable;这个方法同searchType也有关系。

看一些资料上写,canCache默认为true,我这里却是false。

DN-SearchService-loadOrExecuteQueryPhase


5.4. 4、DNQ-QueryPhase#execute

  • L105:预处理

查询逻辑在execute方法中(同名方法,不是重载)。

DN-QueryPhase#execute


5.5. 5、DNQ-QueryPhase#execute

(该方法太长,图片中对该类进行了分屏)

  • L134 - L135:设置from-size。

  • L269:调用 org.apache.lucene.search.IndexSearcher#search 方法查询lucene

DN-QueryPhase#execute

  • L286:检索Lucene获取结果后的处理。通过debug信息可以看到,取出了5个文档,其中包含score得分。

DN-QueryPhase-execute2

至此,数据节点-Query阶段流程结束。

注意:在SearchService#executeQueryPhase 方法中(L387 ~ L389),如果查询只涉及一个分片,数据节点在Query阶段后,直接进行Fetch


6. 协调节点-Fetch阶段

“协调节点” 收到 “数据节点”-Query查询成功的结果后,进行后置处理(Fetch)。

Fetch 阶段的起点为 FetchSearchPhase#innerRun,这里从“协调节点”监听回调看起。


6.1. 1、CNF-InitialSearchPhase#onShardResult

(从debug信息中,可以看到,fetchResult对象中,已经将文档详细信息获取到了,因为我本地启动,只有一个分片,在 数据节点-query阶段就已经做了fetch动作)

CNF-InitialSearchPhase#performPhaseOnShard


6.2. 2、CNF-InitialSearchPhase#successfulShardExecution

收集 数据节点Query阶段返回的结果。

通过 AbstractSearchAsyncAction#onPhaseDone 方法跳转

CNF-InitialSearchPhase-successfulShardExecution


6.3. 3、CNF-AbstractSearchAsyncAction#executeNextPhase

跳转 AbstractSearchAsyncAction#executePhase

CNF-AbstractSearchAsyncAction-executeNextPhase


6.4. 4、CNF-FetchSearchPhase#innerRun

通过 FetchSearchPhase#run 方法跳转进入 FetchSearchPhase#innerRun 方法。

(方法较长,分成两部分)

CNF-FetchSearchPhase#innerRun

核心处理在executeFetch方法

CNF-FetchSearchPhase#innerRun2


6.5. 5、CNF-FetchSearchPhase#executeFetch

“协调节点”发送Fetch请求

CNF-FetchSearchPhase-executeFetch


6.6. 6、CNF-SearchTransportService#sendExecuteFetch

转发请求,FETCH_ID_ACTION_NAME = “indices:data/read/search[phase/fetch/id]”;

这个 actionName 在 SearchTransportService#registerRequestHandler 方法中注册了Fetch处理入口。

CNF-SearchTransportService-sendExecuteFetch


6.7. #数据节点-Fetch

6.8. 1、DNF-SearchTransportService#registerRequestHandler

fetch入口在 searchService.executeFetchPhase 方法。

DNF-SearchTransportService-registerRequestHandler


6.9. 2、DNF-SearchService#executeFetchPhase

fetch核心处理 FetchPhase#execute 方法(L556)

(由于本地启动只有一个分片,这里debug进不来,后面再研究…)

DNF-SearchService-executeFetchPhase