在大多数情况下, MapReduce可以作为一个通用的并行执行框架,充分利用数据的本地性。但是这种简单性是有代价的,设计者必须从以特定的方式组合在一起的一小部分组件的角度决定如何表达他的业务问题。
重新制定MapReduce的初始问题,通常有必要回答以下问题:
1. 怎么把一个大问题分解成多个小任务?更具体地说,你怎么分解问题以至于这些小任务可以并行执行?
2.你会选择哪对key/value作为每个任务的输出/输出?
3. 你如何汇总计算所需要的所有数据?更具体地说, 你怎么安排处理的方式,使所有必要的计算中的数据都同时在内存中?
我们要认识到,很多算法不能很容易地表示为一个单一的MapReduce作业。它往往需要把复杂的算法分解成一系列的作业,把其中一个作业的数据输出成为下一个作业的输入。
本节将会探讨在几个设计不同的实际MapReduce应用问题的例子(从简单到复杂)。所有的例子都会被描述为以下形式:
对问题简单的描述
Mapreduce作业的描述,包括:
1.Mapper的描述
2.Reducer的描述
这种情况下的mapreduce实施是非常简单的――唯一需要的就是mapper,单独的处理每个记录然后输出结果。在这个例子中,Mapreduce控制mappers的分布,提供调度和错误处理的所有支持。下面的例子展示了如何设计这种类型的应用程序。
人脸识别的例子
虽然不是经常作为Hadoop-related问题讨论,但是图像处理应用在mapreduce范例中是非常合适的。假设有一个人脸识别算法的应用,需要一个图像,识别一系列想要的特性,并产生一组识别结果。再假设需要在百万图片上做人脸识别。如果所有的图片以序列文件的形式存放在hadoop中,那么你可以用一个简单的map作业就可以实现并行处理。在这个例子中,输入的key/value是ImageID/Image,输出的key/value是ImageID/可特征识别列表。此外,一组可特征识别必须分布到所有的mapper(例如,利用分布式缓存)。
人脸识别作业
Mapper | 在这个作业中,mapper首先以可识别特征集进行初始化,对于每一个图像,一个map函数通过它的图像本身,以及可识别的列表来调用的人脸识别算法。识别的结果连同原来imageID一起从map中输出。 |
Result | 这个作业执行的结果是所有包含在原始图片中识别出来的图片。 |
注意:要实现完全独立的mappers/reducers。在mapreduce应用中的每一个mapper/reducer需要创建独自的输出文件。这意味着,人脸识别的作业的执行结果将是一组文件(相同目录下的),每一个包含了各自mapper的输出。如果需要把他们放入到一个单个的文件中。必须在人脸识别作业中添加一个单独的reducer。这个reducer是非常简单的。因为在这个例子中,每一个作为reduce的输入的key只有一个单独的value(这里假设图像的ID是唯一的),reducer只是把输入的key/value直接写入到输出文件。我们要知道在这个例子中尽管一个reducer极其简单,但是这种额外的作业明显的增加了作业的整体运行时间。这是因为额外的reducer分为shuffle和sort(不单单在map作业中出现),当图像的数量非常大时,将花费大量的时间。
这种情况的一个例子就是构建倒排索引。这种类型的问题需要所有的mapreduce步骤进行执行,需要shuffle和sort把所有的结果集合在一起。下面的例子展示了如何设计这种类型的应用。
倒排索引的例子
在计算机科学中,倒排索引是一个数据框架,用来存放了从内容(例如单词或者数字)到它在一个文档或一组文档里的位置的映射,如表3-6所示。倒排索引的目的是实现快速的全文搜索,在文档增加的时候增加处理成本为代价,倒排索引式的数据结构是典型搜索引擎的关键部分,优化了查找某些单词出现的文档的速度。
文档 | ||
ID | Title | Content |
1 | Popular | Football is Popular in US |
2 | Common Sport | Soccer is commonly played in Europe |
3 | National Sport | Cricket is played all over India |
… | … | … |
表2-1:文档结构
倒排索引 | ||||
Term | value | Document | Document | Document |
Title | popular | 1 | ||
Title | sport | 1 | 2 | 3 |
Title | common | 2 | ||
Title | national | 3 | ||
Content | football | 1 | ||
Content | is | 1 | 2 | 3 |
Content | popular | 1 | ||
… | … | … | … | … |
表2-2:倒排索引
要创建倒排索引,可以把每个文档(或者文档里行)给mapper。mapper可以解析出文档里的多个单词,然后输出[单词,词频]键值对。reducer可以只是一个识别,输出列表或者可以执行每个单词的一些统计汇总的功能。
注释在第九章你将学会更多关于如何利用Hbase来存储倒排的索引。
表2-3里展示了这个例子中mapreduce作业的实现。
表2-3 倒排索引的计算
处理阶段 | 描述 |
Mapper | 作业中,mapper的任务是构建一个包含一个单词索引的独特的记录和描述在文档里单词出现的信息。它读取每个输入的文档,解析,然后为文档里的每一个独特的单词创建一个索引描述符。该描述符包含文档的ID,文档里索引出现的次数,和任何附件的信息(比如从文档的开头索引位置的偏移量) ,每一个所以描述符被写出。 |
Shuffle和sort | Mapreduce的shuffle和sort过程会把所有的记录都按照索引值排序,确保reducer接受到所有相同key值的索引。 |
Reducer | 这项工作中,reducer的作用是构建一个倒排索引结构。根据系统的要求,可能有一个或多个reducer。Reducer得到所有给定索引的描述符,并生成一个索引记录,并写入到指定的索引存储。 |
Result | 该作业执行的结果是一组原始文档的倒排索引。 |
表2-3:倒排索引的计算
更多复杂的mapreduce应用需要将来自多个获取的数据(就是说连接数据)进行处理。
什么场景下用MapReduce
为了能使Mapreduce可以应用,下面必须符合:
1、? 要运行的计算必须可以组合,它指的是必须能对数据集下的小数据集进行计算。然后对部分结果合并。
2、? 数据集的大小要足够大(或者计算时间要足够长),当基础设施? 为独立的计算和合并结果不会对整体性能造成影响。
3、? 计算主要取决于于正在处理的数据集。用Hbase可以额外添加小的数据集。分布式缓存或者一些其他的技术。
然而,当数据集必须能随机的被访问去执行操作(例如,如果一个给定的数据集记录必须加上额外的记录来执行操作),在这种情境中,mapreduce是不适用的。然后在这种情况下,可以运行额外的mapreduce作业来为计算“准备”数据。
另外一些不适用mapreduce的问题是递归问题(例如,斐波那契问题)。在这种情况下,mapreduce不适用是因为当前value值的计算需要前一个的知识。这就意味着你不能把它们分解成为可以单独运行的子计算(sub computation)。
如果一个数据足够的小,小到可以放到一个机器的内存里,作为一个独立的应用程序可能会处理的更快。在这种情况下,使用mapreduce,会使执行变得不必要的复杂,通常会更慢。
注意,(keep it in mind),虽然一大类的算法不能直接应用在mapreduce的实施上。但是对于同样的基本问题,往往存在可以通过利用mapreduce解决的替代解决方案。这种情况下,使用mapreduce通常是有利的,因为mapreduce是在有丰富的hadoop生态系统中执行的(支持更容易的改进的实施),并与其它应用程序的集成。
最后 你应该记住Mapreduce本质上是一个批处理实现。决不能用于在线计算(比如在线用户请求的实时计算)。
常见的Mapreduce设计陷阱
当你设计mapreduce应用的时候,下面列举的是需要注意和避免的。
?? 当map任务中对数据分片的时候。要确保没有创建过多(通常情况下,mapper的数量应该在数百,而不是数千)或者过少的分片。正确数量的mapper对应用程序有以下优势:
1、? 拥有过多的mapper会造成调度和基础设施的开销,在极端情况下,甚至会杀死一个Jobtracker。另外,过多的mapper通常会提高整体资源的利用率(因为创建过多的JVM)和执行时间(因为执行slot的数量是有限的)。
2、? Mapper太少会导致集群不能充分利用,给一些节点(实现运行mapper的节点)造成过度负载。此外,在有大型map任务情况下,重试和推测执行的情况会变得非常昂贵的代价且会花费更长的时间。
3、? 大量小型的mapper会造成大量的寻求,shuffle map输出给reducer的结果时。当把map的输出结果传递给reducer时,它也会造成过多的连接。
?? 为应用程序配置Reducer的数量是另一个重要因素,reducer太多(通常是成千)或太少都会使效率降低。
1、? 除了调度和基础设施的开销外,大量的reducer会创建太多的输出文件(记住,每个reducer创建自己的输出文件),对namenode有负面的影响。当有其他作业利用该mapreduce作业的结果时,它会变得更为复杂。
2、? 太少的reducer和太少的mapper一样,造成同样的负面影响-不能充分利用集群和非常昂贵(代价)的回调。(retry)
?? 合理利用作业计数器
1、? 计数器在跟踪少量的,重要的,全局的信息是适用的(在Chapter 5了解更多关于使用计数器的详情)。他们绝对不是只是整合非常细粒度统计的应用程序。
2、? 计数器的代价非常高,因为Jobtracker在应用程序的整个持续时间内,必须维持每个map/reduce任务的每一个计数器。
?? 对应用程序的输出,选择一个合适的压缩机制来改善写性能(压缩速度vs压缩效率)。
?? 为mapreduce作业的输出选择一个合适的文件格式。利用序列化文件通常是最好的选择,因为它们可以被压缩和分片。
?? 当单个输入/输出文件很大的时候,考虑使用更大的输出块大小(多个千兆字节大小)。
1、? 尽量避免在map和reduce方法中添加新的类的实例。这些方法在执行过程中会循环执行多次。也就是说类的创建和处理将增加执行的时间,为垃圾收集器增加额外的工作。比较好的方法是在相应的set()方法中创建大量的中间类,然后重写map和reduce方法。
2、? 不要用分布式缓存来移动大数量的工件或者非常大的工件(每个百兆字节)。分布式缓存的设计是用来分布小部分中等大小的工件,几兆到几十兆大小。
3、? 处理少量的数据时,不要创建成百上千个小作业式的工作流。
4、? 不直接从reducer或者mapper直接写入用户自定义的文件。Hadoop中当前实现文件写的功能是单线程的,这意味着当多个mapper/reducer试图写文件时,这个执行将被序列化。
5、? 不要创建这样的mapreduce功能,扫描一个Hbase表来创建一个新的Hbase表(或者写入同样的表中)? 。TableInputFormat是为基于具有时间敏感性的表扫描的Hbase和Mapreduce的实现。? 另一方面,Hbase写功能会因为Hbase表的分割而产生一定的写延迟。 结果是Region服务器会挂掉,然后你会失去一些数据。最好的解决方案是把作业分割成两个作业。一个扫描表并想HDFS中写入中间结果。另一个从HDFS读取数据并写入到HBase中。
来自:
原文地址:MR总结(二)-Mapreduce程序设计, 感谢原作者分享。