Impala可以对Hadoop中大多数格式的文件进行查询。它能通过create table和insert的方式将一部分格式的数据加载到table中,但值得注意的是,有一些格式的数据它是无法写入的(write to)。对于Impala无法写入的数据格式,我们只能通过Hive建表,通过Hive进行数据的写入,然后使用Impala来对这些保存好的数据执行查询操作。
文件类型 |
文件格式 |
压缩编码 |
能否CREATE ? |
能否INSERT ? |
Parquet |
结构化 |
Snappy GZIP |
能 |
能 |
Text |
非结构化 |
LZO |
能。 如果建表时没有指定存储类型,默认采用未压缩的text,字段由ASCII编码的0x01字符串分割。 |
能。 如果使用了LZO压缩,则只能通过Hive建表和插入数据。 |
Avro |
结构化 |
Snappy GZIP Deflate BZIP2 |
在Impala 1.4.0 或者更高的版本上支持,之前的版本只能通过Hive来建表。 |
不能。 只能通过LOAD DATA的方式将已经转换好格式的数据加载进去,或者使用Hive来插入数据。 |
RCFile |
结构化 |
Snappy GZIP Deflate BZIP2 |
能 |
不能。 只能通过LOAD DATA的方式将已经转换好格式的数据加载进去,或者使用Hive来插入数据。 |
SequenceFile |
结构化 |
Snappy GZIP deflate BZIP2 |
能 |
不能。 只能通过LOAD DATA的方式将已经转换好格式的数据加载进去,或者使用Hive来插入数据。 |
Impala支持以下压缩编码:
下面这个图表示了Impala在Hadoop集群中所处的位置:
Impala由以下的组件组成:
Impala执行的查询有以下几个步骤:
Impala和Hive、HDFS、HBase等工具是统一部署在一个Hadoop平台上的。Impala主要由Impalad,State Store和CLI三部分组成。
(1)Impalad
负责协调客户端提交的查询的执行
包含Query Planner、Query Coordinator和Query Exec Engine三个模块。
与HDFS的数据节点(HDFS DN)运行在同一节点上。
给其他Impalad分配任务以及收集其他Impalad的执行结果进行汇总。
Impalad也会执行其他Impalad给其分配的任务,主要就是对本地HDFS和HBase里的部分数据进行操作。
(2)State Store
会创建一个statestored进程。
负责收集分布在集群中各个Impalad进程的资源信息,用于查询调度。
(3)CLI
给用户提供查询使用的命令行工具。
还提供了Hue、JDBC及ODBC的使用接口。
说明:Impala中的元数据直接存储在Hive中。Impala采用与Hive相同的元数据、SQL语法、ODBC驱动程序和用户接口,从而使得在一个Hadoop平台上,可以统一部署Hive和Impala等分析工具,同时支持批处理和实时查询。
Impalad分为Java前端与C++处理后端,接受客户端连接的Impalad即作为这次查询的Coordinator,Coordinator通过JNI调用Java前端对用户的查询SQL进行分析生成执行计划树,不同的操作对应不用的PlanNode, 如:SelectNode, ScanNode, SortNode, AggregationNode, HashJoinNode等等。
执行计划树的每个原子操作由一个PlanFragment表示,通常一条查询语句由多个Plan Fragment组成, Plan Fragment 0表示执行树的根,汇聚结果返回给用户,执行树的叶子结点一般是Scan操作,分布式并行执行。
Java前端产生的执行计划树以Thrift数据格式返回给Impala C++后端(Coordinator)(执行计划分为多个阶段,每一个阶段叫做一个PlanFragment,每一个PlanFragment在执行时可以由多个Impalad实例并行执行(有些PlanFragment只能由一个Impalad实例执行,如聚合操作),整个执行计划为一执行计划树),由Coordinator根据执行计划,数据存储信息(Impala通过libhdfs与HDFS进行交互。通过hdfsGetHosts方法获得文件数据块所在节点的位置信息),通过调度器(现在只有simple-scheduler, 使用round-robin算法)Coordinator::Exec对生成的执行计划树分配给相应的后端执行器Impalad执行(查询会使用LLVM进行代码生成,编译,执行。对于使用LLVM如何提高性能这里有说明),通过调用GetNext()方法获取计算结果,如果是insert语句,则将计算结果通过libhdfs写回HDFS当所有输入数据被消耗光,执行结束,之后注销此次查询服务。
Impala执行查询的具体过程:
第0步,当用户提交查询前,Impala先创建一个负责协调客户端提交的查询的Impalad进程,该进程会向Impala State Store提交注册订阅信息,State Store会创建一个statestored进程,statestored进程通过创建多个线程来处理Impalad的注册订阅信息。
第1步,用户通过CLI客户端提交一个查询到impalad进程,Impalad的Query Planner对SQL语句进行解析,生成解析树;然后,Planner把这个查询的解析树变成若干PlanFragment,发送到Query Coordinator.
第2步,Coordinator通过从MySQL元数据库中获取元数据,从HDFS的名称节点中获取数据地址,以得到存储这个查询相关数据的所有数据节点。
第3步,Coordinator初始化相应impalad上的任务执行,即把查询任务分配给所有存储这个查询相关数据的数据节点。
第4步,Query Executor通过流式交换中间输出,并由Query Coordinator汇聚来自各个impalad的结果。
第5步,Coordinator把汇总后的结果返回给CLI客户端。
Impala自称数据查询效率比Hive快几倍甚至数十倍,它之所以这么快的原因大致有以下几点:
数据存储:使用相同的存储数据池都支持把数据存储于HDFS, HBase。
元数据:两者使用相同的元数据。
SQL解释处理:比较相似都是通过词法分析生成执行计划。
执行计划:
Hive: 依赖于MapReduce执行框架,执行计划分成map->shuffle->reduce->map->shuffle->reduce…的模型。如果一个Query会被编译成多轮MapReduce,则会有更多的写中间结果。由于MapReduce执行框架本身的特点,过多的中间过程会增加整个Query的执行时间。
Impala: 把执行计划表现为一棵完整的执行计划树,可以更自然地分发执行计划到各个Impalad执行查询,而不用像Hive那样把它组合成管道型的map->reduce模式,以此保证Impala有更好的并发性和避免不必要的中间sort与shuffle。
数据流:
Hive: 采用推的方式,每一个计算节点计算完成后将数据主动推给后续节点。
Impala: 采用拉的方式,后续节点通过getNext主动向前面节点要数据,以此方式数据可以流式的返回给客户端,且只要有1条数据被处理完,就可以立即展现出来,而不用等到全部处理完成,更符合SQL交互式查询使用。
内存使用:
Hive: 在执行过程中如果内存放不下所有数据,则会使用外存,以保证Query能顺序执行完。每一轮MapReduce结束,中间结果也会写入HDFS中,同样由于MapReduce执行架构的特性,shuffle过程也会有写本地磁盘的操作。
Impala: 在遇到内存放不下数据时,当前版本1.0.1是直接返回错误,而不会利用外存,以后版本应该会进行改进。这使用得Impala目前处理Query会受到一定的限制,最好还是与Hive配合使用。Impala在多个阶段之间利用网络传输数据,在执行过程不会有写磁盘的操作(insert除外)。
调度:
Hive: 任务调度依赖于Hadoop的调度策略。
Impala: 调度由自己完成,目前只有一种调度器simple-schedule,它会尽量满足数据的局部性,扫描数据的进程尽量靠近数据本身所在的物理机器。调度器目前还比较简单,在SimpleScheduler::GetBackend中可以看到,现在还没有考虑负载,网络IO状况等因素进行调度。但目前Impala已经有对执行过程的性能统计分析,应该以后版本会利用这些统计信息进行调度吧。
容错:
Hive: 依赖于Hadoop的容错能力。
Impala: 在查询过程中,没有容错逻辑,如果在执行过程中发生故障,则直接返回错误(这与Impala的设计有关,因为Impala定位于实时查询,一次查询失败,再查一次就好了,再查一次的成本很低)。但从整体来看,Impala是能很好的容错,所有的Impalad是对等的结构,用户可以向任何一个Impalad提交查询,如果一个Impalad失效,其上正在运行的所有Query都将失败,但用户可以重新提交查询由其它Impalad代替执行,不会影响服务。对于State Store目前只有一个,但当State Store失效,也不会影响服务,每个Impalad都缓存了State Store的信息,只是不能再更新集群状态,有可能会把执行任务分配给已经失效的Impalad执行,导致本次Query失败。
适用面:
Hive: 复杂的批处理查询任务,数据转换任务。
Impala:实时数据分析,因为不支持UDF,能处理的问题域有一定的限制,与Hive配合使用,对Hive的结果数据集进行实时分析。
Impala的核心组件是运行在各个节点上面的impalad这个守护进程(Impala daemon),它负责读写数据文件,接收从impala-shell、Hue、JDBC、ODBC等接口发送的查询语句,并行化查询语句和分发工作任务到Impala集群的各个节点上,同时负责将本地计算好的查询结果发送给协调器节点(coordinator node)。
你可以向运行在任意节点的Impala daemon提交查询,这个节点将会作为这个查询的协调器(coordinator node),其他节点将会传输部分结果集给这个协调器节点。由这个协调器节点构建最终的结果集。在做实验或者测试的时候为了方便,我们往往连接到同一个Impala daemon来执行查询,但是在生产环境运行产品级的应用时,我们应该循环(按顺序)的在不同节点上面提交查询,这样才能使得集群的负载达到均衡。
Impala daemon不间断的跟statestore进行通信交流,从而确认哪个节点是健康的能接收新的工作任务。它同时接收catalogd daemon(从Impala 1.2之后支持)传来的广播消息来更新元数据信息,当集群中的任意节点create、alter、drop任意对象、或者执行INSERT、LOAD DATA的时候触发广播消息。
Impala Statestore检查集群各个节点上Impala daemon的健康状态,同时不间断地将结果反馈给各个Impala daemon。这个服务的物理进程名称是statestored,在整个集群中我们仅需要一个这样的进程即可。如果某个Impala节点由于硬件错误、软件错误或者其他原因导致离线,statestore就会通知其他的节点,避免其他节点再向这个离线的节点发送请求。
由于statestore是当集群节点有问题的时候起通知作用,所以它对Impala集群并不是有关键影响的。如果statestore没有运行或者运行失败,其他节点和分布式任务会照常运行,只是说当节点掉线的时候集群会变得没那么健壮。当statestore恢复正常运行时,它就又开始与其他节点通信并进行监控。
Imppalla catalog服务将SQL语句做出的元数据变化通知给集群的各个节点,catalog服务的物理进程名称是catalogd,在整个集群中仅需要一个这样的进程。由于它的请求会跟statestore daemon交互,所以最好让statestored和catalogd这两个进程在同一节点上。
Impala 1.2中加入的catalog服务减少了REFRESH和INVALIDATE METADATA语句的使用。在之前的版本中,当在某个节点上执行了CREATE DATABASE、DROP DATABASE、CREATE TABLE、ALTER TABLE、或者DROP TABLE语句之后,需要在其它的各个节点上执行命令INVALIDATE METADATA来确保元数据信息的更新。同样的,当你在某个节点上执行了INSERT语句,在其它节点上执行查询时就得先执行REFRESH table_name这个操作,这样才能识别到新增的数据文件。需要注意的是,通过Impala执行的操作带来的元数据变化,有了catalog就不需要再执行REFRESH和INVALIDATE METADATA,但如果是通过Hive进行的建表、加载数据,则仍然需要执行REFRESH和INVALIDATE METADATA来通知Impala更新元数据信息。
以下测试环境以及测试数据来自Impala官方博客。
所有的测试都在同一个集群上面运行,保证硬件环境的一致性。集群有21个节点,每个节点的配置都一样:
Impala on Parquet运行效率最高,比其后的Shark 0.9.2平均快了5倍。
我们同时测试了单用户和10个用户做对比,测试中Impala更好的体现了其性能优势,比其后的工具快了9.5倍。
下面的CPU效率解释了为什么Impala能够做到低延迟和高吞吐量,绝大多数的性能和并发性都在于查询引擎自身的CPU利用效率。
当数据表的统计信息可用的时候,Impala能够更好的对查询进行优化,通过统计信息它能更清楚的知道数据的分布情况,并有效地并行处理和分发工作任务。
在之前,Impala依赖于Hive的机制产生mapreduce任务来收集统计信息。为了更好的用户体验和可靠性,Impala在1.2.2及其之后的版本中实现了自己的COMPUTE STATS语法来进行信息统计,结合使用SHOW TABLE STATS和SHOW COLUMN STATS这两种语法。
用Impala统计表和字段信息的例子如下:
[localhost:21000] > show table stats store;
+-------+--------+--------+--------+
| #Rows | #Files | Size | Format |
+-------+--------+--------+--------+
| -1 | 1 | 3.08KB | TEXT |
+-------+--------+--------+--------+
Returned 1 row(s) in 0.03s
[localhost:21000] > show column stats store;
+--------------------+-----------+------------------+--------+----------+----------+
| Column | Type | #Distinct Values | #Nulls | Max Size | Avg Size |
+--------------------+-----------+------------------+--------+----------+----------+
| s_store_sk | INT | -1 | -1 | 4 | 4 |
| s_store_id | STRING | -1 | -1 | -1 | -1 |
| s_rec_start_date | TIMESTAMP | -1 | -1 | 16 | 16 |
| s_rec_end_date | TIMESTAMP | -1 | -1 | 16 | 16 |
| s_closed_date_sk | INT | -1 | -1 | 4 | 4 |
| s_store_name | STRING | -1 | -1 | -1 | -1 |
| s_number_employees | INT | -1 | -1 | 4 | 4 |
| s_floor_space | INT | -1 | -1 | 4 | 4 |
| s_hours | STRING | -1 | -1 | -1 | -1 |
| s_manager | STRING | -1 | -1 | -1 | -1 |
| s_market_id | INT | -1 | -1 | 4 | 4 |
| s_geography_class | STRING | -1 | -1 | -1 | -1 |
| s_market_desc | STRING | -1 | -1 | -1 | -1 |
| s_market_manager | STRING | -1 | -1 | -1 | -1 |
| s_division_id | INT | -1 | -1 | 4 | 4 |
| s_division_name | STRING | -1 | -1 | -1 | -1 |
| s_company_id | INT | -1 | -1 | 4 | 4 |
| s_company_name | STRING | -1 | -1 | -1 | -1 |
| s_street_number | STRING | -1 | -1 | -1 | -1 |
| s_street_name | STRING | -1 | -1 | -1 | -1 |
| s_street_type | STRING | -1 | -1 | -1 | -1 |
| s_suite_number | STRING | -1 | -1 | -1 | -1 |
| s_city | STRING | -1 | -1 | -1 | -1 |
| s_county | STRING | -1 | -1 | -1 | -1 |
| s_state | STRING | -1 | -1 | -1 | -1 |
| s_zip | STRING | -1 | -1 | -1 | -1 |
| s_country | STRING | -1 | -1 | -1 | -1 |
| s_gmt_offset | FLOAT | -1 | -1 | 4 | 4 |
| s_tax_precentage | FLOAT | -1 | -1 | 4 | 4 |
+--------------------+-----------+------------------+--------+----------+----------+
Returned 29 row(s) in 0.04s
[localhost:21000] > compute stats store;
+------------------------------------------+
| summary |
+------------------------------------------+
| Updated 1 partition(s) and 29 column(s). |
+------------------------------------------+
Returned 1 row(s) in 1.88s
[localhost:21000] > show table stats store;
+-------+--------+--------+--------+
| #Rows | #Files | Size | Format |
+-------+--------+--------+--------+
| 12 | 1 | 3.08KB | TEXT |
+-------+--------+--------+--------+
Returned 1 row(s) in 0.02s
[localhost:21000] > show column stats store;
+--------------------+-----------+------------------+--------+----------+----------------+
| Column | Type | #Distinct Values | #Nulls | Max Size | Avg Size |
+--------------------+-----------+------------------+--------+----------+----------------+
| s_store_sk | INT | 12 | -1 | 4 | 4 |
| s_store_id | STRING | 6 | -1 | 16 | 16 |
| s_rec_start_date | TIMESTAMP | 4 | -1 | 16 | 16 |
| s_rec_end_date | TIMESTAMP | 3 | -1 | 16 | 16 |
| s_closed_date_sk | INT | 3 | -1 | 4 | 4 |
| s_store_name | STRING | 8 | -1 | 5 | 4.25 |
| s_number_employees | INT | 9 | -1 | 4 | 4 |
| s_floor_space | INT | 10 | -1 | 4 | 4 |
| s_hours | STRING | 2 | -1 | 8 | 7.08330011367797 |
| s_manager | STRING | 7 | -1 | 15 | 12 |
| s_market_id | INT | 7 | -1 | 4 | 4 |
| s_geography_class | STRING | 1 | -1 | 7 | 7 |
| s_market_desc | STRING | 10 | -1 | 94 | 55.5 |
| s_market_manager | STRING | 7 | -1 | 16 | 14 |
| s_division_id | INT | 1 | -1 | 4 | 4 |
| s_division_name | STRING | 1 | -1 | 7 | 7 |
| s_company_id | INT | 1 | -1 | 4 | 4 |
| s_company_name | STRING | 1 | -1 | 7 | 7 |
| s_street_number | STRING | 9 | -1 | 3 | 2.83330011367797 |
| s_street_name | STRING | 12 | -1 | 11 | 6.58330011367797 |
| s_street_type | STRING | 8 | -1 | 9 | 4.83330011367797 |
| s_suite_number | STRING | 11 | -1 | 9 | 8.25 |
| s_city | STRING | 2 | -1 | 8 | 6.5 |
| s_county | STRING | 1 | -1 | 17 | 17 |
| s_state | STRING | 1 | -1 | 2 | 2 |
| s_zip | STRING | 2 | -1 | 5 | 5 |
| s_country | STRING | 1 | -1 | 13 | 13 |
| s_gmt_offset | FLOAT | 1 | -1 | 4 | 4 |
| s_tax_precentage | FLOAT | 5 | -1 | 4 | 4 |
+--------------------+-----------+------------------+--------+----------+----------------
Returned 29 row(s) in 0.04s
当在Impala上执行查询的时候,会多个datanode上分布式地读取block数据,如果Impala拥有更多的block信息,将会更高效的获取数据并处理。可以通过以下步骤来启用block location跟踪:
dfs.datanode.hdfs-blocks-metadata.enabled true
对大量数据计算校验和(checksum)会带来巨大的时间损耗,因此用本地库(native library)来执行校验和会带来性能上的提升。在Impala中可以采用以下方式来启用本地校验:
Short-circuit read意味着会从datanode的本地文件系统直接读取数据,而不用首先与datanode进行通信,这肯定会提高性能。你必须使用Cloudera CDH 4.2或更高的版本来达到快速的short-circuit读取数据。可以通过以下步骤来进行设置:
dfs.client.read.shortcircuit true dfs.domain.socket.path /var/run/hadoop-hdfs/dn._PORT dfs.client.file-block-storage-locations.timeout 3000
事实证明更多的Impala节点会显著地提高性能,这跟Hadoop使用更多的datanode提高性能是一样的。拥有更多的节点会让数据分散到更多的节点上,在执行查询的时候能够分发更多的任务并行执行,从而提高整体执行性能。
在启动Impala守护进程的时候可以使用-mem_limits参数来限制内存消耗,这个参数只对查询(query)进行内存限制。
如果数据集太大以至于超出了机器的可用内存,这个查询将会失败。Impala对内存的使用并不直接根据数据集的大小决定,它是根据查询的类型而变化的。聚合查询需要的内存跟group之后的数据量一样,连接查询(join)需要的内存量等价于除开最大表之外的所有表的总大小。
如果你使用的是Cloudera Manager,可以使用Cloudera Manager的设备控制器(cgroups)机制来实现资源隔离(resource isolation)。更多信息请阅读Cloudera Manager文档中对resource isolation的描述。