细细品味Hadoop——Hadoop集群(第14期副刊)精华集锦csAxp虾皮工作室年7月10日创建时间:2012/3/29修改时间:2012/3/29修改次数:0Hadoop集群(第14期副刊)——Hive性能优化1、性能低下根源Hive性能优化时,把HiveQL当做Map/Reduce程序来读,即从Map/Reduce的运行角度来考虑优化性能,从更底层思考如何优化运算性能,而不仅仅局限于逻辑代码的替换层面。RAC(RealApplicationCluster)真正应用集群就像一辆机动灵活的小货车,响应快,Hadoop就像吞吐量巨大的轮船,启动开销大,如果每次只做小数量的输入输出,利用率将会很低。所以用好Hadoop的首要任务是增大每次任务所搭载的数据量。Hadoop的核心能力是pariton和sort,因而这也是优化的根本。观察Hadoop处理数据的过程,有几个显著的特征:数据的大规模并不是负载重点,造成运行压力过大是因为运行数据的倾斜。jobs数比较多的作业运行效率相对比较低,比如即使有几百行的表,如果多次关联多次汇总,产生十几个jobs,将会须要30分钟以上的时间且大部分时间被用于作业分配,初始化和数据输出。Map/Reduce作业初始化的时间是比较耗费时间资源的一个部分。在使用sum,count,max,min等函数时,HadoopMap端对表中数据的汇总去重,能有效的解决数据倾斜问题。count(distinct)效率较低,因为容易产生数据倾斜问题,如果是多count(distinct)效率更低。数据倾斜是导致效率大幅降低的主要原因,可以采用多一次Map/Reduce的方法,避免倾斜。结论:避实就虚,用job数的增加,输入量的增加,占用更多存储空间,充分利用空闲CPU等各种方法,分解数据倾斜造成的负担。2、从配置角度优化Hive系统内部已针对不同的查询预设定了优化方法,用户可以通过调整配置进行控制,以下举例介绍部分优化的策略以及优化控制选项。2.1列裁剪Hive在读数据的时候,可以只读取查询中所需要用到的列,而忽略其它列。例如,若有以下查询:Selecta,bFromQWheree10;在实施此项查询中,Q表有5列(a,b,c,d,e),Hive只读取查询逻辑中真实需要的3列a、b、e,而忽略列c,d;这样做节省了读取开销,中间表存储开销和数据整合开销。河北工业大学——软件工程与理论实验室整理:虾皮1创建时间:2012/3/29修改时间:2012/3/29修改次数:0列裁剪所对应的参数项为:hive.optimize.cp=true(默认值为真)2.2分区裁剪可以在查询的过程中减少不必要的分区。例如,若有以下查询:Select*From(Selecta1,Count(1)FromTGroupBya1)subqWheresubq.prtn=100;Select*FromT1Join(Select*FromT2)subqOn(T1.a1=subq.a2)Wheresubq.prtn=100;此查询语句若将“subq.prtn=100”条件放入子查询中更为高效,可以减少读入的分区数目。Hive自动执行这种裁剪优化。分区参数为:hive.optimize.pruner=true(默认值为真)2.3Join操作在编写带有join操作的代码语句时,应该将条目少的表/子查询放在Join操作符的左边。因为在Reduce阶段,位于Join操作符左边的表的内容会被加载进内存,载入条目较少的表可以有效减少OOM(outofmemory)即内存溢出。所以对于同一个key来说,对应的value值小的放前,大的放后,这便是“小表放前”原则。若一条语句中有多个Join,依据Join的条件相同与否,有不同的处理方法。示例1:在Join的条件相同的情况下,如有以下查询。InsertOverWriteTablepv_usersSelectpv.pageid,u.ageFrompage_viewpJoinuseruOn(pv.userid=u.userid)JoinnewuserxOn(u.suerid=x.userid)备注:不论是外关联outerjoin还是内关联innerjoin,如果Join的key相同,不管有多少个表,都会合并为一个MapReduce任务,而不是“n”个任务。示例2:在Join条件不同的情况下,若有以下查询。InsertOverWriteTablepv_usersSelectpv.pageid,u.ageFrompage_viewpJoinuseruOn(pv.userid=u.userid)JoinnewuserxOn(u.age=x.age);Map/Reduce的任务数目和Join操作的数目是对应的,那么上述的查询和以下的查询时等价的。河北工业大学——软件工程与理论实验室整理:虾皮2创建时间:2012/3/29修改时间:2012/3/29修改次数:0InsertOverWriteTabletmptableSelect*Frompage_viewpJoinuseruOn(pv.userid=u.userid)InsertOverWiriteTablepv_usersSelectx.pageid,x.ageFromtmptablexJoinnewuseryOn(x.age=y.age)2.4MapJoin操作当需要的数据已经存在于Map端,即跟随Map端输入了,那么Join不会进入Reduce,会在Map阶段完成。比如如下查询:InsertOverWriteTablepv_usersSelect/*+MapJoin(pv)*/pv.pageid,u.ageFrompage_viewpvJoinuseruOn(pv.userid=userid);这个查询在Map阶段完成Join,如图2.4-1所示:图2.4-1MapJoin工作原理相关的参数为:hive.join.emit.interval=1000hive.mapjoin.size.key=10000hive.mapjoin.cache.numrows=100002.5GroupBy操作进行GroupBy操作时需要注意以下两点:河北工业大学——软件工程与理论实验室整理:虾皮3创建时间:2012/3/29修改时间:2012/3/29修改次数:0Map端部分聚合事实上,并不是所有的聚合操作都需要在reduce部分进行,很多聚合操作都可以先在map端进行部分聚合,然后在reduce端得出昀终结果。这里需要修改的参数为:hive.map.aggr=true(用于设定是否在map端进行聚合,默认值为真)hive.groupby.mapaggr.checkinterval=100000(用于设定map端进行聚合操作的条目数)有数据倾斜(数据分布不均匀)时进行负载均衡此处需要设定hive.groupby.skewindata,当选项设定为true是,生成的查询计划有两个MapReduce任务。在第一个MapReduce中,map的输出结果集合会随机分布到reduce中,每个reduce做部分聚合操作,并输出结果。这样处理的结果是,相同的GroupByKey有可能分发到不同的reduce中,从而达到负载均衡的目的;第二个MapReduce任务再根据预处理的数据结果按照GroupByKey分布到reduce中(这个过程可以保证相同的GroupByKey分布到同一个reduce中),昀后完成昀终的聚合操作。2.6合并小文件我们知道文件数目小,容易在文件存储端造成瓶颈,给HDFS带来压力,影响处理效率。对此,可以通过合并Map和Reduce的结果文件来消除这样的影响。用于设置合并属性的参数有:是否合并Map输出文件:hive.merge.mapfiles=true(默认值为真)是否合并Reduce端输出文件:hive.merge.mapredfiles=false(默认值为假)合并文件的大小:hive.merge.size.per.task=256*1000*1000(默认值为256000000)3、从程序角度优化3.1熟练地使用SQL提高查询效率熟练地使用SQL,能写出高效率的查询语句。如下列:问题:有一张user表,为卖家每天收到表,user_id,ds(日期)为key,属性有主营类目,指标有交易金额,交易笔数。每天要取前10天的总收入,总笔数,和昀近一天的主营类目。解决方法1如下所示:常用方法InsertOverWriteTablet1Selectuser_id,substr(max(concat(ds,cat),9)asmain_cat)FromusersWhereds=20120329//20120329为日期列的值,实际代码中可以用函数表示出当天日期GroupByuser_id;InsertOverWriteTablet2Selectuser_id,sum(qty)asqty,sum(amt)asamt河北工业大学——软件工程与理论实验室整理:虾皮4创建时间:2012/3/29修改时间:2012/3/29修改次数:0FromusersWheredsBetween20120301And20120329GroupByuser_idSelectt1.user_id,t1.main_cat,t2.qty,t2.amtFromt1Joint2Ont1.user_id=t2.user_id方法1的步骤如下:第一步:利用分析函数,取每个user_id昀近一天的主营类目,存入临时表t1第二步:汇总10天的总交易金额,交易笔数,存入临时表t2第三步:关联t1,t2,得到昀终的结果解决方法2如下所示:优化方法Selectuser_id,substr(max(concat(ds,cat)),9)asmain_cat,sum(qty),sum(amt)FromusersWheredsBetween20120301And20120329GroupByuser_id总结:方案2的开销等于方案1的第二步的开销,性能提升,由原有的25分钟完成,缩短为10分钟以内完成。节省了两个临时表的读写是一个关键原因,这种方式也适用于Oracle中的数据查找工作。SQL具有普适性,很多SQL通用的优化方案在Hadoop分布式计算方式中也可以达到效果。3.2无效id在关联时的倾斜问题问题:日志中常会出现信息丢失,比如每日约为20亿的全网日志,其中的user_id为主键,在日志收集过程中会丢失,出现主键为null的情况,如果取其中的user_id和bmw_users关联,就会碰到数据倾斜的问题。原因是Hive中,主键为null值的项会被当做相同的Key而分配进同一个计算Map。解决方法1:user_id为空的不参与关联,子查询过滤nullSelect*FromlogaJoinbmw_usersbOna.user_idIsNotnullAnda.user_id=b.user_idUnionAllSelect*FromlogaWherea.user_idIsnull解决方法2如下所示:函数过滤nullSelect*FromlogaLeftOuterJoinbmw_usersbOnCaseWhena.user_idIsnullThenconcat(‘dp_hive’,rand())Elsea.user_idEnd=b.user_id;河北工业大学——软件工程与理论实验室整理:虾皮5创建时间:2012/3/29修改时间:2012/3/29修改次数:0调优结果:原先由于数据倾斜导致运行时长超过1小时,解决方法1运行每日平均时长25分钟,解决方法2运行的每日平均时长在20分钟左右。优化效果很明显。总结:解决方法2比解决方法1效果更好,不但IO少了,而且作业数也少了。解决方法1中log读取两次,job数为2。解决方法2中job数是1。这个优化