Impala源代码分析(3)-backend查询执行过程
这篇文章主要介绍impala-backend是怎么执行一个SQL Query的。
在Impala中SQL Query的入口函数是:
void ImpalaServer::query(QueryHandle& query_handle, const Query& query)
- 生成一个QueryExecState伴随这个SQL执行的生命周期,代表正在执行的这个SQL;
- 调用Execute函数启动执行流程;
- 启动一个Wait线程等待结果。
这个Execute()函数首先是通过JNI向impala-fe请求SQL解析和执行计划生成(已经在上一篇文章中讲了这个过程),得到该Query对应的TExecRequest对象,交由impala-backend执行。
从下面这个函数开始backend执行,同时开始fragment status report。
Status ImpalaServer::QueryExecState::Exec(TExecRequest* exec_request)
因为我们知道在impala里面,一个Query是分配到多个节点执行的,我们把其中负责分配和协调这个Query执行的组件叫Coordinator;参与这个Query执行的每个节点叫backend instance,每个backend instance上面会执行一个或者多个PlanFragment。那么每个Query就对应一个Coordinator对象和多个backend instance,同时Coordinator中的query_profile_ 变量是用来统计这个query的执行的整个profile的。
Coordinator
这里首先生成Coordinator用于协调这个Query的执行,然后调用
Status Coordinator::Exec(
const TUniqueId& query_id, TQueryExecRequest* request,
const TQueryOptions& query_options)
启动异步的执行过程:说白了这个Coordinator就是老板,把活(PlanFragment)都给各个下属(backend instance)安排好了,发出去,然后自己下班走人了,才不会等着下属干完了才走呢。因为老板早就安排好自己的秘书(ImpalaServer::Wait())去盯着结果呢。
这个函数里面最重要的两个步骤:
- ComputeScanRangeAssignment(*request);
- ComputeFragmentExecParams(*request);
其中ComputeScanRangeAssignment(const TQueryExecRequest& exec_request) 用于填充std::vector<FragmentScanRangeAssignment> scan_range_assignment_ 这个数组是以PlanFragment为索引的。
typedef boost::unordered_map<THostPort, PerNodeScanRanges> FragmentScanRangeAssignment表示某个PlanFragment的backend instance以及其对应的PerNodeScanRanges的映射。而PerNodeScanRanges表示某个PlanFragment所涉及到的所有PlanNode到ScanRange的映射。
另外一个函数ComputeFragmentExecParams (const TQueryExecRequest& exec_request) 用于填充std::vector<FragmentExecParams> fragment_exec_params_ 。这个参数中每个FragmentExecParams对应着一个PlanFragment执行中用到的参数。
- Status Coordinator::ComputeFragmentHosts(const TQueryExecRequest& exec_request):为每个PlanFragment找到执行所在的backend instance。如果一个PlanFragment是UNPARTITIONED,那么就在这个Coordinator所在的host上运行;如果一个PlanFragment含有ScanNode,那么就调度这个PlanFragment到HDFS/HBase数据块所在的那些DataNodes上,也就是这些DataNodes就成为了执行这个Query的backend
instance。 - 计算TQueryExecRequest.fragments中每个PlanFragment会在哪些hosts上得到执行,填充到fragment_exec_params_ 中。
- 依次给每个PlanFragment执行的每个host分配一个instance_id。
- 填充每个 FragmentExecParams 的destinations(即Data Sink的目的地PlanFragment)和per_exch_num_senders(这个ExchangeNode会接收来自多少个PlanFragment的数据)
回到Coordinator::Exec()函数中,下面就该把各个PlanFragment分配干活了。
- 如果有Coordinator PlanFragment,那么先new PlanFragmentExecutor()生成这个PlanFragment所对应的PlanFragmentExecutor。然后填充其对应的TExecPlanFragmentParams。
- 下面是个双层循环:外层遍历PlanFragment,内层遍历backend instance,生成与每个instance关联的BackendExecState(主要是生成TExecPlanFragmentParams用于Coordinator与多个backend instance交互时的参数),并加入backend_exec_states_列表,用于Coordinator对所有的backend instance执行状况的管理。然后向每个instance发起RPC请求开始执行,请求协议是ImpalaInternalService::
ExecPlanFragment(TExecPlanFragmentParams)
Status fragments_exec_status = ParallelExecutor::Exec(
bind<Status>(mem_fn(&Coordinator::ExecRemoteFragment), this, _1),
reinterpret_cast<void**>(&backend_exec_states_[backend_num - num_hosts]),
num_hosts);
每个Coordinator,PlanFragmentExecutor和ExecNode都会有一个RuntimeProfile,所有的RuntimeProfile会构成树状结构来记录每个执行节点的执行过程中的信息。
在Coordinator有个成员变量boost::scoped_ptr<RuntimeProfile> query_profile_用于表示这个query过程中的所有的profile信息。
每个Coordinator还有个aggregate_profile_专门负责aggregate相关的profile。
PlanFragmentExecutor和ExecNode
无论是在Coordinator端还是在backend instance端执行的PlanFragment都是由一个PlanFragmentExecutor控制的。下面我们看看PlanFragment在backend instance是怎么执行的?
在RPC的server端调用了ImpalaServer::ExecPlanFragment()->ImpalaServer::StartPlanFragmentExecution()
生成FragmentExecState里面含有一个PlanFragmentExecutor。那么下面就是分析PlanFragmentExecutor怎么控制Query的执行的了。
- FragmentExecState::Prepare()调用PlanFragmentExecutor::Prepare()
- FragmentExecState::Exec()调用PlanFragmentExecutor::Open(),这个是PlanFragment执行的主循环,block直到该PlanFragment执行结束。
真正控制PlanFragment执行的是PlanFragmentExecutor,主要由Prepare()/Open()/GetNext()/Close()这几个函数组成。
1, PlanFragmentExecutor::Prepare(TExecPlanFragmentParams):准备执行,主要流程如下:
- 设定这个query能够使用的内存mem_limit;
- DescriptorTbl::Create():初始化descriptor table;
- ExecNode::CreateTree():生成执行树的结构(父子关系)。执行树由ExecNode组成,每一个ExecNode也提供了Prepare(), Open(), GetNext()函数。后面执行ExecNode::Prepare/Open/GenNext /EvalConjuncts/Close函数都是按照这个树状结构递归下去的。初始化完成后,PlanFragmentExecutor ::plan_指向了执行树的根节点。在这棵树中,root节点被最后执行,叶子节点被最先执行;
- 设置该PlanFragment的Exchange Node会接收来自多少个sender的数据;
- 调用plan_->Prepare():从根节点开始递归初始化执行树,主要是初始化runtime_profile等统计信息和conjuncts的LLVM本地代码生成 (adding functions to the LlvmCodeGen object);
- 如果使用本地代码生成,调用runtime_state_->llvm_codegen()->OptimizedModule()进行优化;
- 把所有的ScanNode对应的Scan Range映射到file/offset/length;
- DataSink::CreateDataSink();
- set up profile counter;
- 生成RowBatch用于存储结果。
2,PlanFragmentExecutor::Open()
先是start the profile-reporting thread,然后调用OpenInternal()
(1) 调用plan_->Open()沿着生成的ExecNode执行树依次调用ExecNode:: Open()
下面以HdfsScanNode::Open()为例说明:
- 调用DiskIoMgr:: RegisterReader初始化与HDFS的连接hdfs_connection_;
- 把要读取的File 和Split加入HdfsScanNode的队列queued_ranges_中;
- 调用HdfsScanNode::DiskThread驱动HdfsScanNode::StartNewScannerThread()->HdfsScanNode::ScannerThread->HdfsScanner:: ProcessSplit()去读取数据(目前一个scanner thread只能读取一个scan range);
- 调用IssueQueuedRanges()把上面加入queued_ranges_中的预读取Range发送给DiskIoMgr。由于上一步中已经启动了disk thread,所以就可以读取数据了。
(2) 如果当前这个PlanFragmen有sink,那么需要把这个PlanFragment要发给其他PF的数据都发出去。在发出去之前肯定得获取要发的东西吧,调用PlanFragmentExecutor ::GetNextInternal()从上到下递归调用执行树的ExecNode::GetNext()获取执行结果。
上面说到对于ExecNode::Open()不同种类的ExecNode的逻辑是不一样的,对于GetNext()也是一样的,可以参考下HdfsScanNode::GetNext()或者HashJoinNode::GetNext()看看具体是怎么获取查询结果的。
3, PlanFragmentExecutor::GextNext(RowBatch** batch)
显示触发执行树的ExecNode::GetNext()函数获取查询结果。当其标记PlanFragmentExecutor::done_==true时,则表明所有数据已经被处理完,该PlanFragmentExecutor可以退出了。
至此,impala-backend也分析完了。总的来说impala在执行过程中和MapReduce及Hive的不同可以概括为一拉一推。
- 在MapReduce中,Map的输出结果要等着Reduce去拉;而impala中各个PlanFragment执行结束之后DataSink是推送到其他PlanFragment的。这样能更加有效利用带宽,加快Job执行速度。
- 在Hive中,逻辑上下游节点是由上游节点推送给下游节点的;而impala中是下游节点通过递归调用GetNext()向上游节点拉取的。
Impala源代码分析(3)-backend查询执行过程的更多相关文章
- Impala源代码分析---1
2.Impala源代码分析 參考链接:http://www.sizeofvoid.net/wp-content/uploads/ImpalaIntroduction2.pdf 本章開始进入源代码分析阶 ...
- mysql体系结构和sql查询执行过程简析
一: mysql体系结构 1)Connectors 不同语言与 SQL 的交互 2)Management Serveices & Utilities 系统管理和控制工具 备份和恢复的安全性,复 ...
- MySQL查询执行过程
MySQL查询执行路径 1. 客户端发送一条查询给服务器: 2. 服务器先会检查查询缓存,如果命中了缓存,则立即返回存储在缓存中的结果.否则进入下一阶段: 3. 服务器端进行SQL解析.预处理,再由优 ...
- 详细分析SQL语句逻辑执行过程和相关语法
本文目录: 1.SQL语句的逻辑处理顺序 1.2 各数据库系统的语句逻辑处理顺序 1.2.1 SQL Server和Oracle的逻辑执行顺序 1.2.2 MariaDB的逻辑执行顺序 1.2.3 M ...
- 精尽MyBatis源码分析 - MyBatis 的 SQL 执行过程(一)之 Executor
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- Impala查询执行过程
- 第8章2节《MonkeyRunner源代码剖析》MonkeyRunner启动执行过程-解析处理命令行參数
MonkeyRunnerStarter是MonkeyRunner启动时的入口类,由于它里面包括了main方法.它的整个启动过程主要做了以下几件事情: 解析用户启动MonkeyRunner时从命令行传输 ...
- Red5源代码分析 - 关键类及其初始化过程
原文地址:http://semi-sleep.javaeye.com/blog/348768 Red5如何响应rmpt的请求,中间涉及哪些关键类? 响应请求的流程如下: 1.Red5在启动时会调用RT ...
- 第8章4节《MonkeyRunner源代码剖析》MonkeyRunner启动执行过程-启动AndroidDebugBridge
上一节我们看到在启动AndroidDebugBridge的过程中会调用其start方法,而该方法会做2个基本的事情: 715行startAdb:开启AndroidDebugBridge 722-723 ...
- 第8章5节《MonkeyRunner源代码剖析》MonkeyRunner启动执行过程-执行測试脚本
MonkeyRunner在准备好AndroidDebugBridge和DeviceMonitor等服务之后,就基本上是攻克了和目标设备通信的问题了,那往下须要做的就是把測试脚本执行起来了. 178 p ...
随机推荐
- Seata 1.3.0 Oracle 回滚测试验证 报错 ORA-02289: 序列不存在
使用Seata 1.3.0版本,测试A服务调用B服务,且A方法中,手动写了一个异常,测试是否正常回滚(Mysql已经测试过) 发现报错:ORA-02289: 序列不存在 一看就是undo_log这张表 ...
- 开源文档管理系统 MinDoc 安装和使用教程
说到文档管理,很多团队的文档管理都是一团糟,每个员工在自己本地写了各种 Word 文档.Excel 表格.甚至还有手写的便签,到处都是,找起来就像大海捞针.有些聪明的团队开始用飞书来管理团队文档,但是 ...
- 在stable diffussion中完美修复AI图片
无论您的提示和模型有多好,一次性获得完美图像的情况很少见. 修复小缺陷的不可或缺的方法是图像修复(inpainting).在这篇文章中,我将通过一些基本示例来介绍如何使用图像修复来修复缺陷. 需要的软 ...
- Figma 学习笔记 – Interactive Components
参考: Input Field Interaction using Interactive Components in Figma Create interactive components with ...
- QQ或者微信可以放昵称的超好看的符号
☪︎⋆ ✯ ⛈ •ᴗ• •ᴥ• ◔.̮◔ ᕱ ᕱ ⸝⸝· ᴥ ·⸝⸝ ʕ·͡ˑ·ཻʔ ʕ•̫͡•ོʔ ˃̣̣̥᷄⌓˂̣̣̥᷅ °꒰'ꀾ'꒱° ⋆ᶿ̵᷄ ˒̼ ᶿ̵᷅⋆ ˙ϖ˙ ⚝ ︎ .˗ˏˋ♡ˎˊ˗ ...
- 线段树介绍(segment tree)
1.引入 给定一个区间\([1, n]\),希望你实现一种数据结构,支持以下操作: 1.修改\(i\)号节点的值. 2.询问区间\([i, j]\)中所有节点的和. 这不是树状数组板子 3.修改区间\ ...
- 谈一谈 vuex 中的核心属性
1. state 保存数据的位置 : 2. mutations 唯一修改 state 数据的方式 : 3. getter 监听 state 数据的变化 : 4. actions 执行异步代码,通过 c ...
- 斜率优化DP简单总结&&“土地购买”题解
今天刚刷完了斜率优化DP,简单从头回顾一下. \[首先,能写出DP方程应该是最重要的,毕竟斜率只是用来优化的 \] 那么一个DP方程能用斜率优化,具备一种形式: \[f[i]+s1[i]+A[i]*B ...
- 『玩转Streamlit』--环境配置
尽管Streamlit的使用非常直观,但正确的环境配置对于充分发挥其潜力仍然至关重要. 本篇将介绍如何从头开始配置Streamlit环境,以及Streamlit开发过程中常用的几个命令. 最后通过一个 ...
- 删除 KubeSphere 中一直卡在 Terminating 的 Namespace
介绍 最近一直在玩 EKS(Elastic Kubernetes Service -- Amazon EKS) 和 KubeSphere. 因为之前没有使用过 EKS 和 KubeSphere,所以这 ...