MR添加缓存文件
Hadoop提供了两种DistributedCache使用方式,一种是通过API,在程序中设置文件路径,另外一种是通过命令行(-files、-archives、-libjars)参数告诉Hadoop,命令行方式使用以下三个参数设置文件:
- -files:将指定的本地/hdfs文件分发到各个Task的工作目录下,不对文件进行任何处理;
- -archives:将指定文件分发到各个Task的工作目录下,并对名称后缀为".jar"、".zip",".tar.gz"、".tgz"的文件自动解压,默认情况下,解压后的内容存放到工作目录下名称为解压前文件名的目录中,比如压缩包为dict.zip,则解压后内容存放到目录dict.zip中。为此,你可以给文件起个别名/软链接,比如dict.zip#dict,这样,压缩包会被解压到目录dict/dict中。
- -libjars:指定待分发的jar包,Hadoop将这些jar包分发到各个节点上后,会将其自动添加到任务的CLASSPATH环境变量中。
Tips: 可以登录 Yarn Container WebShell 来验证所需文件是否被localized到container的执行环境里。
需要注意的是,DistributedCache 是每个 node 只会下载一份,然后同一个 node 上的多个 task 是使用软链共享这一份数据的,因此如果是不允许并发读的数据的话是不能够使用 DistributedCache的。替代方式是手动在 task 中下载。
MR用户主动Debug
找到作业链接
通过 applicationId 在 https://cloud.bytedance.net/megatron/jobs 找到作业链接,例如application_1583916629750_4673851
如果作业id是以"job_xxx"形式开头,请替换成"application_xx"查询
错误诊断
点击"作业诊断"
一般在stderr有错误抛出

或者直接进入web UI查看任务,进入failed查看失败task

进入log链接查看日志

常规错误
Pipe failed
java.lang.RuntimeException: PipeMapRed.waitOutputThreads(): subprocess failed with code 1.
用户进程退出
在stderr一般有退出原因,没有找到多看一些错误的task
如果还是没有找到,很有可能是用户没有把stdin的数据读完主动退出,或者依赖的脚本有exit(0)的逻辑,请用户加日志输出到stderr主动debug
ImportError: No module named xxx
如果用户有用-file, -cacheArchive传递了依赖,请在import之前sleep住脚本(任务必须是运行状态,失败和运行结束的task无法进入webshell),进入webshell查看运行目录是否存在这些依赖(执行appcache)。如果不存在,说明-file, -cacheArchive没有设置正确,请详细检查;如果存在,说明import路径不正确,请认真检查。



执行命令appcache,进入运行目录
如果用-file, -cacheArchive传递依赖不方便,可以使用docker镜像Docker on Yarn
Error: java.lang.RuntimeException: Error in configuring object
可能是Hadoop调不到Mapper和Reducer造成的,检查是否有-file配置;
-file把文件上传到hdfs,container本地化的时候会把这些文件拉到程序的启动目录。
bash
hadoop xxx \
-mapper mapper.py \
-file mapper.py \
-reducer reducer.py \
-file reducer.py \
java.lang.ClassNotFoundException
缺相对应的jar包

解决办法:
- 通过libjars将缺的jar包传入(多个包中间用逗号隔开,不要加空格),Hadoop将这些jar包分发到各个节点上后,会将其自动添加到任务的CLASSPATH环境变量中,如:
plain
-libjars /data01/home/xxx/lib/parquet-common-1.11.0.jar,/data01/home/xxx/lib/parquet-hadoop-1.11.0.jar
在pom.xml文件中加入dependency,然后用shade方式把新引入的依赖包打入jar包里
plain
<dependencies>
<dependency>
<groupId>org.apache.parquet</groupId>
<artifactId>parquet-common</artifactId>
<version>1.8.3-bd2</version>
</dependency>
<dependency>
<groupId>org.apache.parquet</groupId>
<artifactId>parquet-hadoop</artifactId>
<version>1.8.3-bd2</version>
</dependency>
</dependencies>
plain
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<!-- Run shade goal on package phase -->
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<!-- <mainClass>${groupId}.${mainClass}</mainClass> -->
</transformer>
</transformers>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
</execution>
</executions>
</plugin>
OOM:OutOfMemoryError
OOM的问题需要调整相应的内存,详见7. MR任务的CPU内存设置

如果map/reduce很快出现(通常几秒钟)出现oom/java heap的错误,很有可能数据损坏,或者数据格式不正确,需要用户检查数据格式,或者通过打日志输出到stderr进行debug。
reducer copier failed
1. 可能是shuffle阶段数据量太大:reducer task数量过少导致heap不足,尝试增大reduce task数量
2. 可能是combiner有问题:combiner需要保证输入和输出的数据格式相同,因为combiner可能会被运行0次,1次或多次。
core dump
一般退出码为137或者139。
公司对core dump做了限制,简单来讲,
- 所有的core文件均在/opt/tiger/cores路径下;
- 可以通过container日志链接获取出core的机器,之后请oncall或者自己申请权限;
- 如果想定制core文件名称,需要在map or reduce的env里设置,比如-D mapreduce.map.env=CORE_DUMP_PROC_NAME={my-core-file-name};
作业启动/运行缓慢
1.
plain
20/06/10 15:27:22 INFO mapreduce.Job: MapReduce job is waiting for execution ...
20/06/10 15:27:54 INFO mapreduce.Job: MapReduce job is waiting for execution ...
20/06/10 15:28:26 INFO mapreduce.Job: MapReduce job is waiting for execution ...
20/06/10 15:28:26 INFO mapreduce.Job: Your job wait too much time to start up
作业启动缓慢
当看到提交日志里有"MapReduce job is waiting for execution"时,代表作业启动缓慢,启动指作业从提交到到第一个container(即AM)运行起来的时间,包括为AM申请container的调度时间和NM拉起该container的时间。比如hdfs name node 有故障的时候,启动时长会变长。
- 作业运行缓慢
运行指即AM启动后到作业结束前,从Yarn的维度查看是否是调度缓慢或者队列资源不足造成。
如果非调度原因,则需要根据不同的监控来看哪个阶段比较慢,详见【MR作业调优】一节
container is beyond the '2.5' load limit
container报错: Exit Code: -10002 Container [pid=3748398,containerID=container_e159_1627974704104_780030_01_008229] is beyond the '2.5' load limit. Current container load is 11.000063840290364.
原因:混部环境限制load 。load申请超过cpu的2.5倍报错
解决方案:增加cpu申请量(mapreduce.map.cpu.vcores)或者降低 container 的线程数
mapreduce 任务信息经常打不开
无法查看mapreduce任务信息,am日志可以查看,查看am资源【MR任务的CPU内存设置】一节, cpu,mem是否满了,增加yarn.app.mapreduce.am.resource.mb yarn.app.mapreduce.am.resource.cpu-vcores
MR作业调优
常用的优化方式
当 map 输出数据量很大时:
- 设置 mapred.compress.map.output=true, 在 map/reduce 间使用压缩数据格式;
- 使用 combiner 减少 map/reduce 间 shuffle 的数据量;
- 增大 io.sort.factor(缺省为10),加快 map 输出的数据的 merge 并发
慢节点问题
从Counter中查看HDFS的读写时间监控,_DecodeSpentMs 表示读取HDFS的累积时间,单位ms; EncodeSpentNs_表示写入HDFS的时间总和,单位ns。


解决方式
DFSClient Hedged Read是Hadoop-2.4.0引入的一个新特性,如果读取一个数据块的操作比较慢,DFSClient Hedged Read将会开启一个从另一个副本的hedged读操作。
- dfs.client.hedged.read.enable=true,开启hedged read,
- dfs.client.hedged.read.threadpool.size 开启多少hedged read线程读block, 默认16
- dfs.client.hedged.read.threshold.millis,开启一个Hedged 读前的等待时间(毫秒), 默认60s.
MR 控制map数量
先确定是否是以下几种InputFormat,压缩格式的input一般无法修改map数量
对于FileInputFormat
plain
minSize = Max(formatMinSize, mapreduce.input.fileinputformat.split.minsize);
maxSize = mapreduce.input.fileinputformat.split.maxsize;
blockSize = dfs.block.size;
splitSize = Math.max(minSize, Math.min(maxSize, blockSize));
splitNum = fileLength / splitSize;
dafault值
key | value |
---|---|
formatMinSize | 与format相关,一般是1 |
mapreduce.input.fileinputformat.split.minsize | 1 |
mapreduce.input.fileinputformat.split.maxsize | Long.MAX_VALUE |
dfs.block.size | 我们集群是512m |
主要是设置 mapreduce.input.fileinputformat.split.maxsize 数值。
大多数的 input format 的基类都是 FileInputFormat,因此都是上面的逻辑。
注意,如果数据是压缩格式,有些压缩格式导致文件不可切分。
对于CombineFileInputFormat
plain
minSizeNode = mapreduce.input.fileinputformat.split.minsize.per.node;
minSizeRack = mapreduce.input.fileinputformat.split.minsize.per.rack;
maxSplitSize = mapreduce.input.fileinputformat.split.maxsize;
# 头条自定义参数
maxSplits = mapreduce.max.splits;
社区逻辑
foreach file
按maxSplitSize将文件切分若干block
统计所有block所在的node和rack
foreach node
将node上的所有block按maxSplitSize的大小生成split
如果node上剩余的block总大小>minSizeNode则将剩余block新建一个split
foreach rack
将rack上所有block按maxSplitSize的大小生成split
如果rack上剩余block总大小>minSizeRack则将剩余block新建一个split
foreach 剩余的小block
将剩余block按maxSplitSize的大小生成split
将剩余block生成split
头条逻辑
plain
while(splits.size() > maxSplits):
maxSize = Math.max(1, maxSize * (splits.size() / maxSplits + 1));
splits = 社区逻辑
dafault值
key | value |
---|---|
mapreduce.input.fileinputformat.split.minsize.per.node | 0 |
mapreduce.input.fileinputformat.split.minsize.per.rack | 0 |
mapreduce.input.fileinputformat.split.maxsize | 0 |
mapreduce.max.splits | 50000 |
CustomCombineFileInputFormat: 合并多个文件
功能
- 通过将多文件合并为一个 split,减少 split 数(同时也是 map task 数量)
用法
- 如果未使用 mr 命令,需要在 -libjars 中加入 /opt/tiger/yarn_deploy/hadoop/bytedance-data-1.0.1.jar;
- 指定 -inputformat com.bytedance.data.CustomCombineFileInputFormat;
- 如果是PB格式 使用 -inputformat com.bytedance.hadoop.mapred.CombinePBInputFormat
- 默认只保留一个 split(即所有输入文件合并到一起),可配置 split 数:-Dmapreduce.input.combinefileformat.tasks=10000。
- **如果数据较大,建议配置到 10000 以上。**任何情况下不要超过 30000。
- 指定 split 数后,split 的数量将尽量控制在指定值附近(近似)。
- Hadoop streaming 的参数需要考虑顺序,所有 -D 参数需要出现在非 -D 参数前 ,例如:
- 正例:

-Dmapreduce.input.combinefileformat.tasks=10000 -inputformat com.bytedance.data.CustomCombineFileInputFormat
- 反例:-inputformat com.bytedance.data.CustomCombineFileInputFormat -Dmapreduce.input.combinefileformat.tasks=10000
注意
- 如果之前没有设置 inputformat,streaming 输入可能多出一列 key,需要额外设置
-Dstream.map.input.ignoreKey=true 来忽略。
- 使用 Combine 以后,Mapper 当中不能再通过环境变量 os.environ['map_input_file'] 获取当前输入文件。此时需要根据字段等其它特性来判别输入数据。
应用:基于MapReduce执行简单并行任务
背景
希望实现一个简单并发执行的作业(用于压测等),这个作业可以控制总的task个数 和最大并发task个数,但数据不从HDFS中读取。
实现及示例
- 增加一个NullInputFormat,作为MapReduce的输入InputFormat格式,其会忽略HDFS的输入,只执行task的逻辑。
- 设置reduce个数为0,不需要跑reduce。
- 其他的MR参数在此均适用,按需添加,里面配置的hdfs输入输出目录随意设置即可,不会使用到。
plain
hadoop fs -rm -R -f hdfs://haruna/user/xxx/no_output
/opt/tiger/yarn_deploy/hadoop/bin//hadoop jar /opt/tiger/yarn_deploy/hadoop/./share/hadoop/tools/lib/hadoop-streaming-2.6.0-cdh5.4.4.jar \
-D mapreduce.job.name=xxx \
-D mapreduce.job.maps=100 \
-D mapreduce.job.running.map.limit=10 \
-D mapred.reduce.tasks=0 \
-libjars /opt/tiger/yarn_deploy/hadoop/bytedance-data-1.0.3.jar \
-inputformat com.bytedance.data.mapred.NullInputFormat \
-input /user/xxx/no_input \
-output /user/xxx/no_output \
-mapper task.py \
-file task.py
task.py逻辑很简单,
plain
#!/usr/bin/env python
import sys
sys.stderr.write("hello world!")


我该如何使用
Copy上面示例,只需修改下面标记出来的即可,其余的不用修改。
