Impala如何执行查询
下面这个图表示impala在Hadoop集群中所处的位置:
Impala由以下组件组成:
Impala执行的查询有以下步骤:
下图展示了Impala的系统架构和查询的执行流程。从图中可以看出,Impala自身包含三个模块:Impalad、Statestore和Catalog,并提供CLI(impala shell)、ODBC、JDBC数据访问接口,除此之外它还依赖Hive Metastore和HDFS。
从Impala的各个模块可以看出,主要查询处理都是在Impalad进程中完成,StateStore和Catalog帮助Impalad完成元数据的管理和负载监控等工作,其实更进一步可以将Query Planner和Query Coordinator模块从Impalad移出单独的作为一个入口服务存在,而Impalad仅负责数据读写和子任务的执行。
客户端可以随便连接到任意一个impalad实例,被连接的impalad实例将充当本次查询的协调者(Ordinator),将查询分发给集群内的其它impalad实例进行并行计算。当所有计算完毕时,其它各个impalad实例将会把各自的计算结果发送给充当 Ordinator的impalad实例,由这个Ordinator实例把结果返回给客户端。每个impalad进程可以处理多个并发请求。在Impalad进行执行优化的时候根本原则是尽可能的数据本地读取,减少网络通信,毕竟在不考虑内存缓存数据的情况下,从远端读取数据需要磁盘->内存->网卡->本地网卡->本地内存的过程,而从本地读取数据仅需要本地磁盘->本地内存的过程,可以看出,在相同的硬件结构下,读取其他节点数据始终慢于本地磁盘的数据读取速度。
Impala执行流程如下:
1)客户端通过ODBC、JDBC、impala shell向impala集群中的任意节点发送sql语句,这个节点的impalad实例作为这个查询的协调器(coordinator)。
2)Impala解析和分析这个查询语句来决定集群中的哪个impalad实例来执行某个任务。
3)HDFS和HBase给本地的impala的实例提供数据访问。
4)各个impalad向协调器impalad返回数据,然后由协调器impalad向client发送结果集。
5) 执行SQL结束以后,将结果返回给Query Coordinator
6) 再由Query Coordinator 将结果返回给Client
Impalad
Imapalad负责接受用户的查询请求,也意味着用户的可以将请求发送给任意一个Impalad进程,该进程在本次查询充当协调者(coordinator)的作用,生成执行计划并且分发到其它的Impalad进程执行,最终汇集结果返回给用户,并且对于当前Impalad和其它Impalad进程而言,他们同时也是本次查询的执行者,完成数据读取、物理算子的执行并将结果返回给协调者Impalad。这种无中心查询节点的设计能够最大程度的保证容错性并且很容易做负载均衡。
正如图中展示的一样,通常每一个HDFS的DataNode上部署一个Impalad进程,由于HDFS存储数据通常是多副本的,所以这样的部署可以保证数据的本地性,查询尽可能的从本地磁盘读取数据而非网络,从这点可以推断出Impalad对于本地数据的读取应该是通过直接读本地文件的方式,而非调用HDFS的接口。为了实现查询分割的子任务可以做到尽可能的本地数据读取,Impalad需要从Metastore中获取表的数据存储路径,并且从NameNode中获取每一个文件的数据块分布。
Impalad服务由三个模块组成:Query Planner、Query Coordinator和Query Executor,前两个模块组成前端,负责接收SQL查询请求,解析SQL并转换成执行计划,交由后端执行,语法方面它既支持基本的操作(select、project、join、group by、filter、order by、limit等),也支持关联子查询和非关联子查询,支持各种outer-join和窗口函数,这部分按照通用的解析流程分为查询解析->语法分析->查询优化,最终生成物理执行计划。对于Query
Planner而言,它生成物理执行计划的过程分成两步,首先生成单节点执行计划,然后再根据它得到分区可并行的执行计划。前者是根据类似于RDBMS进行执行优化的过程,决定join顺序,对join执行谓词下推,根据关系运算公式进行一些转换等,这个执行计划的生成过程依赖于Impala表和分区的统计信息。第二步是根据上一步生成的单节点执行计划得到分布式执行计划,可参照Dremel的执行过程。在上一步已经决定了join的顺序,这一步需要决定join的策略:使用hash join还是broadcast join,前者一般针对两个大表,根据join键进行hash分区以使得相同的id散列到相同的节点上进行join,后者通过广播整个小表到所有节点,Impala选择的策略是依赖于网络通信的最小化。对于聚合操作,通常需要首先在每个节点上执行预聚合,然后再根据聚合键的值进行hash将结果散列到多个节点再进行一次merge,最终在coordinator节点上进行最终的合并(只需要合并就可以了),当然对于非group by的聚合运算,则可以将每一个节点预聚合的结果交给一个节点进行merge。sort和top-n的运算和这个类似。
State Store
StateStore服务完成两个工作:消息订阅服务和状态监测功能。Catalog中的元数据就是通过StateStore服务进行广播分发的,它实现了一个Pub-Sub服务,Impalad可以注册它们希望获得的事件类型,Statestore会周期性的发送两种类型的消息给Impalad进程,一种为该Impalad注册监听的事件的更新,基于版本的增量更新(只通知上次成功更新之后的变化)可以减小每次通信的消息大小;另一种消息为心跳信息,StateStore负责统计每一个Impalad进程的状态,Impalad可以据此了解其余Impalad进程的状态,用于判断分配查询任务到哪些节点。由于周期性的推送并且每一个节点的推送频率不一致可能会导致每一个Impalad进程获得的状态不一致,由于每一次查询只依赖于协调者Impalad进程获取的状态进行任务的分配,而不需要多个进程进行再次的协调,因此并不需要保证所有的Impalad状态是一致的。另外,StateStore进程是单点的,并且不会持久化任何数据到磁盘,如果服务挂掉,Impalad则依赖于上一次获得元数据状态进行任务分配,官方并没有提供可靠性部署的方案,通常可以使用DNS方式绑定多个服务以应对单个服务挂掉的情况。
跟踪集群中的Impalad的健康状态及位置信息,由statestored进程表示,它通过创建多个线程来处理Impalad的注册订阅和与各Impalad保持心跳连接,各Impalad都会缓存一份State Store中的信息,当State Store离线后(Impalad发现State Store处于离线时,会进入recovery模式,反复注册,当State Store重新加入集群后,自动恢复正常,更新缓存数据)因为Impalad有State Store的缓存仍然可以工作,但会因为有些Impalad失效了,而已缓存数据无法更新,导致把执行计划分配给了失效的Impalad,导致查询失败。
用于协调各个运行impalad的实例之间的信息关系,Impala正是通过这些信息去定位查询请求所要的数据。换句话说,state store的作用主要为跟踪各个impalad实例的位置和状态,让各个impalad实例以集群的方式运行起来。
与 HDFS的NameNode不一样,虽然State Store一般只安装一份,但一旦State Store挂掉了,各个impalad实例却仍然会保持集群的方式处理查询请求,只是无法将各自的状态更新到State Store中,如果这个时候新加入一个impalad实例,则新加入的impalad实例不为现有集群中的其他impalad实例所识别(事实上,经笔者测试,如果impalad启动在statestored之后,根本无法正常启动,因为impalad启动时是需要指定statestored的主机信息的)。然而,State Store一旦重启,则所有State Store所服务的各个impalad实例(包括state store挂掉期间新加入的impalad实例)的信息(由impalad实例发给state store)都会进行重建。
Impala Catalog
Catalog服务提供了元数据的服务,它以单点的形式存在,它既可以从外部系统(例如HDFS NameNode和Hive Metastore)拉取元数据,也负责在Impala中执行的DDL语句提交到Metatstore,由于Impala没有update/delete操作,所以它不需要对HDFS做任何修改。之前我们介绍过有两种方式向Impala中导入数据(DDL)——通过hive或者impala,如果通过hive则改变的是Hive
metastore的状态,此时需要通过在Impala中执行REFRESH以通知元数据的更新,而如果在impala中操作则Impalad会将该更新操作通知Catalog,后者通过广播的方式通知其它的Impalad进程。默认情况下Catalog是异步加载元数据的,因此查询可能需要等待元数据加载完成之后才能进行(第一次加载)。该服务的存在将元数据从Impalad进程中独立出来,可以简化Impalad的实现,降低Impalad之间的耦合。
Imppalla catalog服务将SQL语句做出的元数据变化通知给集群的各个节点,catalog服务的物理进程名称是catalogd,在整个集群中仅需要一个这样的进程。由于它的请求会跟statestore daemon交互,所以最好让statestored和catalogd这两个进程在同一节点上。
catalog服务减少了REFRESH和INVALIDATE METADATA语句的使用。在之前的版本中,当在某个节点上执行了CREATE DATABASE、DROP DATABASE、CREATE TABLE、ALTER TABLE、或者DROP TABLE语句之后,需要在其它的各个节点上执行命令INVALIDATE METADATA来确保元数据信息的更新。同样的,当你在某个节点上执行了INSERT语句,在其它节点上执行查询时就得先执行REFRESH table_name这个操作,这样才能识别到新增的数据文件。
CLI(Impala shell)
提供给用户查询使用的命令行工具(Impala Shell使用python实现),同时Impala还提供了Hue,JDBC, ODBC使用接口。该客户端工具提供一个交互接口,供使用者发起数据查询或管理任务,比如连接到impalad。这些查询请求会传给ODBC这个标准查询接口。
查询执行
impalad分为frontend和backend两个层次, frondend用java实现(通过JNI嵌入impalad), 负责查询计划生成, 而backend用C++实现, 负责查询执行。
frontend生成查询计划分为两个阶段:
(1)生成单机查询计划,单机执行计划与关系数据库执行计划相同,所用查询优化方法也类似。
(2)生成分布式查询计划。 根据单机执行计划, 生成真正可执行的分布式执行计划,降低数据移动, 尽量把数据和计算放在一起。
上图是SQL查询例子, 该SQL的目标是在三表join的基础上算聚集, 并按照聚集列排序取topN。 impala的查询优化器支持代价模型: 利用表和分区的cardinality,每列的distinct值个数等统计数据, impala可估算执行计划代价, 并生成较优的执行计划。 上图左边是frontend查询优化器生成的单机查询计划, 与传统关系数据库不同, 单机查询计划不能直接执行,
必须转换成如图右半部分所示的分布式查询计划。 该分布式查询计划共分成6个segment(图中彩色无边框圆角矩形), 每个segment是可以被单台服务器独立执行的计划子树。
impala支持两种分布式join方式, 表广播和哈希重分布:表广播方式保持一个表的数据不动, 将另一个表广播到所有相关节点(图中t3); 哈希重分布的原理是根据join字段哈希值重新分布两张表数据(譬如图中t1和t2)。分布式计划中的聚集函数分拆为两个阶段执行。第一步针对本地数据进行分组聚合(Pre-AGG)以降低数据量, 并进行数据重分步, 第二步, 进一步汇总之前的聚集结果(mergeAgg)计算出最终结果。
与聚集函数类似, topN也是分为两个阶段执行,
(1)本地排序取topN,以降低数据量;
(2)merge sort得到最终topN结果。
Backend从frontend接收plan segment并执行, 执行性能非常关键,impala采取的查询性能优化措施有:
向量执行。 一次getNext处理一批记录, 多个操作符可以做pipeline。
LLVM编译执行, CPU密集型查询效率提升5倍以上。
IO本地化。 利用HDFS short-circuit local read功能,实现本地文件读取
Parquet列存,相比其他格式性能最高提升5倍。
资源管理
impala通常与MR等离线任务运行在一个集群上, 通过YARN统一管理资源, 如何同时满足交互式查询和离线查询两种需求具有较大挑战性。 YARN通过全局唯一的Resource Mananger调度资源, 好处是RM拥有整个集群全局信息,能做出更好调度决策, 缺点是资源分配的性能不足。 Impala每个查询都需要分配资源, 当每秒查询数上千时, YARN资源分配的响应时间变的很长, 影响到查询性能。 目前通过两个措施解决这个问题:
(1)引入快速、非集中式的查询准入机制, 控制查询并发度。
(2)LLAM(low latency application master)通过缓存资源, 批量分配,增量分配等方式实现降低资源分配延时。