带着疑问学源码,第二篇:Elasticsearch 搜索
代码分析基于:https://github.com/jiankunking/elasticsearch
Elasticsearch 7.10.2+
目的
在看源码之前先梳理一下,自己对于检索流程疑惑的点:
当索引是按照日期拆分之后,在使用-* 检索,会不会通过索引层面的时间配置直接跳过无关索引?使用*会对性能造成多大的影响?
源码分析
第二部分是代码分析的过程,不想看的朋友可以跳过直接看第三部分总结。
分析的话,咱们就以_search操作为主线。
在RestSearchAction可以看到:
- 路由注册
- 请求参数转换
真正执行的是TransportSearchAction,类图如下:
1 | TransportSearchAction doExecute => |
下面先看一下:
1 | private void executeRequest(Task task, SearchRequest searchRequest, |
下面再看一下executeSearch:
1 | private void executeSearch(SearchTask task, SearchTimeProvider timeProvider, SearchRequest searchRequest, |
在看searchAsyncAction之前先看一下AbstractSearchAsyncAction的继承及实现类:
searchAsyncAction主要是生成查询的请求,也就是AbstractSearchAsyncAction的实例:
1 | private AbstractSearchAsyncAction<? extends SearchPhaseResult> searchAsyncAction( |
获取到具体的SearchAsyncAction之后具体的执行是通过run()来调用各个实现类具体的执行了:
1 | /** |
因为默认的搜索类型是QUERY_THEN_FETCH,那么下面看一下SearchQueryThenFetchAsyncAction,在SearchQueryThenFetchAsyncAction中没有重写run(),所以真正执行的还是父类AbstractSearchAsyncAction中的run(),下面看下:
1 | @Override |
通过performPhaseOnShard,来进行具体某个shard的搜索:
1 | protected void performPhaseOnShard(final int shardIndex, final SearchShardIterator shardIt, final SearchShardTarget shard) { |
每个分片在执行完毕Query子任务后,通过节点间通信,回调AbstractSearchAsyncAction类中的onShardResult方法,把查询结果记录在协调节点保存的数组结构results中,并增加计数:
1 | /** |
当返回结果的分片数等于预期的总分片数时,协调节点会进入当前Phase的结束处理,启动下一个阶段Fetch Phase的执行。注意,ES中只需要一个分片执行成功,就会进行后续Phase处理得到部分结果,当然它会在结果中提示用户实际有多少分片执行成功。
onPhaseDone会调用executeNextPhase方法进入下一个阶段,从而开始进入Fetch 阶段。
1 | /** |
下面看一下FetchSearchPhase中的run():
1 | @Override |
从代码在哪个可以看到Fetch后的结果保存到了counter中,而counter是定义在innerRun内:
1 | final CountedCollector<FetchSearchResult> counter = new CountedCollector<>(fetchResults, |
fetchResults用于存储从某个shard收集的结果,每收到一个shard数据就执行一次counter.countDown()。当所有shard收集完成之后,countDown会触发执行finishPhase:
1 | // FetchSearchPhase类中 |
获取查询结果之后,进入ExpandSearchPhase类中的run():
1 | // 主要判断是否启用字段折叠(field collapsing),根据需要实现字段折叠, |
看到这里还是没有发现针对-*有什么特殊的优化,还是会根据检索条件遍历符合条件的所有索引及其shard。那下面看那一下具体获取数据的时候有没有什么特殊处理,也就是data node 在Query、Fetch阶段有没有什么特殊的优化?
下面看一下SearchTransportService下的sendExecuteQuery
1 | public void sendExecuteQuery(Transport.Connection connection, final ShardSearchRequest request, SearchTask task, |
通过请求路径QUERY_ACTION_NAME可以在SearchTransportService中找到对应的处理函数searchService.executeQueryPhase:
1 | transportService.registerRequestHandler(QUERY_ACTION_NAME, ThreadPool.Names.SAME, ShardSearchRequest::new, |
下面具体看一下执行:
1 | public void executeQueryPhase(ShardSearchRequest request, boolean keepStatesInContext, |
先略过cache部分,重点看一下QueryPhase类中的execute:
1 | public void execute(SearchContext searchContext) throws QueryPhaseExecutionException { |
searchWithCollectorManager与searchWithCollector都是调用ContextIndexSearcher类中的search调用Lucene接口进行查询。
到目前为止都没发现,针对-*查询都没有任何优化。
唯一有希望进行优化的地方就是通过luece检索shard的时候,会进行优化,事实上会进行一定的优化,比如借助Lucene的PointValues来优化IntField,LongField,FloatField,DoubleField。
但不管怎么优化,对于搜索而言,还是能缩小范围就缩小,不管怎么优化,都不是一点成本没有的。
总结
elasticsearch针对-*检索不会在索引、shard层面优化,但会在检索具体shard的时候,通过luece的特性来快速调过一些不符合条件的shard。但这些特性不能保证一定会快速检索某些shard,因为很有可能你的检索条件位于shard的上下限之间。
所以说,还是在数据入es时,拆分到合适的索引,效果最好。
推荐阅读: