文章来源:畅游DT时代(微信公众号)
作者:中国联通网研院网优部李珂
一. 引言
Hadoop生态中的NoSQL数据分析三剑客Hive、HBase、Impala分别在海量批处理分析、大数据列式存储、实时交互式分析各有所长。尤其是Impala,自从加入Hadoop大家庭以来,凭借其各个特点鲜明的优点博取了广大大数据分析人员的欢心。
Impala通过主节点生成执行计划树并分发执行计划至各节点并行执行的拉式获取数据的工作方式,替代了Hadoop中传统的MapReduce推式获取数据的工作方式,计算的中间结果不写入磁盘,及时通过网络以stream的方式传递,交互性和实时性更强;Impala不花费额外的精力管理元数据,而是使用Hive的Metastore进行元数据管理,能够直接访问存储在Hadoop的HDFS和HBase中的PB级大数据;Impala采用块的方式将元数据加载到内存进行运算,相比Hive、HBase而言运算性能有了较大的提升;Impala提供SQL语义,相比HBase对于用户而言使用方便快捷并且简单实用,无需其他编程语言,只需使用SQL语句即可完成复杂的数据分析任务;Impala还继承了Hadoop的灵活性、伸缩性和经济性,具有分布式本地化处理的特性以避免网络瓶颈。
说了Impala这么多优点,难道它真是一点缺点没有的一款“完美”的分析工具吗?
非也!在一年以上的Impala海量数据分析、web应用开发实战经验中,Impala也陆陆续续暴露出了一些致命的问题:
“啃内存”一族,对于内存的依赖过于严重,内存溢出直接导致技术任务的失败。
“阉割版”SQL——不支持UDF,不支持UPDATE/DELTE操作,不支持同一SELECT中多个DISTINCT。
权限角色等安全机制的实现需要额外的安全组件,管理配置较复杂。
但是瑕不掩瑜,通过研究Impala的工作机制及错误日志,我们发现Impala的上述缺陷都可以通过一些优化方案和替代措施得以很好的解决。通过合理的优化将Impala的性能最大化的挖掘,利用Hadoop生态开源的优势与商业化的NewSQL数据库相对抗,在节省软件成本的同时获取不逊于NewSQL数据库的分析性能。
由于篇幅有限,今天我们先来谈一谈Impala内存溢出问题的优化。
SQLOperations that Spill to Disk
在介绍Impala内存优化技巧之前,我们先来看一下Impala官方针对内存溢出问题所作出的努力。Impala从Impala 1.4 for CDH4、CDH5.1版本开始新增了“SQL Operations that Spill to Disk”功能,即当Impala内存运算溢出时转移到磁盘进行运算,虽然在计算时需要占用临时的磁盘空间,增加了磁盘I/O,导致运算时间远高于纯内存运算,但至少解决了内存溢出时分析任务直接失败这一“0”容错的致命问题。
该功能的开启也非常简单,只需在impalashell中执行“setDISABLE_UNSAFE_SPILLS=0”或者“setDISABLE_UNSAFE_SPILLS=FALSE”命令即可。当设置DISABLE_UNSAFE_SPILLS参数的值为0(FALSE)时,内存运算濒临溢出时转为磁盘运算;设置为1(TRUE)时,当内存溢出时直接报内存溢出“Memory limit exceeded”错误。下图是笔者在Impala 2.2.0 for CDH5.4.1版本对此功能进行测试的截图:
可以看到,在开启DISABLE_UNSAFE_SPILLS参数时,禁用了内存溢出写入磁盘的功能,查询任务失败;但关闭DISABLE_UNSAFE_SPILLS参数时,开启了内存溢出写入磁盘的功能,虽然查询任务用时1225秒,但完成了该查询任务。
在Cloudera manager的Impala查询监控界面中,我们可以看到两次查询所消耗的资源情况:
在测试中,笔者将集群Impala节点的内存限制为3.5GB,可以看到该查询语句累积内存使用峰值为3.6GB,产生“Memory limit exceeded”错误。而开启了“SQL Operations that Spill to Disk”功能后,使用内存峰值改变为2.3GB,计算所需多余的内存都转为至磁盘进行运算(在集群三个DataNode节点各写入了10G的临时数据),避免了“Memory limit exceeded”报错,成功完成了查询。
内存溢出的问题仅凭借这一招就解决了?其实不然!
实际上,Impala官方也承认该功能存在很多限制,并且还建议用户尽可能的规避触发“Spill to Disk”功能。该功能目前存在的限制包括:
不是所有的SQL语句都能触发,例如union关键字还是会触发内存溢出错误;
各个节点的内存峰值限制不能过低,低于运算所需分配给各个节点的最小内存;
运算explain输出的各个节点预估内存不能过分高于各个节点的实际物理内存;
当触发“Spill to Disk”功能时有其他并发查询,仍会触发内存溢出错误;
对磁盘的空间有一定的要求,磁盘运算的数据会写入到impala各个节点的临时目录下,增加了磁盘I/O,并且会引发不可控制的磁盘占用。
无论是官方建议,还是实际使用经验而言,“Spillto Disk”不是一个长远之计。还是应该通过优化SQL查询、修改系统参数、提高硬件配置等方式来解决内存溢出的发生。
优化SQL查询
SQL优化不仅是一个成熟的分析、开发团队需要具备的基本素质,更能最大限度的挖掘Impala集群的性能,在数据分析时达到事半功倍的效果。下面介绍一下Impala SQL优化的四大法宝:
法宝一:Compute Stats
COMPUTE STATS(DDL)完成对表、分区、列的数据量和数据分布信息的搜集,搜集到的相关信息会被存储在元数据库中。看似只是Impala中一条获取表的统计信息的简单语句,但在整个分析任务调度过程中却起着相当重要的作用。该语句获取的统计信息不仅在Impala对JOIN、GROUP BY、ORDER BY、UNION、DISTINCT等资源高消耗的查询进行优化时会使用到,而且对HBase的表也同样起作用。
在Impala的分析任务中,无论是数据库中的原始表、中间表、结果表还是查询用到的海量数据表或者影响性能的关键表,建议在生成表之后执行COMPUTE STATS table_name命令对表的相关信息进行统计,集群会根据统计信息自动选择优化的查询方案。越是规模大的表(GB、TB级以上)统计命令执行时间越长,统计完成后的查询优化效果越是明显。
在执行完成统计信息命令后,使用SHOW TABLE STATS table_name命令,检测确认统计信息是否可用,输出表的详细统计信息。
Compute Stats究竟有多重要?一起看看下面这个例子就知道了。
在做用户画像专题分析的过程中,我需要将用户基础信息表(3亿条)、用户语音业务信息表(1.6亿条)、用户数据业务信息表(9千万条)三张大表进行关联汇总,在使用Compute Stats命令前任务耗时50分钟,各节点内存峰值占用140GB,最终分析结果出现BUG,存在多条重复记录的情况,任务所消耗的资源情况如下图所示:
在对三张大表分别执行ComputeStats命令之后,再次执行该分析任务,任务耗时32分钟,各节点内存峰值仅为17GB,分析结果正常,任务所消耗的资源情况如下图所示:
可以看到Impala各节点在获取到表的统计信息后,能够更加合理的生成任务执行计划并完成任务的调度,不仅降低了产生“Memory limit exceeded”错误的风险,而且避免了内存消耗过大导致分析任务异常的BUG。
值得一提的是在实际应用过程中,经常会有一些规模较大的表(TB级上千亿条记录)在执行COMPUTE STATS命令时假死无法完成统计任务的情况。针对这种情况,有两种方法可以解决:一是从Impala 2.1 for CDH5.3版本新增了COMPUTE INCREMENTAL STATS(DDL)命令,将大表分区存储后,可用该命令增量的对各分区进行统计,并且当分区变动时只对变化部分进行扫描,并更新相应的统计信息;二是对于没有升级到高版本的Impala集群或是物理资冶受限的增量统计也无法完成统计任务的情况,我们可以通过手动更新表的统计信息、设置分区的统计信息等方式解决。如下图示例,首先对表count(*),然后通过alter table table_name settblproperties('numRows'='n')命令(n表示count(*)获取到的具体记录条数)手动设置表的记录条数,再次查询表的统计信息,发现Impala集群已经成功获取到表的统计信息了。
法宝二:执行计划
通过在SQL语句前加入“EXPLAIN”关键字,可以返回该语句的执行计划。执行计划从底层显示Impala如何读取数据,如何在各节点之间协调工作,组合并传输中间结果,并获得最终结果集的全过程。
根据执行计划,可以判断这个语句的执行效率,如果发现效率过低,可以调整语句本身,或者调整表结构。例如可以通过改变WHERE查询条件、对JOIN查询使用hints、引入子查询、改变表的连接顺序、改变表的分区、搜集表及列统计信息或者其他方式来对语句进行性能优化。
上图是对一个查询语句调用EXPLAIN查看执行计划,对于输出结果自底向上按序阅读:
(1)最后一部分内容展示了读取的总数据量等底层的详细信息。通过读取的数据量,我们可以判断分区策略是否有效,并结合集群大小预估读取这些数据需要的实际等。
(2)可以看到执行过程中聚合、排序、统计函数、交互的顺序及具体执行细节,可以从更高级别看到中间结果在不同节点间的流向。
(3)我们可以看到操作是否被Impala不同的节点并行执行,以及各节点所需内存预估值。
(4)通过配置EXPLAIN_LEVEL参数,可以了解到更详细的输出信息。取值从0~3,对应的执行计划信息越来越详细。
法宝三:PROFILE查询信息
使用PROFILE语句可以输出最近执行的SQL查询的更详细更底层的信息。在查询执行完成后,无论查询成功与否,输入PROFILE命令即可输出查询的详细信息。它包括了执行该查询每个节点读取的物理字节数,使用的最大内存量等信息。通过这些信息,我们可以判断查询是IP消耗型的、CPU消耗型的、网络消耗型的,或者受性能低下节点的影响,从而可以检查某些推荐的配置是否生效等。
Profile输出的查询信息内容包括:
(1)执行查询的查询状态、查询起始时间、查询语句、提交查询任务的Impala节点等基础信息:
(2)查询的执行计划和步骤:
(3)各个步骤执行情况的统计信息,包括各节点的执行平均时长、最大时长、读取数据量、内存峰值等信息:
(4)各个步骤执行情况的详细信息及每个步骤分配到各个节点执行的详细信息(由于该部分信息过多,截图省略)。
在运行一个查询之后,使用PROFILE命令从底层确认该查询的IO、内存消耗、网络带宽占用、CPU使用率等信息是否在我们期望的范围之内,若不是最优方案,则有的放矢通过对SQL语句进行调优、调整节点配置等方式进行优化。
法宝四:结构优化
在使用了上述三个法宝之后若Impala内存溢出问题还是没有解决的话,那么就有必要从结构方面考虑一下优化措施了。
(1)Parquet表存储
为Impala中源数据、中间表、结果表选择合适的存储结构。Impala默认是建立TEXT格式的表存储,而对于海量数据的存储,优选Parquet格式的存储方式。在建表时显示的指定以Parquet格式建表,并且避免使用INSER...VALUES语句向Parquet表中插入数据。因为这种方式每插入一行数据就会在HDFS上产生单独的一个小数据文件,会降低数据查询的并行度从而影响查询速度。
(2)分区技术
当表的数据量非常庞大或者表总是按照某些特定的列进行查询时,通过分区技术能极大的提升Impala的查询速度。在分区时分区列要有一定的区分度,也就是要包含一定数据的非重复值。根据实际情况选择分区的粒度,尽量保证每个分区的数据都大于1GB或者是1GB的倍数。只有分区的粒度使数据文件的大小合适,才能充分利用HDFS的IO批处理性能和Impala的分布式查询。
(3)SQL语句
SQL语句性能对最终任务的执行效率有着直接的影响。以下是SQL优化的一些“经验之谈”:
关联条件尽量避免用过长的string进行关联,尽可能使用数字进行关联。
对庞大任务拆分后“分而治之”,用建立中间表逐步执行的方式替代多层嵌套任务。
如果参与关联的表的统计信息不可用,或者Impala自动选择的连接顺效率很低,可以在SELECT关键字后使用STRAIGHT_JOIN关键字按照“大表在左,小表在右”的原则手动指定表连接顺序。
使用Hints手动从底层调整SQL查询的工作方式,Hints的使用方式为使用[]将特定的Hints括起来放在SQL语句中。
大表与小表的关联时,使用Hints指定[BROADCAST]为连接方式;处理大表之间的关联时,使用Hints指定[SHUFFLE]为连接方式。
优化集群系统资源
除了从SQL查询方面进行优化之外,还可以通过对Impala集群系统在资源配置方面加以优化,做到“双管齐下”。Impala的准入控制功能是一个轻量级的、分布式资源控制机制,它可以限制查询使用的内存段大小,并行执行的查询数量等。利用该功能对系统配置进行优化视具体集群环境而定,优化的思路包括:
合理对impalad进程制定-MEM_LIMIT选项来限制预留给查询的内存大小。
配置YARN进行动态分配,合理制定Impala与Hadoop其他组件的内存分配关系。
针对不同的用户组的应用请求,配置Cgroup资源池机制来隔离资源的使用。
若内存溢出大多出现在并行小查询数过多而非少量内存占用较高的大查询时,通过Impala admission control功能限制并发查询。
配置查询队列,新的查询请求到来时若系统资源已达到峰值,则被放入等待队列,在之前查询执行完成并释放相关资源后,等待队列中的查询才能够被执行。
使用准入控制来控制集群中Impala作业对并发和内存的消耗,使用YARN控制Hadoop其他组件的资源消耗。
Impala作为一款开源的大数据分析引擎,其自身存在的一些缺陷是可以接受的。通过采用一些合理的优化方案,挖掘它的潜力,当真正“玩转”之后,Impala一定会带给你意想不到的惊喜。