背景
ES的读取分为GET和Search两种操作,这两种读取操作有较大的差异,GET/MGET必须指定三元组:_index、_type、_id。也就是说,根据文档id从正排索引中获取内容。而Search不指定_id,根据关键词从倒排索引中获取内容。
可接收客户端请求的节点称为协调节点。在协调节点,搜索任务被执行成一个两阶段过程,即query then fetch。真正执行搜索任务的节点称为数据节点。当搜索任务执行在分布式系统上时,整体流程如下图所示。
需要两个阶段才能完成搜索的原因是,在查询的时候不知道文档位于哪个分片,因此索引的所有分片(某个副本)都要参与搜索,然后协调节点将结果合并,再根据文档ID获取文档内容。例如,有5个分片,查询返回前10个匹配度最高的文档,那么每个分片都查询出当前分片的TOP 10,协调节点将5*10=50的结果再次排序,返回最终TOP 10的结果给客户端。
下面我们按照请求涉及的两个节点来分析流程:协调节点和数据节点。
协调节点流程
两阶段相应的实现位置:查询(Query)阶段——InitialSearchPhase;取回(Fetch)阶段——FetchSearchPhase。它们都继承自SearchPhase。
Query阶段
-
协调节点广播查询请求到所有相关分片时,可以是主分片或副分片,协调节点将在之后的请求中轮询所有的分片副本来分摊负载。请求是基于shard遍历的,如果列表中的N个shard位于同一个节点,则向其发送N次请求,并不会把请求合并为一个。
// InitialSearchPhase.java public final void run() { if (shardsIts.size() > 0) { // 分shard发送搜索请求 for (int index = 0; index < shardsIts.size(); index++) { final SearchShardIterator shardRoutings = shardsIts.get(index); performPhaseOnShard(index, shardRoutings, shardRoutings.nextOrNull()); } } }
-
构建异步线程将搜索请求发送到真正的shard,监听所有shard query请求,成功返回回调onShardResult方法,失败返回回调onShardFailure方法。
// InitialSearchPhase.java private void performPhaseOnShard(final int shardIndex, final SearchShardIterator shardIt, final ShardRouting shard) { // 构建异步线程将搜索请求发送到真正的shard Runnable r = () -> { final Thread thread = Thread.currentThread(); try { executePhaseOnShard(shardIt, shard, new SearchActionListener<FirstResult>(shardIt.newSearchShardTarget(shard.currentNodeId()), shardIndex) { @Override public void innerOnResponse(FirstResult 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); } } }); } }; if (throttleConcurrentRequests) { pendingEx