这篇文章主要介绍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. Vue3 比 Vue2 快的体现-第一部分

    Vue3 比 Vue2 快的原因 首先体现在 Diff算法的优化上, Vue2 中的 虚拟DOM对比采用全量对比策略,这样的话每次渲染也就把静态dom节点做对比了.在Vue3 中 Diff算法 优化了 ...

  2. CSIG企业行-走进合合信息成功举行,聚焦生成式人工智能、智能文档处理前沿热点

    3月18日,由中国图象图形学学会(CSIG)主办,合合信息.CSIG文档图像分析与识别专业委员会联合承办的"CSIG企业行"系列活动成功举办.此次活动以"图文智能处理与多 ...

  3. 中国信通院高质量数字化转型产品及服务全景图发布,合合信息多项AI产品入选

    随着5G.人工智能.大数据等新一代技术的发展,企业在商业竞争中正面临更多不确定性.中国信通院高度关注企业数字化转型中遇到的痛点,发起"铸基计划-高质量数字化转型行动",链接企业数字 ...

  4. 为什么在EffectiveJava中建议用EnumSet替代位字段,以及使用EnumMap替换序数索引

    在EffectiveJava中的第 36条中建议 用 EnumSet 替代位字段,在第37条中建议 用EnumMap替换序数索引,为什么? EnumSet 在EffectiveJava中的第 36条中 ...

  5. RxJS 系列 – Filtering Operators

    前言 介绍完 RxJS 两大概念 Observable 和 Subject 之后, 篇幅最大的就是各种 Operators 了. 这篇先介绍比较简单的 Filter Operators. 请先看完上面 ...

  6. JavaScript – 冷知识 (新手)

    当 charAt 遇上 Emoji 参考: stackoverflow – How to get first character of string? 我们经常会用 charAt(0) 来获取 fir ...

  7. MyBatis——案例——修改(修改全部字段,修改动态字段)

    修改-修改全部字段   1.编写接口方法:Mapper接口     参数:所有数据     结果:void(通过异常捕获判断成功修改与否)        int (表示sql语句影响的行数) /** ...

  8. Spirng Aop 实现自定义注解及实现

    需求:日志记录 需要记录当前用户访问的每个接口对应的前端页面功能信息 声明一个注解 @Documented @Retention(RetentionPolicy.RUNTIME) @Target({E ...

  9. VM Ware 安装mac OS xxx 系统

    1. unlock 文件破解Vm Ware 默认不能安装苹果系统 :https://drive.google.com/file/d/1_AUeYh5JYltqjnuztQh-5UTomAIVBcLZ/ ...

  10. 删除 KubeSphere 中一直卡在 Terminating 的 Namespace

    介绍 最近一直在玩 EKS(Elastic Kubernetes Service -- Amazon EKS) 和 KubeSphere. 因为之前没有使用过 EKS 和 KubeSphere,所以这 ...