写好Hive程序的五个提示淘宝数据平台团队doc.docx
- 文档编号:27878096
- 上传时间:2023-07-05
- 格式:DOCX
- 页数:19
- 大小:35.52KB
写好Hive程序的五个提示淘宝数据平台团队doc.docx
《写好Hive程序的五个提示淘宝数据平台团队doc.docx》由会员分享,可在线阅读,更多相关《写好Hive程序的五个提示淘宝数据平台团队doc.docx(19页珍藏版)》请在冰豆网上搜索。
写好Hive程序的五个提示淘宝数据平台团队doc
写好Hive程序的五个提示,淘宝数据平台团队
使用Hive可以高效而又快速地编写复杂的MapReduce查询逻辑。
但是某些情况下,因为不熟悉数据特性,或没有遵循Hive的优化约定,Hive计算任务会变得非常低效,甚至无法得到结果。
一个"好"的Hive程序仍然需要对Hive运行机制有深入的了解。
有一些大家比较熟悉的优化约定包括:
Join中需要将大表写在靠右的位置;尽量使用UDF而不是transfrom…诸如此类。
下面讨论5个性能和逻辑相关的问题,帮助你写出更好的Hive程序。
全排序Hive的排序关键字是SORTBY,它有意区别于传统数据库的ORDERBY也是为了强调两者的区别–SORTBY只能在单机范围内排序。
考虑以下表定义:
CREATETABLEifnotexistst_order(idint,--订单编号sale_idint,--销售IDcustomer_idint,--客户IDproduct_idint,--产品IDamountint--数量)PARTITIONEDBY(dsSTRING);在表中查询所有销售记录,并按照销售ID和数量排序:
setmapred.reduce.tasks=2;Selectsale_id,amountfromt_orderSortbysale_id,amount;这一查询可能得到非期望的排序。
指定的2个reducer分发到的数据可能是(各自排序):
Reducer1:
Sale_id|amount0|1001|301|502|20Reducer2:
Sale_id|amount0|1100|1203|504|20因为上述查询没有reducekey,hive会生成随机数作为reducekey。
这样的话输入记录也随机地被分发到不同reducer机器上去了。
为了保证reducer之间没有重复的sale_id记录,可以使用DISTRIBUTEBY关键字指定分发key为sale_id。
改造后的HQL如下:
setmapred.reduce.tasks=2;Selectsale_id,amountfromt_orderDistributebysale_idSortbysale_id,amount;这样能够保证查询的销售记录集合中,销售ID对应的数量是正确排序的,但是销售ID不能正确排序,原因是hive使用hadoop默认的HashPartitioner分发数据。
这就涉及到一个全排序的问题。
解决的办法无外乎两种:
1.)不分发数据,使用单个reducer:
setmapred.reduce.tasks=1;这一方法的缺陷在于reduce端成为了性能瓶颈,而且在数据量大的情况下一般都无法得到结果。
但是实践中这仍然是最常用的方法,原因是通常排序的查询是为了得到排名靠前的若干结果,因此可以用limit子句大大减少数据量。
使用limitn后,传输到reduce端(单机)的数据记录数就减少到n*(map个数)。
2.)修改Partitioner,这种方法可以做到全排序。
这里可以使用Hadoop自带的TotalOrderPartitioner(来自于Yahoo!
的TeraSort项目),这是一个为了支持跨reducer分发有序数据开发的Partitioner,它需要一个SequenceFile格式的文件指定分发的数据区间。
如果我们已经生成了这一文件(存储在/tmp/range_key_list,分成100个reducer),可以将上述查询改写为setmapred.reduce.tasks=100;sethive.mapred.partitioner=org.apache.hadoop.mapred.lib.TotalOrderPartitioner;settotal.order.partitioner.path=/tmp/range_key_list;Selectsale_id,amountfromt_orderClusterbysale_idSortbyamount;有很多种方法生成这一区间文件(例如hadoop自带的o.a.h.mapreduce.lib.partition.InputSampler工具)。
这里介绍用Hive生成的方法,例如有一个按id有序的t_sale表:
CREATETABLEifnotexistst_sale(idint,namestring,locstring);则生成按sale_id分发的区间文件的方法是:
createexternaltablerange_keys(sale_idint)rowformatserde'org.apache.hadoop.hive.serde2.binarysortable.BinarySortableSerDe'storedasinputformat'org.apache.hadoop.mapred.TextInputFormat'outputformat'org.apache.hadoop.hive.ql.io.HiveNullValueSequenceFileOutputFormat'location'/tmp/range_key_list';insertoverwritetablerange_keysselectdistinctsale_idfromsourcet_salesampletable(BUCKET100OUTOF100ONrand())ssortbysale_id;生成的文件(/tmp/range_key_list目录下)可以让TotalOrderPartitioner按sale_id有序地分发reduce处理的数据。
区间文件需要考虑的主要问题是数据分发的均衡性,这有赖于对数据深入的理解。
怎样做笛卡尔积?
当Hive设定为严格模式(hive.mapred.mode=strict)时,不允许在HQL语句中出现笛卡尔积,这实际说明了Hive对笛卡尔积支持较弱。
因为找不到Joinkey,Hive只能使用1个reducer来完成笛卡尔积。
当然也可以用上面说的limit的办法来减少某个表参与join的数据量,但对于需要笛卡尔积语义的需求来说,经常是一个大表和一个小表的Join操作,结果仍然很大(以至于无法用单机处理),这时MapJoin才是最好的解决办法。
MapJoin,顾名思义,会在Map端完成Join操作。
这需要将Join操作的一个或多个表完全读入内存。
MapJoin的用法是在查询/子查询的SELECT关键字后面添加/*+MAPJOIN(tablelist)*/提示优化器转化为MapJoin(目前Hive的优化器不能自动优化MapJoin)。
其中tablelist可以是一个表,或以逗号连接的表的列表。
tablelist中的表将会读入内存,应该将小表写在这里。
PS:
有用户说MapJoin在子查询中可能出现未知BUG。
在大表和小表做笛卡尔积时,规避笛卡尔积的方法是,给Join添加一个Joinkey,原理很简单:
将小表扩充一列joinkey,并将小表的条目复制数倍,joinkey各不相同;将大表扩充一列joinkey为随机数。
怎样写existin子句?
Hive不支持where子句中的子查询,SQL常用的existin子句需要改写。
这一改写相对简单。
考虑以下SQL查询语句:
SELECTa.key,a.valueFROMaWHEREa.keyin(SELECTb.keyFROMB);可以改写为SELECTa.key,a.valueFROMaLEFTOUTERJOINbON(a.key=b.key)WHEREb.keyNULL;一个更高效的实现是利用leftsemijoin改写为:
SELECTa.key,a.valFROMaLEFTSEMIJOINbon(a.key=b.key);leftsemijoin是0.5.0以上版本的特性。
Hive怎样决定reducer个数?
HadoopMapReduce程序中,reducer个数的设定极大影响执行效率,这使得Hive怎样决定reducer个数成为一个关键问题。
遗憾的是Hive的估计机制很弱,不指定reducer个数的情况下,Hive会猜测确定一个reducer个数,基于以下两个设定:
1.hive.exec.reducers.bytes.per.reducer(默认为1000^3)2.hive.exec.reducers.max(默认为999)计算reducer数的公式很简单:
N=min(参数2,总输入数据量/参数1)通常情况下,有必要手动指定reducer个数。
考虑到map阶段的输出数据量通常会比输入有大幅减少,因此即使不设定reducer个数,重设参数2还是必要的。
依据Hadoop的经验,可以将参数2设定为0.95*(集群中TaskTracker个数)。
合并MapReduce操作Multi-groupbyMulti-groupby是Hive的一个非常好的特性,它使得Hive中利用中间结果变得非常方便。
例如,FROM(SELECTa.status,b.school,b.genderFROMstatus_updatesaJOINprofilesbON(a.userid=b.useridanda.ds='2009-03-20'))subq1INSERTOVERWRITETABLEgender_summaryPARTITION(ds='2009-03-20')SELECTsubq1.gender,COUNT
(1)GROUPBYsubq1.genderINSERTOVERWRITETABLEschool_summaryPARTITION(ds='2009-03-20')SELECTsubq1.school,COUNT
(1)GROUPBYsubq1.school上述查询语句使用了Multi-groupby特性连续groupby了2次数据,使用不同的groupbykey。
这一特性可以减少一次MapReduce操作。
Multi-distinctMulti-distinct是淘宝开发的另一个multi-xxx特性,使用Multi-distinct可以在同一查询/子查询中使用多个distinct,这同样减少了多次MapReduce操作。
Technorati:
hiveHive随谈(六)–Hive的扩展特性四月21,2010By:
yuancaiCategory:
Hive,所有NoComments→Hive是一个很开放的系统,很多内容都支持用户定制,包括:
文件格式:
TextFile,SequenceFile内存中的数据格式:
JavaInteger/String,HadoopIntWritable/Text用户提供的map/reduce脚本:
不管什么语言,利用stdin/stdout传输数据用户自定义函数:
Substr,Trim,1–1用户自定义聚合函数:
Sum,Average…n–1FileFormatTextFileSequenceFIleRCFFileDatatypeTextOnlyText/BinaryText/BinaryInternalStorageOrderRow-basedRow-basedColumn-basedCompressionFileBasedBlockBasedBlockBasedSplitableYESYESYESSplitableAfterCompressionNoYESYESCREATETABLEmylog(user_idBIGINT,page_urlSTRING,unix_timeINT)STOREDASTEXTFILE;当用户的数据文件格式不能被当前Hive所识别的时候,可以自定义文件格式。
可以参考contrib/src/java/org/apache/hadoop/hive/contrib/fileformat/base64中的例子。
写完自定义的格式后,在创建表的时候指定相应的文件格式就可以:
CREATETABLEbase64_test(col1STRING,col2STRING)STOREDASINPUTFORMAT'org.apache.hadoop.hive.contrib.fileformat.base64.Base64TextInputFormat'OUTPUTFORMAT'org.apache.hadoop.hive.contrib.fileformat.base64.Base64TextOutputFormat';SerDeSerDe是Serialize/Deserilize的简称,目的是用于序列化和反序列化。
序列化的格式包括:
分隔符(tab、逗号、CTRL-A)Thrift协议反序列化(内存内):
JavaInteger/String/ArrayList/HashMapHadoopWritable类用户自定义类目前存在的Serde见下图:
其中,LazyObject只有在访问到列的时候才进行反序列化。
BinarySortable:
保留了排序的二进制格式。
当存在以下情况时,可以考虑增加新的SerDe:
用户的数据有特殊的序列化格式,当前的Hive不支持,而用户又不想在将数据加载至Hive前转换数据格式。
用户有更有效的序列化磁盘数据的方法。
用户如果想为Text数据增加自定义Serde,可以参照contrib/src/java/org/apache/hadoop/hive/contrib/serde2/RegexSerDe.java中的例子。
RegexSerDe利用用户提供的正则表倒是来反序列化数据,例如:
CREATETABLEapache_log(hostSTRING,identitySTRING,userSTRING,timeSTRING,requestSTRING,statusSTRING,sizeSTRING,refererSTRING,agentSTRING)ROWFORMATSERDE'org.apache.hadoop.hive.contrib.serde2.RegexSerDe'WITHSERDEPROPERTIES("input.regex"="([^]*)([^]*)([^]*)(-|\[[^\]]*\])([^\"]*|\"[^\"]*\")(-|[0-9]*)(-|[0-9]*)(?
:
([^\"]*|\"[^\"]*\")([^\"]*|\"[^\"]*\"))?
","output.format.string"="%1$s%2$s%3$s%4$s%5$s%6$s%7$s%8$s%9$s";)STOREDASTEXTFILE;用户如果想为Binary数据增加自定义的SerDE,可以参考例子:
serde/src/java/org/apache/hadoop/hive/serde2/binarysortable,例如:
CREATETABLEmythrift_tableROWFORMATSERDE'org.apache.hadoop.hive.contrib.serde2.thrift.ThriftSerDe'WITHSERDEPROPERTIES("serialization.class"="com.facebook.serde.tprofiles.full","serialization.format"="com.facebook.thrift.protocol.TBinaryProtocol";);Map/Reduce脚本(Transform)用户可以自定义Hive使用的Map/Reduce脚本,比如:
FROM(SELECTTRANSFORM(user_id,page_url,unix_time)USING'page_url_to_id.py'AS(user_id,page_id,unix_time)FROMmylogDISTRIBUTEBYuser_idSORTBYuser_id,unix_time)mylog2SELECTTRANSFORM(user_id,page_id,unix_time)USING'my_python_session_cutter.py'AS(user_id,session_info);Map/Reduce脚本通过stdin/stdout进行数据的读写,调试信息输出到stderr。
UDF(User-Defined-Function)用户可以自定义函数对数据进行处理,例如:
addjarbuild/ql/test/test-udfs.jar;CREATETEMPORARYFUNCTIONtestlengthAS'org.apache.hadoop.hive.ql.udf.UDFTestLength';SELECTtestlength(src.value)FROMsrc;DROPTEMPORARYFUNCTIONtestlength;UDFTestLength.java为:
packageorg.apache.hadoop.hive.ql.udf;publicclassUDFTestLengthextendsUDF{publicIntegerevaluate(Strings){if(s==null){returnnull;}returns.length();}}自定义函数可以重载:
addjarbuild/contrib/hive_contrib.jar;CREATETEMPORARYFUNCTIONexample_addAS'org.apache.hadoop.hive.contrib.udf.example.UDFExampleAdd';SELECTexample_add(1,2)FROMsrc;SELECTexample_add(1.1,2.2)FROMsrc;UDFExampleAdd.java:
publicclassUDFExampleAddextendsUDF{publicIntegerevaluate(Integera,Integerb){if(a=null||b=null)returnnull;returna+b;}publicDoubleevaluate(Doublea,Doubleb){if(a=null||b=null)returnnull;returna+b;}}%%在使用UDF的时候,会自动进行类型转换,这个java或者C中的类型转换有些类似,比如:
SELECTexample_add(1,2.1)FROMsrc;的结果是3.1,这是因为UDF将类型为Int的参数"1″转换为double。
类型的隐式转换是通过UDFResolver来进行控制的,并且可以根据不同的UDF进行不同的控制。
UDF还可以支持变长的参数,例如UDFExampleAdd.java:
publicclassUDFExampleAddextendsUDF{publicIntegerevaluate(Integer.a){inttotal=0;for(inti=0;ia.length;i++)if(a!
=null)total+=a;returntotal;}//thesameforDoublepublicDoubleevaluate(Double.a)}使用例子为:
SELECTexample_add(1,2)FROMsrc;SELECTexample_add(1,2,3)FROMsrc;SELECTexample_add(1,2,3,4.1)FROMsrc;综上,UDF具有以下特性:
用java写UDF很容易。
Hadoop的Writables/Text具有较高性能。
UDF可以被重载。
Hive支持隐式类型转换。
UDF支持变长的参数。
genericUDF提供了较好的性能(避免了反射)。
UDAF(User-DefinedAggregationFuncation)例子:
SELECTpage_url,count
(1),count(DISTINCTuser_id)FROMmylog;UDAFCount.java:
publicclassUDAFCountextendsUDAF{publicstaticclassEvaluatorimplementsUDAFEvaluator{privateintmCount;publicvoidinit(){mcount=0;}publicbooleaniterate(Objecto){if(o!
=null)mCount++;returntrue;}publicIntegerterminatePartial(){returnmCount;}publicbooleanmerge(Integero){mCount+=o;returntrue;}publicIntegerterminate(){returnmCount;}}UDAF总结:
编写UDAF和UDF类似UDAF可以重载UDAF可以返回复杂类在使用UDAF的时候可以禁止部分聚合功能UDF,UDAF和MR脚本的对比:
Hive随谈(五)–Hive优化四月14,2010By:
yuancaiCategory:
Hive,所有NoComments→Hive针对不同的查询进行了优化,优化可以通过配置进行控制,本文将介绍部分优化的策略以及优化控制选项。
列裁剪(ColumnPruning)在读数据的时候,只读取查询中需要用到的列,而忽略其他列。
例如,对于查询:
SELECTa,bFROMTWHEREe10;其中,T包含5个列(a,b,c,d,e),列c,d将会被忽略,只会读取a,b,e列这个选项默认为真:
hive.optimize.cp=true分区裁剪(PartitionPruning)在查询的过程中减少不必要的分区。
例如,对于下列查询:
SELECT*FROM(SELECTc1,COUNT
(1)FROMTGROUPBYc1)subqWHEREsubq.prtn=100;SELECT*FROMT1JOIN(SELECT*FROMT2)subqON(T1.c1=subq.c2)WHEREsubq.prtn=100;会在子查询中就考虑subq.prtn=100条件,从而减少读入的分区数目。
此选项默认为真:
hive.optimize.pruner=trueJoin在使用写有Join操作的查询语句时有一条原则:
应该将条目少的表/子查询放在Join操作符的左边。
原因是在Join操作的Reduce阶段,位于Join操作符左边的表的内容会被加载进内存,将条目少的表放在左边,可以有效减少发生OOM错误的几率。
对于一条语句中有多个Join的情况,如果Join的条件相同,比如查询:
INSERTOVERWRITETABLEpv_usersSELECTpv.pageid,u.ageFROMpage_viewpJOINuseruON(pv.userid=u.userid)JOINnewuserxON(u.userid=x.userid);
如果Join的key相同,不管有多少个表,都会则会合并为一个Map-Reduce一个Map-Reduce任务,而不是'n'个在做OUTERJOIN的时候也是一样如果Join的条件不相同,比如:
INSERTOVERWRITETABLEpv_usersSELECTpv.
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Hive 程序 五个 提示 淘宝 数据 平台 团队 doc
