Impala与分布式执行计划
文章目录
Imapla的架构原理
Impala的组件
Impala是⼀个分布式,⼤规模并⾏处理(MPP)数据库引擎,它包括多个进程。Impala与Hive类似不是数据库⽽是数据分析⼯具;
impalad
- ⻆⾊名称为Impala Daemon,是在每个节点上运⾏的进程,是Impala的核⼼组件,进程名是Impalad;
- 作⽤,负责读写数据⽂件,接收来⾃Impala-shell,JDBC,ODBC等的查询请求,与集群其它Impalad分布式并⾏完成查询任务,并将查询结果返回给中⼼协调者。
- 为了保证Impalad进程了解其它Impalad的健康状况,Impalad进程会⼀直与statestore保持通信。
- Impalad服务由三个模块组成:Query Planner、Query Coordinator和Query Executor,前两个模块组成前端,负责接收SQL查询请求,解析SQL并转换成执⾏计划,交由后端执⾏。
statestored
- statestore监控集群中Impalad的健康状况,并将集群健康信息同步给Impalad。
- statestore进程名为statestored
catalogd
- Impala执⾏的SQL语句引发元数据发⽣变化时,catalog服务负责把这些元数据的变化同步给其它Impalad进程(⽇志验证,监控statestore进程⽇志)
- catalog服务对应进程名称是catalogd
- 由于⼀个集群需要⼀个catalogd以及⼀个statestored进程,⽽且catalogd进程所有请求都是经过statestored进程发送,所以官⽅建议让statestored进程与catalogd进程安排同个节点。
Impala的查询
-
Client提交任务
Client发送⼀个SQL查询请求到任意⼀个Impalad节点,会返回⼀个queryId用于之后的客户端操作。 -
生成单机和分布式执行计划
SQL提交到Impalad节点之后,Analyser依次执行SQL的词法分析、语法分析、语义分析等操作;
从MySQL元数据库中获取元数据,从HDFS的名称节点中获取数据地址,以得到存储这个查询相关数据的所有数据节点。- 单机执行计划: 根据上⼀步对SQL语句的分析,由Planner先⽣成单机的执行计划,该执行计划是有PlanNode组成的⼀棵树,这个过程中也会执行⼀些SQL优化,例如Join顺序改变、谓词下推等。
- 分布式并行物理计划:将单机执行计划转换成分布式并行物理执行计划,物理执行计划由⼀个个的Fragment组成,Fragment之间有数据依赖关系,处理过程中需要在原有的执行计划之上加⼊⼀些ExchangeNode和DataStreamSink信息等。
* Fragment :sql生成的分布式执行计划的⼀个子任务;
* DataStreamSink:传输当前的Fragment输出数据到不同的节点
-
任务调度和分发
Coordinator将Fragment(子任务)根据数据分区信息发配到不同的Impalad节点上执行。Impalad节点接收到执行Fragment请求交由Executor执行。 -
Fragment之间的数据依赖
每⼀个Fragment的执行输出通过DataStreamSink发送到下⼀个Fragment,Fragment运行过程中不断向coordinator节点汇报当前运行状态。 -
结果汇总
查询的SQL通常情况下需要有⼀个单独的Fragment用于结果的汇总,它只在Coordinator节点运行,将多个节点的最终执行结果汇总,转换成ResultSet信息。 -
获取结果
客户端调⽤获取ResultSet的接口,读取查询结果。
分布式执行计划
以⼀个SQL例⼦来展示查询计划
SQL语句
select
t1.n1,
t2.n2,
count(1) as c
from t1 join t2 on t1.id = t2.id
join t3 on t1.id = t3.id
where t3.n3 between ‘a’ and ‘f’
group by t1.n1, t2.n2
order by c desc
limit 100;
单机执行计划
分析上⾯的单机执⾏计划,第⼀步先去扫描t1表中需要的数据,如果数据⽂件存储是列式存储我们可以便利的扫描到所需的列id,n1; 接着需要与t2表进⾏Join操作,扫描t2表与t1表类似获取到所需数据列id,n2。t1与t2表进⾏关联执行join操作,关联之后再与t3表进⾏关联,这⾥会使⽤谓词下推扫描t3表只取join所需数据;关联三个表得到数据后,对group by进⾏相应的aggregation操作,最终是排序取出指定数量的数据返回。
这里由于是在一台机器上完成上述过程,所以逻辑上还是比较直观的。但是倘若所查询的T1和T2的数据都比较大,无法在一台机器上存储,或者单机执行效率太慢,这个时候要怎么办呢。此时就需要分布式并行执行计划来执行上述流程,那么进行分布式设计呢?
分布式并行执行计划
所谓的分布式并行化执行计划就是在单机执行计划基础之上结合数据分布式存储的特点,按照任务的计要求把单机执行计划拆分为多段子任务,每个子任务都是可以并并执行的。上面的单机执行计划转为分布式并行执行计划如下图所示:
接下来对每一部分进行详细描述。
整体流程
一、Hash join分区
首先底层仍然先执行的是数据间的join操作。这里我们假设T1表和T2表是大表,分布在三个节点上(三台机器上)。而T3表是小表,存储在第三台节点上。此时为了做表之间数据的Join操作,可以想到需要将相同id字段的数据汇总到一起,这样才能进行拼接。
为了实现该过程,分布式执行计划会首先根据T1和T2两个大表中数据的id字段进行hash join,采取的公式类似于:
这个是MapReduce中HashPartitioner
类中的方法。可以看出上式的分区结果只与key值的hashCode有关。这里id字段是int类型,则hashCode就是其本身,所以通过上式,就可以实现将T1和T2表中相同id字段的数据划分到同一分区中。(这里要注意的是,不是id为1的字段进入一个分区,2、3、4、5…都各进入一个分区,通过上式可以看出是对分区大小取%的操作,图中分区大小是3,因此id为1,4,7…的数据进入同一分区。)
同时要注意的是,对于T3表与T1、T2表数据的关联采用的不是hash join,而是利用Broadcast来进行join。 因为T3表的数据量较小,如果仍然采用hash join的话,就需要在T1表和T2表通过hash join分区后,再利用hash join与T3表重复执行一遍上述分区流程。这样增加了不同节点间网络传输的开销,也降低了执行效率。因此,对于小表,分布式执行计划采用的方法是利用广播机制,将该T3表直接分发到三个节点上。这样每个节点在执行完T1表与T2表的关联后,就可以直接在本机上执行与T3表的关联。
通过上面的步骤,就可以保证对于T1表和T2表,id字段相同的数据进入到同一分区中进行join操作,然后再与本机的T3表进行join操作。即完成了sql中的from t1 join t2 on t1.id = t2.id join t3 on t1.id = t3.id
二、分区内预聚合
这一步相当于执行group by
的步骤。因为在分区时每条数据都带着各自的字段,也就包含着n1,n2的数据,所以此时在每个分区中,就可以对该分区内的数据按照n1, n2的值,执行group by
操作进行一个局部的预聚合。
三、全局聚合
通过上述操作可以得到分区内的局部预聚合。但此时,条件相同的n1,n2可能分散在各个分区中(因为最初是按id字段分区的)。因此还需要一个全局的聚合,把所有hash结果相同的<n1,n2>进行汇总,这样才能得到最后的结果。因此此时各个分区中预聚合(group by)后的数据会根据该组的<n1,n2>值再利用hash join进行分区,使得对<n1,n2>的hash值相同的数据组发送到同一分区中(此时数据量经过预聚合后已经变小了,所以就可以减少分区数,图上减少为2个)。这样在每个分区中每个组内的数据都是包含了所有<n1,n2> Hash值相等的结果,也即可以得到SQL查询语句中group by的最终结果(相当于原查询语句中,group by n1, n2 后所有应该进入同一分组中的数据)。
四、局部TopN
除了上述过程可以并行外,还有其它操作也可以并行吗?由于原SQL语句有order by limit
操作,所以其实还可以在全局聚合后执行局部的order by limit
操作。由于order by
操作是对得到的最终结果进行order by,因此,这里就可以在每个分区进行全局聚合后,对得到的count(1) as c
进行order by limit操作,得到局部的Top100的结果。这样同样减少了网络间传输的数据量,同时也体现了并行执行的特点。
五、全局TopN
最后,就是将所有的数据进行汇总,然后执行全局的order by limit 操作,就可以得到最终的结果。
整体的分布式并行执行计划流程图如下图所示:
各部分的具体操作就如上述流程介绍中所示。
对上述流程的总结如下:
- T1和T2使⽤Hash join,此时需要按照id的值分别将T1和T2分散到不同的进程,但是相同的id会散列到相同的进程,这样每⼀个Join之后是全部数据的⼀部分
- T1与T2Join之后的结果数据再与T3表进⾏Join,此时T3表采⽤Broadcast⽅式把⾃⼰全部数据(id列)⼴播到需要的节点上
- T1,T2,T3Join之后再根据Group by执⾏本地的预聚合,每⼀个节点的预聚合结果只是最终结果的⼀部分(不同的节点可能存在相同的group by的值),需要再进⾏⼀次全局的聚合。
- 全局的聚合同样需要并⾏,则根据聚合列进⾏Hash分散到不同的节点执⾏Merge运算(其实仍然是⼀次聚合运算),⼀般情况下为了较少数据的⽹络传输, 会选择之前本地聚合节点做全局聚合⼯作。
- 通过全局聚合之后,相同的key只存在于⼀个节点,然后对于每⼀个节点进⾏排序和TopN计算,最终将每⼀个全局聚合节点的结果返回进⾏合并、排序、limit计算,返回结果给⽤户。