MapReduce
文章目录
- MapReduce
- 个人指导
- 目的【Purpose】
- 必备条件【Prerequisites】
- 综述【Overview】
- 输入输出【Inputs and Outputs】
- 用户接口【MapReduce - User Interfaces】
- 有效负载【Payload】
- 作业配置【Job Configuration】
- 任务执行和环境【Task Execution & Environment】
- 作业提交和监控【Job Submission and Monitoring】
- 作业输入【Job Input】
- 作业输出【Job Output】
- 其他有用的特性【Other Useful Features】
个人指导
目的【Purpose】
- 该文档作为一份个人指导全面性得描述了所有用户使用Hadoop Mapreduce框架时遇到的方方面面。
必备条件【Prerequisites】
- 确保Hadoop安装、配置和运行。更多细节:
- 初次使用用户配置单节点。
- 配置大型、分布式集群。
综述【Overview】
- Hadoop Mapreduce是一个易于编程并且能在大型集群(上千节点)快速地并行得处理大量数据的软件框架,以可靠,容错的方式部署在商用机器上。
- MapReduce Job通常将独立大块数据切片以完全并行的方式在map任务中处理。该框架对maps输出的做为reduce输入的数据进行排序,Job的输入输出都是存储在文件系统中。该框架调度任务、监控任务和重启失效的任务。
- 一般来说计算节点和存储节点都是同样的设置,MapReduce框架和HDFS运行在同组节点。这样的设定使得MapReduce框架能够以更高的带宽来执行任务,当数据已经在节点上时。
- MapReduce 框架包含一个主ResourceManager,每个集群节点都有一个从NodeManager和每个应用都有一个MRAppMaster。
- 应用最少必须指定输入和输出的路径并且通过实现合适的接口或者抽象类来提供map和reduce功能。前面这部分内容和其他Job参数构成了Job的配置。
- Hadoop 客户端提交Job和配置信息给ResourceManger,它将负责把配置信息分配给从属节点,调度任务并且监控它们,把状态信息和诊断信息传输给客户端。
- 尽管 MapReduce 框架是用Java实现的,但是 MapReduce 应用却不一定要用Java编写。
- Hadoop Streaming 是一个工具允许用户创建和运行任何可执行文件。
- Hadoop Pipes 是兼容SWIG用来实现 MapReduce 应用的C++ API(不是基于JNI)。
输入输出【Inputs and Outputs】
- MapReduce 框架只操作键值对,MapReduce 将job的不同类型输入当做键值对来处理并且生成一组键值对作为输出。
- Key和Value类必须通过实现Writable接口来实现序列化。此外,Key类必须实现WritableComparable 来使得排序更简单。
- MapRedeuce job 的输入输出类型:
- (input) ->map-> ->combine-> ->reduce-> (output)
用户接口【MapReduce - User Interfaces】
- 这部分将展示 MapReduce 中面向用户方面的尽可能多的细节。这将会帮助用户更小粒度地实现、配置和调试它们的Job。然而,请在 Javadoc 中查看每个类和接口的综合用法,这里仅仅是作为一份指导。
- 让我们首先来看看Mapper和Reducer接口。应用通常只实现它们提供的map和reduce方法。
- 我们将会讨论其他接口包括Job、Partitioner、InputFormat和其他的。
- 最后,我们会讨论一些有用的特性像分布式缓存、隔离运行等。
有效负载【Payload】
- 应用通常实现Mapper和Reducer接口提供map和reduce方法。这是Job的核心代码。
Mapper
- Mappers将输入的键值对转换成中间键值对。
- Maps是多个单独执行的任务将输入转换成中间记录。那些被转换的中间记录不一定要和输入的记录为相同类型。输入键值对可以在map后输出0或者更多的键值对。
- MapReduce 会根据 InputFormat 切分成的各个 InputSplit 都创建一个map任务
- 总的来说,通过 job.setMapperClass(Class)来给Job设置Mapper实现类,并且将InputSplit输入到map方法进行处理。应用可复写cleanup方法来执行任何需要回收清除的操作。
- 输出键值对不一定要和输入键值对为相同的类型。一个键值对输入可以输出0至多个不等的键值对。输出键值对将通过context.write(WritableComparable,Writable)方法进行缓存。
- 应用可以通过Counter进行统计。
- 所有的中间值都会按照Key进行排序,然后传输给一个特定的Reducer做最后确定的输出。用户可以通过Job.setGroupingComparatorClass(Class)来控制分组规则。
- Mapper输出会被排序并且分区到每一个Reducer。分区数和Reduce的数目是一致的。用户可以通过实现一个自定义的Partitioner来控制哪个key对应哪个Reducer。
- 用户可以随意指定一个combiner,Job.setCombinerClass(Class),来执行局部输出数据的整合,将有效地降低Mapper和Reducer之间的数据传输量。
- 那些经过排序的中间记录通常会以(key-len, key, value-len, value)的简单格式储存。应用可以通过配置来决定是否需要和怎样压缩数据和选择压缩方式。
有多少个maps?
- maps的数据通常依赖于输入数据的总长度,也就是,输入文档的总block数。
- 每个节点map的正常并行度应该在10-100之间,尽管每个cpu已经设置的上限值为300。任务的配置会花费一些时间,最少需要花费一分钟来启动运行。
- 因此,如果你有10TB的数据输入和定义blocksize为128M,那么你将需要82000 maps,除非通过Configuration.set(MRJobConfig.NUM_MAPS, int)(设置一个默认值通知框架)来设置更高的值。
Reducer
- Reduce处理一系列相同key的中间记录。
- 用户可以通过 Job.setNumReduceTasks(int) 来设置reduce的数量。
- 总的来说,通过 Job.setReducerClass(Class) 可以给 job 设置 recuder 的实现类并且进行初始化。框架将会调用 reduce 方法来处理每一组按照一定规则分好的输入数据,应用可以通过复写cleanup 方法执行任何清理工作。
- Reducer有3个主要阶段:混洗、排序和reduce。
Shuffle
- 输出到Reducer的数据都在Mapper阶段经过排序的。在这个阶段框架将通过HTTP从恰当的Mapper的分区中取得数据。
Sort
- 这个阶段框架将对输入到的 Reducer 的数据通过key(不同的 Mapper 可能输出相同的key)进行分组。
- 混洗和排序阶段是同时进行;map的输出数据被获取时会进行合并。
Secondary Sort
- 如果想要对中间记录实现与 map 阶段不同的排序方式
- 可以通过Job.setSortComparatorClass(Class) 来设置一个比较器 。
- Job.setGroupingComparatorClass(Class) 被用于控制中间记录的排序方式
- 这些能用来进行值的二次排序。
Reduce
- 在这个阶段reduce方法将会被调用来处理每个已经分好的组键值对。
- reduce 任务一般通过 Context.write(WritableComparable, Writable) 将数据写入到FileSystem。
- 应用可以使用 Counter 进行统计。
- Recuder 输出的数据是不经过排序的。
有多少个Reduce?
- 合适的 reduce 总数应该在 节点数*每个节点的容器数*0.95 至 节点数*每个节点的容器数*1.75 之间。
- 当设定值为0.95时,map任务结束后所有的 reduce 将会立刻启动并且开始转移数据,当设定值为1.75时,处理更多任务的时候将会快速地一轮又一轮地运行 reduce 达到负载均衡。
- reduce 的数目的增加将会增加框架的负担(天花板),但是会提高负载均衡和降低失败率。
- 整体的规模将会略小于总数,因为有一些 reduce slot 用来存储推测任务和失败任务。
Reducer None
- 当没有 reduction 需求的时候可以将 reduce-task 的数目设置为0,是允许的。
- 在这种情况当中,map任务将直接输出到 FileSystem,可通过 FileOutputFormat.setOutputPath(Job, Path) 来设置。该框架不会对输出的FileSystem 的数据进行排序。
Partitioner
- Partitioner对key进行分区。
- Partitioner 对 map 输出的中间值的 key(Recuder之前)进行分区。分区采用的默认方法是对 key 取 hashcode。分区数等于 job 的 reduce 任务数。因此这会根据中间值的key 将数据传输到对应的 reduce。
- HashPartitioner 是默认的的分区器。
Counter
-
计数器是一个工具用于报告 Mapreduce 应用的统计。
-
Mapper 和 Reducer 实现类可使用计数器来报告统计值。
-
Hadoop Mapreduce 是普遍的可用的 Mappers、Reducers 和 Partitioners 组成的一个库。
作业配置【Job Configuration】
- Job类用来表示MapReduce作业的配置。Job是用户用来描述MapReduce job在Hadoop框架运行的主要接口。Hadoop将尽量如实地按照job所描述的来执行。然而:
- 一些配置参数已经被管理员标注为不可更改的因此不能被改变。
- 一些参数是直接设置的(如Job.setNumReduceTasks(int)),有一些参数是跟框架或者任务配置之间有微妙的互相影响并且复杂的设置。
- Job 典型地用于指定Mapper、Combiner、Partitioner、Reducer、InputFormat、OutputFormat实现类。 FileInputFormat指定输入文档的设定(FileInputFormat.setInputPaths(Job, Path…)/FileInputFormat.addInputPath(Job, Path))和(FileInputFormat.setInputPaths(Job, String…)/FileInputFormat.addInputPaths(Job, String))和输出文件应该写入通过(FileOutputFormat.setOutputPath(Path)).
- 随意地,Job也常用来指定job的其他高级配置,例如比较器、文档置于分布式缓存、中间记录是否压缩和怎样压缩, job任务是否已预测的方式去执行,每个任务的最大处理量等等。
- 当然,用户可以使用来设置或者获得应用所需要的任何参数。然而,使用分布式缓存来存储大量的可读数据。
任务执行和环境【Task Execution & Environment】
- MRAppMaster 在一个单独的jvm中运行Mapper/Reducer任务做为一个子进程。
- 子任务继承父MRAppMaster的运行环境。用户可以通过(mapreduce.{map|reduce}.java.opts和配置参数例如通过 Djava.library.path=<>可以设置非标准的路径用于运行时搜索库)指定额外的设置。如果mapreduce.{map|reduce}.java.opts参数包含@taskid@ 符号那么Mapreduce任务将会被修改为taskid的值。
- 下面有个例子;配置多个参数和代替,展示jvm gc 日志,和 JVM JMX 代理用于无密码登录以致可以连接JConsole来监控子程序的内存、线程和线程垃圾回收。也分别设置了map和reduce的最大堆内存为512M和1024M。它也给子jvm添加了额外的路径通过java.library.path参数。
<property>
<name>mapreduce.map.java.opts</name>
<value>
-Xmx512M -Djava.library.path=/home/mycompany/lib -verbose:gc -Xloggc:/tmp/@taskid@.gc
-Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false
</value>
</property>
<property>
<name>mapreduce.reduce.java.opts</name>
<value>
-Xmx1024M -Djava.library.path=/home/mycompany/lib -verbose:gc -Xloggc:/tmp/@taskid@.gc
-Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false
</value>
</property>
内存管理【Memory Management】
- 用户或者管理员可以使用mapreduce.{map|reduce}.memory.mb指定子任务或者任何子进程运行的最大虚拟内存。需要注意的这里的值是针对每个进程的限制。{map|reduce}.memory.mb的值是以MB为单位的。并且这个值应该大于等于传给JavaVM的-Xmx的值,要不VM可能会无法启动。
- 说明:mapreduce.{map|reduce}.java.opts只用来设置MRAppMaster发出的子任务。守护线程的内存选项配置在Configuring the Environment of the Hadoop Daemons.
- 框架的一些组成部分的内存也是可配置的。在map和reduce任务中,性能可能会受到并发数的调整和写入到磁盘的频率的影响。文件系统计数器监控作业的map输出和输入到reduce的字节数对于调整这 些参数是宝贵的。
Map参数【Map Parameters】
-
Map发出的数据将会被序列化在缓存中和源数据将会储存在统计缓存。正如接下来的配置所描述的,当序列化缓存和元数据超过设定的临界值,缓存中的内容将会后台中写入到磁盘中而map将会继续输出记录。当缓存完全满了溢出之后,map线程将会阻塞。当map任务结束,所有剩下的记录都会被写到磁盘中并且磁盘中所有文件块会被合并到一个单独的文件。减小溢出值将减少map的时间,但更大的缓存会减少mapper的内存消耗。
-
其他说明:
- 当任何一个spill超出的临界值,收集还会持续进行直到结束。例如,当mapreduce.map.sort.spill.percent 设置为0.33,那么剩余的缓存将会继续填充而spill会继续运行,而下一个spill将会包含所有的收集的记录,而当值为0.66,将不会产生另一个spills。也就是说,临界值会被触发,但不会阻塞。
- 一个记录大于序列化缓存将会第一时间触发溢出,并且会被写到一个单独的文件。无论是否有定义都会第一时间通过combiner进行传输。
Name Type Description mapreduce.task.io.sort.mb int 序列化的累积(累积)大小和会计缓冲区存储记录发出地图,以兆字节为单位。 mapreduce.map.sort.spill.percent float 序列化缓冲区中的软限制。到达后,线程将开始将内容泄漏到后台的磁盘。
Shuffle/Reducer参数【Shuffle/Reduce Parameters】
-
正如前面提到的,每个reduce都会通过HTTP在内存中拿到Partitioner分配好的数据并且定期地合并数据写到磁盘中。如果map输出的中间值都进行压缩,那么每个输出都会减少内存的压力。下面这些设置将会影响reduce之前的数据合并到磁盘的频率和reduce过程中分配给map输出的内存。
-
其他说明:
- 如果一个map输出大于分配给用于复制map输出的内存的25%,那么将会直接写到磁盘不会通过内存进行临时缓存。
- 当运行一个combiner,高的临界值和大的缓存的理由将没有效果。在map输出进行合并之前,combiner将会进行溢出写到磁盘的操作。在一些例子当中,耗费资源combine map输出数据获得更小的溢出会比粗暴地增加缓存大小使得recuder的时间更少。
- 当合并内存中的map数据到磁盘来开始recuder时,如果磁盘中已经存在部分切片数据的话,那么必须将内存中的数据作为磁盘中间数据的一部分来进行合并操作。
Name Type Description mapreduce.task.io.soft.factor int 指定同时合并磁盘上的段数。它限制了合并期间打开的文件和压缩编解码器的数量。如果文件的数量超过这个限制,合并将进行几次传递。尽管这个限制也适用于映射,但大多数作业都应该配置为不太可能达到这个限制。 mapreduce.reduce.merge.inmem.thresholds int 在合并到磁盘之前提取到内存中的已排序映射输出的数量。与前面说明中的溢出阈值一样,这不是定义分区单元,而是定义触发器。在实践中,这个值通常设置得很高(1000)或禁用(0),因为合并内存段通常比从磁盘合并花费更少(参见本表后面的注释)。此阈值仅影响洗牌期间内存中合并的频率。 mapreduce.reduce.shuffle.merge.percent float 启动内存中合并前获取的映射输出的内存阈值,表示为分配给内存中存储映射输出的内存的百分比。由于无法装入内存的map输出可能会被延迟,因此设置这个值可能会降低获取和合并之间的并行性。相反,对于输入可以完全装入内存的reduce,值高达1.0是有效的。此参数仅影响洗牌期间内存中合并的频率。 mapreduce.reduce.shuffle.input.buffer.percent float 通常在mapreduce.reduce.java中指定的相对于最大堆大小的内存百分比。选项——可在洗牌期间分配给存储映射输出的选项。尽管应该为框架留出一些内存,但通常情况下,将内存设置到足够高以存储大的和大量的map输出是有利的。 mapreduce.reduce.input.buffer.percent float 相对于最大堆大小,在减少过程中可以保留映射输出的内存的百分比。当开始减少时,映射输出将被合并到磁盘,直到那些剩余的输出在此定义的资源限制之下。默认情况下,在开始减少之前,将所有映射输出合并到磁盘,以最大化减少可用的内存。对于内存密集型更少的减少,应该增加这一点,以避免到磁盘的传输。
配置参数【Configured Parameters】
- 说明:流式任务的执行过程中,名字以mapreduce开头的参数会被改变。符号(.)会变成(_)。例如,mapreduce.job.id会变成mapreduce_job_id和mapreduce.job.jar会变成mapreduce_job_jar。在Mapper/Reducer中使用带下划线的参数名来获得对应的值。
Name | Type | Description |
---|---|---|
mapreduce.job.id | String | The job id |
mapreduce.job.jar | String | job.jar location in job directory |
mapreduce.job.local.dir | String | The job specific shared scratch space |
mapreduce.task.id | String | The task id |
mapreduce.task.attempt.id | String | The task attempt id |
mapreduce.task.is.map | boolean | Is this a map task |
mapreduce.task.partition | int | The id of the task within the job |
mapreduce.map.input.file | String | The filename that the map is reading from |
mapreduce.map.input.start | long | The offset of the start of the map input split |
mapreduce.map.input.length | long | The number of bytes in the map input split |
mapreduce.task.output.dir | String | The task’s temporary output directory |
任务日志【Task Logs】
- NodeManager 会读取stdout、sterr和任务的syslog并写到${HADOOP_LOG_DIR}/userlogs。
分布式缓存库【Distributing Libraries】
- 分布是缓存也可以在map/reduce任务中用来分不是存储jars和本地库。子JVM经常将它的工作路径添加到java.librarypath和LD_LIBRARY_PATH.因此缓存的库能通过System.loadLibrary 或者 System.load 来加载。更多关于如何通过分布式缓存来加载第三方库参考Native Libraries.
作业提交和监控【Job Submission and Monitoring】
- Job 是用户Job与ResourceManager交互的主要接口。
- Job 提供工具去提交jobs、跟踪他们的进程、使用组成任务的报告和日志,获得MapReduce集群的状态信息和其他。
- Job的提交包含以下内容:
- 检查Job的输入输出指定
- 计算Job的InputSplit的值
- 如果必要的话,设置分布式缓存的需求信息。
- 将Job的jar和configuration复制到Mapreduce系统的文件系统路径下。
- 将Job提交到ResourceManger并且随时监控它的状态。
- Job的历史文件也被记录到用户通过mapreduce.jobhistory.intermediate-done-dir and mapreduce.jobhistory.done-dir指定的路径下,默认是Job的输出路径。
- 用户可以通过下面的指令来查看指定路径下的所有的历史记录。
- $ mapred job -history output.jhist
- 这个命令可以打印job的细节,失败和杀死Job的技巧。
- 用以下的命令可以考到更多关于Job例如成功任务和每个任务的目的细节。
- $ mapred job -history all output.jhist
- 一般来说用户使用Job来创建应用,描述Job的各个方面,提交Job和监控它的进程。
作业控制【Job Control】
- 用户可能需要将多个任务串行实现复杂任务而没办法通过一个MapReduce任务实现。这是相当容易,job的output通常是输出到分布式缓存,而输出,依次作为下一个任务的输入。
- 然而,这也意味确保任务的完成(成功/失败)的义务是完全建立在客户端上。在这种情况下,各种作业的控制选项有:
- Job.submit() :提交作业给集群并立刻回复
- Job.waitForCompletion(boolean) :提交作业给集群并且等待它完成。
作业输入【Job Input】
-
InputFormat描述MapReduce Job的输入规定。
-
MapReduce框架依赖Job的InputFormat:
- 使Job的输入设定生效。
- 将输入文件分割成逻辑上的输入块实例,并将每一输入块分配给单独的Mapper。
- 提供RecordReader实现用于收集从逻辑输入块的记录输入到Mapper中。
-
那些默认的基于InputFormat的实现,通常来说FileInputForamt的子类,基于总字节数将输入基于字节数分成逻辑输入块实例,然而,FileSystem的块大小将是inputSplits的上限值,下限值可以通过mapreduce.input.fileinputformat.split.minsize来设置。
-
很明显,很多应用必须重视记录的边界,因存在着输入大小不足以逻辑分割。在这种情况,应用应当实现一个RecordReader,负责在单独任务中处理记录边界和显示,面向记录的逻辑视图。
-
TextInputForamt是默认的InputForamt。
-
如果job的InputForamt是TextInputFormat,框架会对输入文件进行检测,如果扩展名为.gz那么会自动用合适的压缩编码器进行解压。然而,必须说明的是经过压缩的文件将不能被切割并且每一个压缩文件都必须完全在一个Mapper单独处理。
输入块【InputSplit】
- 输入块表示每个单独Mapper处理的数据。
- 通常来说,输入块代表输入的面向字节视图,而RecordReader代表的是面向记录视图。
- FileSplit是默认的InputSplit。mapreduce.map.input.file设置用于逻辑分割的输入路径。
记录读取器【RecordReader】
- RecordReader从InputSplit读取键值对。
- 通常来说,RecordReader将输入的面向字节视图转换成面向记录视图并输入到Mapper的实现类进行处理。RecordReader因此承担起处理记录边界和显示任务的Keys和Values的责任。
作业输出【Job Output】
-
OutputFormat 描述MapReduce Job的输出规定。
-
MapReduce 框架依赖于Job的OutputFormat:
- 使job的输出设置生效;例如,检查输出路径是否已经存在。
- 提供RecordWriter实现用于输出文件。输出文件储存在FileSystem。
输出提交器【OutputCommitter】
- OutputCommitter 描述着MapReduce的任务输出的提交。
- MapReduce依赖于Job的输出提交器:
- 初始化时设置Job。例如,job的初始化过程中创建临时输出路径。当Job处于准备阶段和初始化任务之后,Job通过一个单独的任务完成创建。,一旦任务的创建完成之后,job将会转成运行状态。
- Job完成之后清除Job。例如,Job完成后移除临时输出路径。Job结束之时用一个单独的任务完成Job的清除。在完成对任务的清除之后Job会声明SUCCEDED/FAILED/KILLED.
- 设置任务临时输出。在任务的初始化过程中,任务设置作为任务的一部分来完成。
- 检查一个任务是否需要提交。这将避免一个不需要提交的任务执行提交程序。
- 提交任务的输出。一旦任务完成,任务将会提交它的输出如果需要的话。
- 放弃任务提交。如果任务已经失败或者被杀死,那么输出将会被清除掉。如果任务因为意外没有被清除掉,那么一个单独的任务将会被运行来执行清除工作。
- FileOutputCommitter是默认的OutputCommitter。Job 创建/清除任务占有map或者reduce容器,无论NodeManager是否可用。Job的清除任务,任务的清除任务和Job的创建任务拥有最高的优先级。
任务副文件【Task Side-Effect Files】
- 在一些应用当中,组成的任务必须创建一些其他文档,跟实际输出不同的文档。
- 在这些情况当中将会同时存在两个Mapper或者Reducer实例去打开或者写到FileSystem中相同的文档。因此应用开发者将会获取独一无二的任务目的(使用目的ID,假如say attempt_200709221812_0001_m_000000_0),不仅是每个任务。
- 说明:${mapreduce.task.output.dir}的值在一个特定任务执行过程中实际上是${mapreduce.output.fileoutputformat.outputdir}/_temporary/_{$taskid}的值,而这个值是由MapReduce框架设定的。所以,MapReduce任务利用这个特性从FileOutputForamt.getWorkOutPath(Context)返回的路径创建副文档。
- 整个讨论适用于作业有map但没有reduce的情况,因此map的output直接写到HDFS.
记录输出器【RecordWriter】
- RecordWriter将键值对的输出写到输出文件中。
- RecordWriter实现类将job的输出写到FileSytem。
其他有用的特性【Other Useful Features】
提交作业到队列中【Submitting Jobs to Queues】
- 用户提交job到队列中。队列,也就是job的集合,允许系统提供特定的功能。例如,队列使用ACLS来控制哪些用户可以提交队列。Hadoop Schedulers是队列的主要使用者。
- Hadoop设置一个单独的强制的队列,称之为“默认”。队列的名称是在Hadoop-site配置文件中的mapreduce.job.queuename>属性决定的。一些作业调度器支持多个的队列,例如容量调度器。
- 一个作业通过mapreduce.job.queuename属性或者Configuration.set(MRJobConfig.QUEUE_NAME, String)API来定义一个队列。设置队列的名字是可选的。如果一个作业被提交时并没有设置队列名称,那么队列名称为“默认”。
计数器【Counters】
- 计数器是全局计数器,由MapReduce框架或者应用定义。每一个计数器都可以是任何枚举类型。Counters of a particular Enum are bunched into groups of type Counters.Group。
- 应用可以定义任意计数器和通过 Counters.incrCounter(Enum, long) 或者Counters.incrCounter(String, String, long)来更新在map/reduce方法中。这些计数器是通过框架进行全局计算的。
分布式缓存【DistributedCache】
- 分布式缓存有效地分布存储应用专用的、大的、只读的文件。
- 分布是缓存是MapReduce框架提供给应用用于缓存文件(文本,档案、jar包和其他)。
- 应用可以通过urls (hdfs://)在Job中指定文件的缓存路径。分布式缓存假设通过hdfs:// urls指定的文件已经存在现在的FileSystem。
- 这个框架将在某个从属节点执行任何任务之前复制必要的文件到该节点上。它的高效源于这样的事实:每个作业只复制一次到那些能够存档但是还没存档的节点上。
- 分布式缓存跟踪缓存文件的修改时间戳。显然当作业在执行时缓存文件不应该被应用或者外部修改。
- 分布式缓存可以用来分布缓存简单的、只读的的数据或者文本文档和更复杂类型例如档案和Jar包。档案(zip, tar, tgz and tar.gz files)指的是未存档到从属节点的。文档是有执行权限的。
- 文件可以通过设置mapreduce.job.cache.{files|archives}属性来分配存储。如果有更多的文件需要存储,那么在用逗号隔开路径即可。该属性还可以通过Job.addCacheFile(URI)/ Job.addCacheArchive(URI) and Job.setCacheFiles(URI[])/ Job.setCacheArchives(URI[]) 来设置,URL的格式为hdfs://host:port/absolute-path#link-name。文件可以通过命令-cacheFile/-cacheArchive来实现分配存储。
- 分布式缓存也可以用作一个基本的软件分发机制用于map/reduce任务。它也可以用来分布存储jar包和本地库。Job.addArchiveToClassPath(Path) or Job.addFileToClassPath(Path) api可以用来缓存文件/jars并且子Jvm也会将它们添加到类路径下。通过设置mapreduce.job.classpath.{files|archives}属性也可以达到同样效果。同样地缓存文件通过符号链接到任务的工作路径来分布缓存本地库和加载它们。
私有和公共分布式缓存文件 【Private and Public DistributedCache Files】
- 分布式缓存文件可以是私有的或者公有的,以确定它们是否可以被分享到从属节点。
- 私有分布式缓存文件被缓存在局部路径属于那些作业需要这些文件的用户。这些文件只可以被指定用户的所有任务和Job使用,而这些节点的其他用户就不能使用。分布式缓存文档在它所上传的文件系统中通过他的权限变成私有的,文件系统通常为HDFS.如果这些文档没有全局读取权限,或者它的路径没有全局的可执行查找权限,那么这些文档就是私有的。
- 公有分布式缓存文档被缓存在一个全局路径并且文件被设置为对所有用户都可见。这些文件可以被所有节点上的所有用户分享。分布式缓存文件在它所上传的文件系统上通过他的权限变成公有的,文件系统通常为HDFS。如果文件具有全局可读权限,并且他的路径具有全局的可执行查找权限,那么它就是公有的。也就是说,如果用户想要使文件对所有用户可见可操作,那么文件权限必须是全局可读和他的路径权限必须是全局可执行。
分析器【Profiling】
- 分析器是一个工具可以用来获取2到3个Java内置分析器关于map和reduce的分析样本。
- 用户可以通过 mapreduce.task.profile来指定系统是否要收集某个作业的一些任务分析信息。这个值也可以通过Configuration.set(MRJobConfig.TASK_PROFILE, boolean) api来设置。如果这个值为真,那么任务分析将会生效。分析器的信息将储存在用户的log路径下。该属性默认是不生效的。
- 一旦用户配置了该属性,那么他/她就可以通过 mapreduce.task.profile.{maps|reduces} 来设置MapReduce任务的范围。这个值也可以通过Configuration.set(MRJobConfig.NUM_{MAP|REDUCE}_PROFILES, String) api来设置。默认的值为0-2。
- 用户也可以通过配置mapreduce.task.profile.params属性来指定分析器的的参数。这个值也可以通过api Configuration.set(MRJobConfig.TASK_PROFILE_PARAMS, String)来设置。假如字符串里面包含%s,那么将会在任务执行时被替换成分析输出文件的名字。这些参数将会在命令行中传输给任务所在的子JVM。默认的参数的值为-agentlib:hprof=cpu=samples,heap=sites,force=n,thread=y,verbose=n,file=%s。
调试器【Debugging】
- MapReduce框架提供一个工具用来运行用户提供的脚本用于调试。当一个MapReduce任务失败,用户可以运行调试脚本,去处理任务log。脚本可以读取任务的stdout、stderr输出、syslog和jobconf。调试脚本的stdout和sterr输出将会作为Job UI的一部分显示出来。
- 在接下来的部分我们将讨论如何提交一个调试脚本到作业中。脚本文件需要提交和存储在框架中。
如何分发脚本文件【How to distribute the script file】
- 用户需要使用分布式缓存来分发和符号链接脚本文件。
如何提交脚本【How to submit the script】
- 通过mapreduce.map.debug.script 和nd mapreduce.reduce.debug.script属性来分别设置map和reduce任务的调试脚本是一个快速的提交调试脚本的方法。这些属性可以通过APIs Configuration.set(MRJobConfig.MAP_DEBUG_SCRIPT, String) 和 Configuration.set(MRJobConfig.REDUCE_DEBUG_SCRIPT, String)来设置。在流式编程模式,可以通过命令行选项 -mapdebug 和 –reducedebug来分别设置map和reduce的调试脚本用于调试。
- 脚本的参数是任务的标准输出、标准错误、系统日志和作业配置文档。调试命令,运行在某个Mapreduce任务失败的节点上,是$script $stdout $stderr $syslog $jobconf $program。
- 拥有C++程度的Pipes项目在命令中增加第五个参数。因此命令如下:$script $stdout $stderr $syslog $jobconf $program
默认行为【Default Behavior】
- 在Pipes中,默认的脚本是运行在GDP的核心转储,打印堆跟踪和运行线程的信息。
数据压缩【Data Compression】
- Hadoop MapReduce提供一个功能让应用开发指定压缩方式用于map输出的中间数据和job-outputs也就是reduce的输出。它也捆绑着实现zlib压缩算法的压缩编码器。支持gzip、bzip2、snappy和lz4文件格式的文档。
- Hadoop也提供上述编码器的本地实现,因为性能和Java库不支持的原因。更多关于它们的使用细节和可用性可参考官方文档。
中间输出【Intermediate Outputs】
- 应用可以通过Configuration.set(MRJobConfig.MAP_OUTPUT_COMPRESS, boolean) api来设置是否对map的输出进行压缩和Configuration.set(MRJobConfig.MAP_OUTPUT_COMPRESS_CODEC, Class) api指定压缩编码器。
作业输出【Job Outputs】
- 应用可以通过FileOutputFormat.setCompressOutput(Job, boolean) api来控制是否对作业输出进行压缩和通过FileOutputFormat.setOutputCompressorClass(Job, Class)api来设置压缩编码器。
- 如果作业的输出是以SequenceFileOutputFormat格式存储的,那么需要序列化。压缩类型通过SequenceFileOutputFormat.setOutputCompressionType(Job, SequenceFile.CompressionType) api来指定。
跳过不良记录【Skipping Bad Records】
-
Hadoop提供一个选项当执行map输入时可以跳过某一组确定的坏数据。应用可以通过SkipBadRecords 类来控制特性。
当map任务中某些输入一定会导致崩溃时可以使用这个属性。这通常发生在map函数中的bug。通常地,用户会修复这些bug。然而,某些时候不一定有用。这个bug可能是第三方库导致的,例如那些源代码看不了的。在这些情况当中,尽管经过多次尝试都没有办法完成任务,作业也会失败。通过这个属性,只有一小部分的坏数据周边数据会丢失,这对于某些应用是可以接受的(那么数据量非常的统计分析)
这个属性默认是失效的。可以通过SkipBadRecords.setMapperMaxSkipRecords(Configuration, long) 和SkipBadRecords.setReducerMaxSkipGroups(Configuration, long)。来使它生效。
当这个属性生效,框架在一定数量的map失败后会进入“跳过模式”。在跳过模式中,map任务维持被处理数据的范围,看看SkipBadRecords.setAttemptsToStartSkipping(Configuration, int)。为了达到这个目标,框架依赖于记录计数器。看看SkipBadRecords.COUNTER_MAP_PROCESSED_RECORDS and SkipBadRecords.COUNTER_REDUCE_PROCESSED_GROUPS的说明。这个计数器是的框架可以知道有多少条记录被成功处理了,因此来找出哪些记录范围会引起任务崩溃。在进一步的尝试中,这些范围的记录会被跳过。
跳过记录的数目取决于运行的记录计数器的增长频率。建议这个计数器在每天记录处理增加。这在批量处理中可已不太可能实现。在这些情况当中,框架会跳过不良记录附近的额外数据。用户可以通过SkipBadRecords.setMapperMaxSkipRecords(Configuration, long) and SkipBadRecords.setReducerMaxSkipGroups(Configuration, long)来控制跳过记录的数量。框架会试图使用二进制搜索方式来缩窄跳过记录的范围。跳过范围被分成两部分并且只有其中一半会被拿来执行。在接下来的错误当中,框架将会指出哪一半范围包含不良数据。一个任务将会重新执行直到跳过记录或者尝试次数用完。可以通过Job.setMaxMapAttempts(int) and Job.setMaxReduceAttempts(int).来增加尝试次数。
跳过的记录将会以序列化的形式写到HDFS中。可以通过 SkipBadRecords.setSkipOutputPath(JobConf, Path)来修改路径。