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 | controller.registerHandler(GET, "/_search", this); |
老规矩,在 ActionModule 类中对 RestSearchAction → SearchAction → TransportSearchAction 进行了映射。
3.1. 1、CN-RestSearchAction#prepareRequest
RestSearchAction#prepareRequest方法,对rest请求进行解析,包装成 SearchRequest。
3.2. 2、CN-TransportSearchAction#doExecute
校验source参数,source就是query语句
获取当前集群状态
对集群进行分组,按照rest请求中的索引
elasticsearch search 可以跨集群查询,所以对rest请求中的索引,按照集群名称进行分组。
remoteClusterIndices为空,则是本地集群;否则调用远程集群。
3.3. 3、CN-TransportSearchAction#executeSearch
代码较长,分两部分
- L297:集群状态检查
- L301:解析索引
- L303:解析每个索引对应的路由
- 311:根据routingMap查找出本次请求的所有目标分片
- L316:检查操作分片数量是否超出限制,对应参数’action.search.shard_count.limit’
- L323:只有一个分片时,searchType=QUERY_THEN_FETCH,
- L331:查询缓存
- L357:查询前是否分片过滤(预过滤)
需要过滤分片的话,走过滤分片逻辑
然后根据searchType,构建不同对象
返回的对象 AbstractSearchAsyncAction,该类继承了 InitialSearchPhase
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.
这是搜索的主要入口点。此方法启动初始阶段的搜索执行。
4.2. 2、CNQ-InitialSearchPhase#run
- L158:分发请求,到每个分片上执行查询
遍历所有shard,发送请求。请求时基于shard的,如果请求中有多个shard位于同一节点,则向其发送多次请求,并不会合并请求。
shardsIts 是本次搜索涉及的所有分片。shardRoutings.nextOrNull() 方法是从某个分片的所有副本中选择一个。
4.3. 3、CNQ-InitialSearchPhase#performPhaseOnShard
给分片发送query阶段请求
- L217:执行成功后,进行Fetch处理
- L222:执行失败后的处理
4.4. 4、CNQ-SearchQueryThenFetchAsyncAction#executePhaseOnShard
在跳转 SearchTransportService#sendExecuteQuery 方法中,L152行方法参数列表中 指定了 queryActionName。
1 | QUERY_ACTION_NAME = "indices:data/read/search[phase/query]"; |
数据节点收到协调节点发送的请求,会根据这个 queryActionName,进行不同的业务处理。
“协调节点” query阶段的请求流程这里就差不多结束了。
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行,处理入口。
5.2. 2、DNQ-SearchService#executeQueryPhase
executeQueryPhase方法,是在 ActionListener#onResponse 中回调的,debug好几次都没有进去。
查询业务在 loadOrExecuteQueryPhase 方法中。
注意:L387 ~ L389,如果查询只涉及一个分片,数据节点在Query阶段后,直接进行Fetch。
5.3. 3、DNQ-SearchService#loadOrExecuteQueryPhase
canCache方法 是否走缓存;对应 配置:index.requests.cache.enable;这个方法同searchType也有关系。
看一些资料上写,canCache默认为true,我这里却是false。
5.4. 4、DNQ-QueryPhase#execute
- L105:预处理
查询逻辑在execute方法中(同名方法,不是重载)。
5.5. 5、DNQ-QueryPhase#execute
(该方法太长,图片中对该类进行了分屏)
L134 - L135:设置from-size。
L269:调用 org.apache.lucene.search.IndexSearcher#search 方法查询lucene
- L286:检索Lucene获取结果后的处理。通过debug信息可以看到,取出了5个文档,其中包含score得分。
至此,数据节点-Query阶段流程结束。
注意:在SearchService#executeQueryPhase 方法中(L387 ~ L389),如果查询只涉及一个分片,数据节点在Query阶段后,直接进行Fetch。
6. 协调节点-Fetch阶段
“协调节点” 收到 “数据节点”-Query查询成功的结果后,进行后置处理(Fetch)。
Fetch 阶段的起点为 FetchSearchPhase#innerRun,这里从“协调节点”监听回调看起。
6.1. 1、CNF-InitialSearchPhase#onShardResult
(从debug信息中,可以看到,fetchResult对象中,已经将文档详细信息获取到了,因为我本地启动,只有一个分片,在 数据节点-query阶段就已经做了fetch动作)
6.2. 2、CNF-InitialSearchPhase#successfulShardExecution
收集 数据节点Query阶段返回的结果。
通过 AbstractSearchAsyncAction#onPhaseDone 方法跳转
6.3. 3、CNF-AbstractSearchAsyncAction#executeNextPhase
跳转 AbstractSearchAsyncAction#executePhase
6.4. 4、CNF-FetchSearchPhase#innerRun
通过 FetchSearchPhase#run 方法跳转进入 FetchSearchPhase#innerRun 方法。
(方法较长,分成两部分)
核心处理在executeFetch方法
6.5. 5、CNF-FetchSearchPhase#executeFetch
“协调节点”发送Fetch请求
6.6. 6、CNF-SearchTransportService#sendExecuteFetch
转发请求,FETCH_ID_ACTION_NAME = “indices:data/read/search[phase/fetch/id]”;
这个 actionName 在 SearchTransportService#registerRequestHandler 方法中注册了Fetch处理入口。
6.7. #数据节点-Fetch
6.8. 1、DNF-SearchTransportService#registerRequestHandler
fetch入口在 searchService.executeFetchPhase 方法。
6.9. 2、DNF-SearchService#executeFetchPhase
fetch核心处理 FetchPhase#execute 方法(L556)
(由于本地启动只有一个分片,这里debug进不来,后面再研究…)