Hadoop使用心得,流程详解

CYD同学的Hadoop使用心得和大家分享下

使用hadoop的map/reduce进行分布式运算已经有一段时间了,谈下自己的心得,纯粹是自己的感受,欢迎拍砖。

首先是部署,在linux下的部署非常简单,随便在网上找个攻略就能搞定。关键是如何配置一个调试环境。在实验室,我是用ubuntu,直接在图形界面下用eclipse,非常方便。考虑到很多时候,linux没有图形界面,比如淘宝这种在windows下用xshell登陆开发机的情况,有必要在windows下模拟一个伪分布式环境,这样可以把java程序调通,结果正确之后,再打成jar包放上去。而不用每次打成jar包,放上去试试能不能跑对,很没效率。Windows下使用eclipse模拟伪分布式环境需要用到cygwin,具体方法在http://ebiquity.umbc.edu/Tutorials/Hadoop/。

接下来就是map/reduce程序的编写。有了刚才的环境,我们可以很方便地在eclipse下debug。学习map/reduce程序的编写,我主要是参考了Hadoop:The Definite Guide这本书,我觉得写的非常的好。在我做量子排行榜的工作中,主要用到的全局排序和二次排序在书中讲的比较清楚,唯一要注意的是一些实现上的细节。

如果你没有用Map/reduce模式写过程序,那么建议先看下wordcount。Map/reduce框架的工作原理如下:先是利用map读入数据,并转化成reduce需要的格式(key,value)二元组,然后把这样的对,按照key分配到不同的reduce结点中。在每个节点按照reduce函数进行计算。该计算只能在相同的key内进行,比如你不能进行(a,1)和(b,2)之间的计算,除非你自己写一个函数把a,b认为是相同的key。在这个过程中,key一共要被比较两次,第一次是分发的时候,根据key的值来决定将这个二元组分发到哪个reduce结点中进行计算,第二次是在每一个reduce结点中,比较他所包含的所有二元组的key值,以确定哪些二元组具有相同的key,需要放到一个group中进行计算。也就是因为有第二步,所以每个reduce中的二元组是按照key的大小,有序排列的。这两个对key的比较方法可以是不同的,分别在JobConf的setOutputKeyComparatorClass和setOutputValueGroupingComparator中设置。

很自然地我们想到,可以利用reduce结点内部,key有序这个事实来做排序工作。量子排行榜主要需要2个工作,全局排序和二次排序。全局排序是因为要排序的数据量太大,不可能只利用1个reduce结点来实现,要保证不同的redece结点的数据也是有序的,比如结点1中的数据必须比结点2及它以后的结点中的数据大。二次排序是主要用于店铺宝贝的过滤,我们在进行某些排名时,对于一个店铺,只拿他排在前50的宝贝来做排序。这就不能简单地利用key的有序性来做了,因为这里key要用来满足第一个条件,店铺id相同。

全局排序利用了它自带的TotalOrderPartition方法,在二元组的分发时就保证了reduce之间数据的有序性。假设一共用到k个reduce结点,它事先生成k个有序的区间,key落在哪个区间就分到哪个reduce结点。这需要在JonConf中setPartitionerClass为我们所需的Partition函数。Hadoop0.19版本自带了TotalOrderPartitionr.class,他首先对map中的数据进行采样,然后划分区间,尽可能地使得分发到每个reduce的二元组的个数相差不大。但是在淘宝的数据排序中,自带的Partitionr函数不能用,因为该Partitioner函数要求区间是严格递增的,即不能有区间为空。而淘宝的数据中,很多店铺宝贝的pv是小于10的,最多的就是pv=1的情况。如果直接利用自带的sampler和Partitioner函数,会出错,因为pv=1的太多,若要平均分配必然会导致空区间的产生,比如前3个区间都是[1,1]。为了解决这个问题,我简单地改写了下Partitioner函数,把要求严格递增那出的>改成了>=。这使得程序能够正确运行,但是会出现的问题就是,有的reduce结点是空的。比如前三个区间都是[1,1]的话,那么pv=1的所有数据都会分配到第一个节点中,而2,3是空的。有兴趣的朋友可以继续改写,使得这种情况下,pv=1的数据能随机分配到那3个结点中。我没有改写是因为我要做的工作只是排序,而即使结点1中的数据量很大,由于它们的值都是相同的,所以使用快排算法的话,O(N)时间内就能搞定,别的结点是O(NlogN),所以结点1不会成为算法效率的瓶颈。等哪次不再是排序算法,而是别的算法时,我再来继续改写这个分发函数。

还要注意的一点是,这个sampler是在map读入的(key,value)对中做采样,而不是map的输出对(即reduce的输入对)。而我们要进行排序,是将要排序的数值,作为reduce输入的key。这就要求map的输入对和map的输出对的格式一致(至少key得一样,并且就是要排序的那些数值)。这样的话,用那些自带的InputFormat函数就不行了。比如TextInputFormat它将map的输入直接变成,一个表示行的LongWritable,和该行的内容Text。因为我们要排序的一般都是数值,我按照自带的KeyValueTextInputFormat和KeyValueLineRecordReader改写了一个KeyValueFloatInputFormat和KeyValueFloatRecordReader来实现,map直接从有规则的文本中,读入对。主要是懒得去改sampler,有能力的牛人,也可以去改动sampler使得它在map的输出中采样,不过我觉得这样的改动量会比较大,我自己就采取了偷懒的办法。这点浪费了我很多时间,书中没说sampler在什么时候采样,试了好久才发现居然是在map之前,太土了。

二次排序直接是参考的The Definite Guide的secondary sort那一节,原理书上已经说的很清楚了。就是自己定义一个二元组类,来作为key,但是要定义它的两个不同的比较函数。比如我们要求相同店铺的放在一起,同时按照pv排序。我们就定义一个二元组<店铺id,pv>。当分发时,我们定义它的比较函数,只比较他的第一部分。这样就保证了相同店铺的数据一定被分在同一个reduce结点内。而在节点内部比较时,我们先比较该二元组的第一部分,如果相同,再比较第二部分,这就是实现了上述的要求。

同理我们可以实现三次排序,四次排序。只要自己定义相应的三元组类和四元组类就行了。比如相同的店铺,在pv相同的情况下,按uv排序。只要定义<店铺id,pv,uv>这样的三元组。分发时,还是只比较第一部分。结点内则先后比较三部分即可。

代码在书中其实已经有了,不过他的是的pair,而我们排序要求的是的pair。自己写一个,并实现下它的compare函数即可。

你可能感兴趣的:(hadoop)