Local Aggregation
说到Local Aggregation,你可能会想不就是Combiner吗。实际上在mapper中进行combining比使用真正的combiner高效得多。首先combiner只是作为MapReduce的可选优化方案(就像inline对于C++编译器是一种可选优化方案一样),不一定会被执行。其次在mapper中进行combining可以减少很多的I/O操作,提高效率,毕竟mapper的每个结果都个结果都要被写入磁盘,我们当然希望写入磁盘的数据越少越好。
回想一下WordCount的例子,在mapper中Emit(word,1),在reducer中对相同的键值Emit(word,sum),而combiner采用和reducer一样的例程。
现在我们要在mapper中就把combiner的事情给做了。
class Mapper
method setup
H := new AssociativeArray
method map(docid a,doc d)
foreach term in doc
H{term} := H{term}+1
method cleanup
foreach term in H
Emit(term,value of H{term})
但是上述方法在可扩展性上会遇到瓶颈:我们必须在内存中维持一个关联数组H,当mapper接收到的数据分片很大,里面有很多word时,H就会很大,甚至超出内存。解决办法有两种:
设定一个值K,mapper每读入K条键值对,就把H中的内容全部Emit,然后把H清空接着读入键值对。类似于先读满一个buffer,再把它flush掉,然后buffer重复利用。
在mapper中监控内存的使用率,当达到一定阈值时,就把H的内容flush。
使用Local Aggregation设计算法的正确性
有一个计算平均值的例子。输入文件中的每一行记录着(string,count)表示一个单词出现的次数,在所有的记录中相同的string会出现多次,现在我们要计算每一个string的平均count。
求和是一种“分配型”的聚合,即
但求平均不是分配型的,即
一种直接的想法是:
class Mapper
method map(string word,integer count)
Emit(word,count)
class Reducer
method reduce(string word,integer-list [c1,c2,...])
sum := 0
cnt := 0
foreach integer c in [c1,c2,...]
sum := sum+c
cnt := cnt+1
avg := sum/cnt
Emit(word,avg)
现在我们想使用combiner进行local aggregation。在上面的代码中,很容易地可以将reducer的部分工作放到combiner中来完成。
class Mapper
method map(string word,integer count)
Emit(word,count)
class Combiner
method reduce(string word,integer-list [c1,c2,...])
sum := 0
cnt := 0
foreach integer c in [c1,c2,...]
sum := sum+c
cnt := cnt+1
Emit(word,pair(sum,cnt))
class Reducer
method reduce(string word,pairs[(s1,c1),(s2,c2),...])
sum := 0
cnt := 0
foreach pair(s,c) in pairs[(s1,c1),(s2,c2),...]
sum := sum+s
cnt := cnt+c
avg := sum/cnt
Emit(word,avg)
很遗憾上面的代码是错误的,不符合MapReduce的规范,由于Combiner只是可选的优化方案,所以MapReduce要求在没有Combiner的情况下程序仍能正确的运行。上述代码mapper的输出键值对和reducer的输入键值对类型不匹配,导致程序无法运行。还好,我们只需要把mapper稍作修改就可以了。
class Mapper
method map(string word,integer count)
Emit(word,pair(count,1))
进一步,现在我们想把local aggregation从combiner中转移到mapper中,与wordcount例子中使用的技巧一样,使用关联数组!
class Mapper
method setup
S := new AssociativeArray
C := new AssociativeArray
method map(string word,integer count)
S{word} := S{word}+count
C{word} := C{word}+1
method cleanup
foreach word in S
Emit(word,pair(S{word},C{word}))
Pairs and Stripes
mapper和reducer之间能够交换的数据仅仅是一个键值对,很多时候我们为了使这一个键值对中包含更多的信息,就把键或值设计得足够复杂,比如让它是一个pair或AssociativeArray。在上文中我们已经看到这种应用实例了。
在N-Gram模型中,我们要计算一个方阵A,其规模是N×N,N是语料库中单词的个数。A[i][j]表示单词j在单词i的领域中出现的次数。
Pairs方式:
class Mapper
method map(docid a,doc d)
foreach term w in doc
foreach term u in Neighbour(w)
Emit(pair(w,u),1)
class Reducer
method reduce(pair p,counts [c1,c2,...])
s := 0
foreach count c in [c1,c2,...]
s := s+c
Emit(p,s)
Stripes方式:
class Mapper
method map(docid a,doc d)
foreach term w in doc
H := new AssociativeArray
foreach term u in Neighbour(w)
H{u} := H{u}+1
Emit(w,H)
class Reducer
method reduce(term w,stripes [H1,H2,...])
F := new AssociativeArray
foreach stripe H in [H1,H2,...]
sum(F,H)
Emit(w,F)
上述pairs和stripe方式都可以很轻松的写一个combiner来提高效率,并且在stripes方式中combiner发挥的作用会相对大一些。
注意在Stripes方式中,由于要为doc中的每一个term建立一个AssociativeArray,维持这么多的关联数组可能会导致内存溢出。解决方案在本文的第一小节已经提到了。
stripe方式要比pairs方式快得多,因为pairs方式Emit操作(即写磁盘操作)太频繁了。
Computing Relative Frequencies
还说N-Gram这个问题,刚才我们只是计算了单词u在单词w的领域内出现的次数,但是个次数多并不能说明u和w的关系就强烈,因为可能是w太平凡了,导致w出现的次数很多,进而导致w和u相伴出现的次数也很多。另外有一对单词p和q,它们相伴出现的次数比较少并不能说明它们的关系不强烈,可能是因为在语料库p总共出现的次数也不多。所以我们应该计算“相对频率”才是合理的:
实际上就是对已有矩阵的每一行求和,再让该行的每个元素除以这个和。
使用上面的Stripes方式代码可以很容易地扩展为计算相对频率的代码,因为在redcuer中Emit的每一个键值对刚好对应矩阵的一行数据,计算矩阵的行和非常方便。
现在我们要在pairs方式中计算相对频率该怎么做呢?因为mapper输入的键值对是(pair(w,u),1),要计算行和就必须使所相同的w都到一个reducer中,但默认情况下Hadoop只能保证相同的pair(w,u)映射到同一个reducer当中--对于复杂的键类型,MapReduce中要把它转换成bytes再进行Hash。因此我们需要重写Partitioner,仅对pair的左值(即w)进行散列,这就可以保证相同的w都映射到了同一个reducer。但是注意,映射到同一个reducer的pair的左值可能不只一种。MapReduce保证映射到同一个reducer的键值对都是按键排序好的,为了使在同一个reducer中,相同的左值是连续在一起的,我们还需要自定义的pair的比较方式:对pair进行比较时,只比较左值。
class Pair
method compareTo(Pair obj)
rect := this.left.compareTo(obj.left)
if(rect == 0)
if(this.right == "*")
return -1
else if(obj.right == "*")
return 1
return rect
class Mapper
method map(docid a,doc d)
foreach term w in doc
foreach term u in Neighbour(w)
Emit(Pair(w,u),1)
Emit(Pair(w,*),1)
class Partitioner
method getPartition(Pair key)
return key.getLeft().hashCode()
class Reducer
method setup
prev := NULL
rowsum := 0
method reduce(pair p,counts [c1,c2,...])
foreach count c in [c1,c2,...]
s := s+c
if(p.left != prev)
assert(p.right == "*")
prev := p.left
rowsum := s
else
Emit(p,s/rowsum)
上面的代码中,我们使mapper多输出了一项Emit(Pair(w,*),1),它的作用是使在reducer中不需要遍历所有具有相同左值的pair(w,u)就能提前计算出行和--仅依靠pair(w,*)就可计算出行和,这样在第一次遇到pair(w,u)时就可以计算出它的相对频率。为了使reducer最先遇到pair(w,*),我们定义在对pair排序时,在左值相同的情况下,右值为“*"者较小。
在一个reducer中出现的键值对的顺序是这样子的:
同样,仍然可以使用combiner进行优化,把local aggregate放在mapper中更好。
说到这里顺便扯一句,一般程序员在计算样本方差时会采用这个公式:
这个公式要求2趟扫描样本,第1趟扫描是为了计算出X的平均值,第2趟扫描是按照公式来计算。而统计学专业的学生知道计算方差还有另外一个公式:
只需要1趟扫描就可以把ΣXi2和ΣXi都算出来,直接代入公式就可算出方差。
Secondary Sorting
Hadoop只能保证同一个reducer中的健值对是按键排好序的,在Goole在MapReduce中提供一个可选的函数--在按键排序之后按value排序。考虑这么一种情形:mapper输出的键值对形如
key value
t1 (m1,r1988)
t1 (m2,r1987)
t2 (m1,r1990)
key已经排好序了,t1<t2。我们希望key相同的时候按value中的第2列排序,即希望的结果为
key value
t1 (m2,r1987)
t1 (m1,r1988)
t2 (m1,r1990)
Hadoop是不对value进行排序的,所以我们的解决方法是:把需要排序的valueu部分放到key里面去,即
key value
(t1,r1987) m2
(t1,r1988) m1
(t2,r1990) m1
当然这个时候需要自定义partitioner仅对pair的左值进行散列,且pair的比较规则是先比较左值,左值相等时再比较右值。
分享到:
相关推荐
### 高级算法设计技巧 - 算法的优化,包括循环优化、递归优化等。 - 特定问题的高级策略,例如网络流问题的Ford-Fulkerson算法等。 - 用于解决特定类型问题的启发式算法,例如用于旅行商问题(TSP)的遗传算法、模拟...
总的来说,"算法设计技巧与分析寻找多数元素"这一主题涵盖了从理论到实践的多个方面,包括算法设计、实现、优化和应用。通过学习和理解这个主题,我们可以提升在处理大数据集时的分析能力,同时增强对算法设计思维的...
可能会讨论到贪心算法、分治法、动态规划等基础设计技巧,并通过实例解释如何应用这些方法解决问题。此外,这部分也可能涉及算法的时间和空间复杂度分析,帮助学生理解算法效率的重要性。 第二部分,"第二部分_...
《数据算法:Hadoop/Spark大数据处理技巧》介绍了很多基本设计模式、优化技术和数据挖掘及机器学习解决方案,以解决生物信息学、基因组学、统计和社交网络分析等领域的很多问题。这还概要介绍了MapReduce、Hadoop和...
#### 四、MapReduce优化技巧 为了进一步提升MapReduce的性能,可以采取以下几种策略: - **数据压缩**:通过对输入数据进行压缩,可以减少网络传输的数据量,从而提高整体处理速度。 - **本地化读取**:尽可能地让...
这本书详细解析了MapReduce的架构设计和实现原理,不仅涵盖了基础概念,还深入探讨了高级话题和优化技巧,对于理解Hadoop及其在大数据处理中的作用至关重要。通过学习,读者能够掌握如何在实际项目中有效利用...
在大数据处理领域,MapReduce是一种广泛使用的编程模型,它能够高效地处理海量数据。...通过理解ItemCF的基本原理和MapReduce的工作流程,我们可以设计出高效的推荐算法,服务于大规模的在线服务。
在大数据处理领域,MapReduce是一种广泛使用的...这不仅涉及编程技巧,还包括对协同过滤算法的理解、大数据处理的思维方式以及优化策略。此外,由于使用Java实现,所以还涉及到Java编程基础和Hadoop生态系统的应用。
总之,《算法设计与分析09版PPT课件》是一个全面而深入的学习资源,它不仅教授了算法设计的基本技巧,还引入了概率算法和分布式算法的高级主题,对于提升学生的理论素养和实际操作能力具有重要作用。
MapReduce是一种分布式计算模型,由Google在2004年提出,主要用于处理和生成大规模数据集。这个模型将复杂的并行计算任务分解为两...通过熟练掌握这些高级技巧,你将能够设计和实现更高效、更灵活的数据处理解决方案。
通过学习和实践其中的源代码,我们可以了解到如何在Hadoop和Spark环境下设计和优化数据算法,同时提升我们的大数据处理技能。对于想进入大数据领域的开发者或者已经在该领域工作的专业人士来说,这都是一个不容错过...
这份PPT旨在帮助学生掌握高级算法的核心概念、设计思想和实现技巧,对于提升编程能力,尤其是解决复杂问题的能力具有重要作用。 在高级算法的课程中,通常会涉及到以下关键知识点: 1. **排序与搜索算法**:包括...
《MapReduce2.0源码分析与实战编程》是一本深度探讨Hadoop MapReduce核心框架的书籍,旨在帮助读者理解MapReduce的工作原理,并通过实际编程掌握其应用技巧。以下是各章节主要内容的概述: 第1章:MapReduce简介 本...
【基于Hadoop的异构网络协同过滤推荐算法设计】 本文主要探讨了如何利用Hadoop架构设计并实现一种异构网络协同过滤推荐算法。Hadoop是一个开源的分布式计算框架,广泛应用于大数据处理和分析,尤其在处理大规模数据...
MapReduce设计有强大的容错机制,如果某个节点故障,任务可以自动重定向到其他节点,保证了系统的高可用性。此外,数据的复制策略使得即使部分节点失效,也能保证数据的完整性。 5. **适用场景**: MapReduce适合...
Hadoop/Spark大数据处理技巧》介绍了很多基本设计模式、优化技术和数据挖掘及机器学习解决方案,以解决生物信息学、基因组学、统计和社交网络分析等领域的很多问题。这还概要介绍了MapReduce、Hadoop和Spark。, 主要...
这个压缩包集合了大约80篇关于算法的精华文章,涵盖了算法设计、分析、优化等多个方面。CSDN(China Software Developer Network)是中国知名的程序员社区,其论坛是技术交流的重要平台。这些文章可能包括了经典算法...
Java算法大全源码包是一个集合了近一百种算法实现的宝贵资源,对于学习和提升Java编程技巧,尤其是算法设计与分析能力来说,是极其重要的工具。这个源码包涵盖了从基础到高级的各种算法,旨在帮助开发者深入理解算法...