这篇文章主要介绍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查询执行过程的更多相关文章

  1. Impala源代码分析---1

    2.Impala源代码分析 參考链接:http://www.sizeofvoid.net/wp-content/uploads/ImpalaIntroduction2.pdf 本章開始进入源代码分析阶 ...

  2. mysql体系结构和sql查询执行过程简析

    一: mysql体系结构 1)Connectors 不同语言与 SQL 的交互 2)Management Serveices & Utilities 系统管理和控制工具 备份和恢复的安全性,复 ...

  3. MySQL查询执行过程

    MySQL查询执行路径 1. 客户端发送一条查询给服务器: 2. 服务器先会检查查询缓存,如果命中了缓存,则立即返回存储在缓存中的结果.否则进入下一阶段: 3. 服务器端进行SQL解析.预处理,再由优 ...

  4. 详细分析SQL语句逻辑执行过程和相关语法

    本文目录: 1.SQL语句的逻辑处理顺序 1.2 各数据库系统的语句逻辑处理顺序 1.2.1 SQL Server和Oracle的逻辑执行顺序 1.2.2 MariaDB的逻辑执行顺序 1.2.3 M ...

  5. 精尽MyBatis源码分析 - MyBatis 的 SQL 执行过程(一)之 Executor

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  6. Impala查询执行过程

  7. 第8章2节《MonkeyRunner源代码剖析》MonkeyRunner启动执行过程-解析处理命令行參数

    MonkeyRunnerStarter是MonkeyRunner启动时的入口类,由于它里面包括了main方法.它的整个启动过程主要做了以下几件事情: 解析用户启动MonkeyRunner时从命令行传输 ...

  8. Red5源代码分析 - 关键类及其初始化过程

    原文地址:http://semi-sleep.javaeye.com/blog/348768 Red5如何响应rmpt的请求,中间涉及哪些关键类? 响应请求的流程如下: 1.Red5在启动时会调用RT ...

  9. 第8章4节《MonkeyRunner源代码剖析》MonkeyRunner启动执行过程-启动AndroidDebugBridge

    上一节我们看到在启动AndroidDebugBridge的过程中会调用其start方法,而该方法会做2个基本的事情: 715行startAdb:开启AndroidDebugBridge 722-723 ...

  10. 第8章5节《MonkeyRunner源代码剖析》MonkeyRunner启动执行过程-执行測试脚本

    MonkeyRunner在准备好AndroidDebugBridge和DeviceMonitor等服务之后,就基本上是攻克了和目标设备通信的问题了,那往下须要做的就是把測试脚本执行起来了. 178 p ...

随机推荐

  1. c程序设计语言 by K&R(一)一些c语言基础知识

    出自<c程序设计语言> by K&R: 一.导言 二.类型.运算符与表达式 三.控制流 1. 字符输入与输出 getchar() 和 putchar(),输入一个字符.输出一个字符 ...

  2. EF Core – Soft Delete 实现

    前言 在 SQL Server – Soft Delete 中, 讲到了如果在 SQL Server 实现 Soft Delete. 这篇来说说, EF Core 在中间扮演的角色. 主要参考 Ent ...

  3. 【QT性能优化】QT性能优化之QT性能优化实战 QML优化 QT高性能 QT6系列视频课程 QT6 性能优化实战 QT高性能 QT原理源码 QML优化 GUI绘图原理源码

    QT性能优化实战视频课程 QT6 Widgets高性能应用编程 1.课前考试 2.字符串优化(上) 3.字符串优化(下) 4.绘图优化(上) 5.绘图优化(下)  6.QT界面优化(上) 7.QT界面 ...

  4. C#|.net core 基础 - 深拷贝的五大类N种实现方式

    在实际应用中经常会有这样的需求:获取一个与原对象数据相同但是独立于原对象的精准副本,简单来说就是克隆一份,拷贝一份,复制一份和原对象一样的对象,但是两者各种修改不能互相影响.这一行为也叫深克隆,深拷贝 ...

  5. mysql alter table修改表命令整理

    这篇文章主要介绍了mysql alter table修改表命令整理的相关资料,需要的朋友可以参考下   MYSQL ALTER TABLE命令用于修改表结构,例如添加/修改/删除字段.索引.主键等等, ...

  6. 配置windows update失败还原更改

    配置windows update失败还原更改_解决方案 解决方法: 方法1:     重启,按F8,选择最后一次正常启动.     如果还是需要等待.可采用方法2: 方法2:     重启,按F8,选 ...

  7. 《赵渝强》《Docker+Kubernetes容器实战派》新书上市!!!

    用一本书掌握Docker与Kubernetes核心内容!!! 本书基于作者多年的教学与实践经验编写,分为上下两篇,共20章. 上篇(第1-11章)介绍Docoker,包含:Docker入门.Docke ...

  8. 【赵渝强老师】搭建Hadoop环境

    说明:这里我们以本地模式和伪分布模式伪列,为大家介绍如何搭建Hadoop环境.有了这个基础,大家可以自行搭建Hadoop的全分布模式. 需要使用的安装介质: hadoop-2.7.3.tar.gz j ...

  9. Solon-Boot 与 SpringBoot 的概念不同

    平常我们是拿 Solon 生态与 SpringBoot 生态作比较.而非 Solon-Boot(仅是功能模块) 与 SpringBoot 生态,但这两名字太容易让人误解了. Solon-Boot So ...

  10. EBS GL 当前职责有访问权限的所有账套

    CREATE OR REPLACE VIEW CUX_GL_ACCESS_LEDGER_V AS SELECT L.LEDGER_ID,L.NAME,L.LEDGER_CATEGORY_CODE FR ...