MapReduce配置与优化

本文详细介绍了MapReduce的流程,从配置详解到各个阶段的优化,包括Map优化如输入过滤、小文件处理、连接策略,Shuffle优化如排序、本地Reducer和Combiner,Reduce优化以及作业JVM堆大小的设置。通过对各项配置和技巧的深入探讨,有助于提升MapReduce作业的性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 流程简介

MapReduce流程

参考MapReduce学习笔记之简介(一)

2. 配置详解

core-site.xml是全局配置,hdfs-site.xml和mapred-site.xml分别是hdfs和mapred的局部配置。

本文配置基于2.7.3版本。另外只列出了部分属性。

2.1 core-default.xml

选项默认值描述
hadoop.tmp.dir/tmp/hadoop-${user.name}全局临时文件路径
io.seqfile.local.dir${hadoop.tmp.dir}/io/local合并序列化文件的中间文件存储路径,多个路径是用逗号隔开
fs.defaultFSfile:///namenode RPC交互端口,一般使用9000端口
io.file.buffer.size4096序列化文件的缓存大小,应该设置为硬件页面大小的倍数(x86是4096)
file.blocksize67108864块大小
ha.zookeeper.quorum(空)NameNode HA时,配置ZooKeeper节点,以逗号隔开
ha.zookeeper.session-timeout.ms5000ZKFC连接ZooKeeper的超时时间

2.2 hdfs-default.xml

选项默认值描述
dfs.replication3副本数
dfs.namenode.handler.count10设定 namenode server threads 的数量,这些 threads 會用 RPC 跟其他的 datanodes 沟通。
dfs.namenode.name.dirfile://${hadoop.tmp.dir}/dfs/namenamenode元数据存储路径,多个目录时,以逗号隔开
dfs.datanode.data.dirfile://${hadoop.tmp.dir}/dfs/datadatanode元数据存储路径,多个目录时,以逗号隔开
dfs.permissions.enabledtrue开关权限验证,false时可以开启远程调试功能
dfs.namenode.secondary.http-address0.0.0.0:50090第二名字空间Http Server地址
dfs.namenode.secondary.https-address0.0.0.0:50091第二名字空间Https Server地址
dfs.datanode.address0.0.0.0:50010datanode地址
dfs.datanode.http.address0.0.0.0:50075datanode的http server地址
dfs.datanode.ipc.address0.0.0.0:50020datanode的ipc server地址
dfs.namenode.http-address0.0.0.0:50070dfs namenode的web ui地址
dfs.datanode.https.address0.0.0.0:50475datanode的https server地址
dfs.namenode.https-address0.0.0.0:50470namenode的https server地址
dfs.namenode.backup.address0.0.0.0:50100backupnode server地址
dfs.namenode.backup.http-address0.0.0.0:50105backupnode http server地址
dfs.blocksize134217728块大小,默认128m
dfs.namenode.checkpoint.dirfile://${hadoop.tmp.dir}/dfs/namesecondary第二名字空间存储用于合并的临时镜像的目录,可多个目录,用逗号隔开
dfs.nameservices(空)用逗号分开的nameservice列表
dfs.ha.namenodes.xxx(空)xxx是nameservice名,该属性的值为其下的namenode列表,以逗号隔开
dfs.namenode.rpc-address.xxx.yyy(空)xxx是nameservice名,yyy是namennode名,该属性为对应RPC地址
dfs.namenode.http-address.xxx.yyy(空)xxx是nameservice名,yyy是namennode名,该属性为对应http server地址
dfs.namenode.shared.edits.dir(空)NameNode HA中,多个NameNode间共享数据的目录
dfs.ha.automatic-failover.enabledfalse是否开启故障恢复
dfs.journalnode.rpc-address0.0.0.0:8485JournalNode RPC Server地址
dfs.journalnode.http-address0.0.0.0:8480 JournalNode Http server地址
dfs.journalnode.https-address0.0.0.0:8481JournalNode https server地址

2.3 mapred-default.xml

选项默认值描述
mapreduce.jobtracker.jobhistory.location(空)job历史文件保存路径,默认在logs的history文件夹下。
mapreduce.task.io.sort.factor10排序文件时用于合并的流数量,即打开的文件句柄数
mapreduce.task.io.sort.mb100排序文件时总的内存量(MB),默认每个合并流1MB
mapreduce.map.sort.spill.percent0.80Map阶段溢写文件的阈值(排序缓冲区大小的百分比)
mapreduce.jobtracker.http.address0.0.0.0:50030jobtracker的tracker页面服务监听地址
mapreduce.cluster.local.dir${hadoop.tmp.dir}/mapred/localmapred做本地计算所使用的文件夹,可以配置多块硬盘,逗号分隔
mapreduce.jobtracker.system.dir${hadoop.tmp.dir}/mapred/systemmapred存放控制文件所使用的文件夹,可配置多块硬盘,逗号分隔。
mapreduce.jobtracker.staging.root.dir${hadoop.tmp.dir}/mapred/staging用来存放与每个job相关的数据
mapreduce.job.running.map.limit0单个任务并发的最大map数,0或负数没有限制
mapreduce.job.running.reduce.limit0单个任务并发的最大reduce数,0或负数没有限制
mapreduce.map.memory.mb1024每个Map Task需要的内存量
mapreduce.map.cpu.vcores1每个Map Task需要的虚拟CPU个数
mapreduce.reduce.memory.mb1024每个Reduce Task需要的内存量
mapreduce.reduce.cpu.vcores1每个Reduce Task需要的虚拟CPU个数
mapred.child.java.opts-Xmx200mvm启动的子线程可以使用的最大内存。建议值-XX:-UseGCOverheadLimit -Xms512m -Xmx2048m -verbose:gc -Xloggc:/tmp/@taskid@.gc
mapreduce.reduce.shuffle.merge.percent0.66超过shuffle最大内存的一定限度后,开始往磁盘刷
mapreduce.reduce.shuffle.input.buffer.percent0.70shuffile在reduce内存中的数据最多使用内存量
mapreduce.reduce.shuffle.memory.limit.percent0.25每个fetch取到的输出的大小能够占的内存比的大小,所以,如果我们想fetch不进磁盘的话,可以适当调大这个值。
mapreduce.map.speculativetrue是否对Map Task启用推测执行机制
mapreduce.reduce.speculativetrue是否对Reduce Task启用推测执行机制
mapreduce.job.queuenamedefault作业提交到的队列
mapreduce.reduce.shuffle.parallelcopies5Reduce Task启动的并发拷贝数据的线程数目
mapreduce.map.output.compressfalsemap输出结果是否要压缩
mapreduce.map.output.compress.codecorg.apache.hadoop.io.compress
.DefaultCodec
map输出的压缩算法
map.sort.classorg.apache.hadoop.util.QuickSort排序时使用的算法
mapreduce.shuffle.port13562ShuffleHandler运行的默认端口
mapreduce.jobhistory.address0.0.0.0:10020MapReduce JobHistory Server IPC地址
mapreduce.jobhistory.webapp.address0.0.0.0:19888MapReduce JobHistory Server Web UI地址
mapreduce.jobhistory.admin.address0.0.0.0:10033History Server的管理地址
mapreduce.input.fileinputformat.split.minsize0map任务输入数据块最小大小
yarn.app.mapreduce.am.command-opts-Xmx1024mMR App master的java选项

3. Map优化

3.1 输入过滤

见本人博客
输入过滤

3.2 小文件优化

见本人博客
Hadoop的“小文件”问题

3.3 连接

3.3.1 Map端连接

使用场景:待连接的数据集中有一个数据集小到可以完全放在缓存中。

job的main函数中设置缓存文件

Job job = Job.getInstance(conf, "MapJoinDemo");
job.setJarByClass(ProvinceMapJoinStatistics.class);

job.addCacheFile(new Path(args[1]).toUri());

Mapper的setup方法中读取缓存文件

private String provinceWithProduct = "";

/**
 * 加载缓存文件
 *
 * @param context 上下文
 *
 * @throws IOException
 * @throws InterruptedException
 */
@Override
protected void setup(Context context) throws IOException, InterruptedException {

    URI[] uri = context.getCacheFiles();
    if (uri == null || uri.length == 0) {
        return;
    }
    for (URI p : uri) {
        if (p.toString().endsWith("part-r-00000")) {
            // 读缓存文件
            try {
                provinceWithProduct = HdfsUtil.read(new Configuration(), p.toString());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

Mapper的map方法中实现连接

public void map(LongWritable key, Text value, Context context)
        throws IOException, InterruptedException {

    if (!provinceWithProduct.contains(value.toString()
            .substring(0, 2))) {
        context.write(value, NullWritable.get());
    }
}
3.3.2 半连接

待连接的数据集中有一个数据集非常大,但同时这个数据集可以被过滤成小到可以放在内存中。

job的main函数中设置缓存文件(即过滤条件)

# 同map端连接

Mapper的setup方法中读取缓存文件

# 同map端连接
# 生成过滤集合 joinKeySet

Mapper的map方法中实现数据过滤

@Override  
protected void map(Object key, Text value, Context context)  
        throws IOException, InterruptedException {  
    // 获得文件输入路径  
    String pathName = ((FileSplit) context.getInputSplit()).getPath()  
            .toString();  
    // 数据来自tb_dim_city.dat文件,标志即为"0"  
    if (pathName.endsWith("tb_dim_city.dat")) {  
        String[] valueItems = value.toString().split("\\|");  
        // 过滤格式错误的记录  
        if (valueItems.length != 5) {  
            return;  
        }  
        // 过滤掉不需要参加join的记录  
        if (joinKeySet.contains(valueItems[0])) {  
            flag.set("0");  
            joinKey.set(valueItems[0]);  
            secondPart.set(valueItems[1] + "\t" + valueItems[2] + "\t"  
                    + valueItems[3] + "\t" + valueItems[4]);  
            combineValues.setFlag(flag);  
            combineValues.setJoinKey(joinKey);  
            combineValues.setSecondPart(secondPart);  
            context.write(combineValues.getJoinKey(), combineValues);  
        } else {  
            return;  
        }  
    }
    // 数据来自于tb_user_profiles.dat,标志即为"1"  
    else if (pathName.endsWith("tb_user_profiles.dat")) {  
        String[] valueItems = value.toString().split("\\|");  
        // 过滤格式错误的记录  
        if (valueItems.length != 4) {  
            return;  
        }  
        // 过滤掉不需要参加join的记录  
        if (joinKeySet.contains(valueItems[3])) {  
            flag.set("1");  
            joinKey.set(valueItems[3]);  
            secondPart.set(valueItems[0] + "\t" + valueItems[1] + "\t"  
                    + valueItems[2]);  
            combineValues.setFlag(flag);  
            combineValues.setJoinKey(joinKey);  
            combineValues.setSecondPart(secondPart);  
            context.write(combineValues.getJoinKey(), combineValues);  
        } else {  
            return;  
        }  
    }  
}

Reducer的reduce方法实现连接

public static class SemiJoinReducer extends  
        Reducer<Text, CombineValues, Text, Text> {  
    // 存储一个分组中的左表信息  
    private ArrayList<Text> leftTable = new ArrayList<Text>();  
    // 存储一个分组中的右表信息  
    private ArrayList<Text> rightTable = new ArrayList<Text>();  
    private Text secondPar = null;  
    private Text output = new Text();  

    /** 
     * 一个分组调用一次reduce函数 
     */  
    @Override  
    protected void reduce(Text key, Iterable<CombineValues> value,  
            Context context) throws IOException, InterruptedException {  
        leftTable.clear();  
        rightTable.clear();  
        /** 
         * 将分组中的元素按照文件分别进行存放 这种方法要注意的问题: 如果一个分组内的元素太多的话,可能会导致在reduce阶段出现OOM, 
         * 在处理分布式问题之前最好先了解数据的分布情况,根据不同的分布采取最 
         * 适当的处理方法,这样可以有效的防止导致OOM和数据过度倾斜问题。 
         */  
        for (CombineValues cv : value) {  
            secondPar = new Text(cv.getSecondPart().toString());  
            // 左表tb_dim_city  
            if ("0".equals(cv.getFlag().toString().trim())) {  
                leftTable.add(secondPar);  
            }  
            // 右表tb_user_profiles  
            else if ("1".equals(cv.getFlag().toString().trim())) {  
                rightTable.add(secondPar);  
            }  
        }  
        logger.info("tb_dim_city:" + leftTable.toString());  
        logger.info("tb_user_profiles:" + rightTable.toString());  
        for (Text leftPart : leftTable) {  
            for (Text rightPart : rightTable) {  
                output.set(leftPart + "\t" + rightPart);  
                context.write(key, output);  
            }  
        }  
    }  
}

4. Shuffle优化

4.1 中间输出结果的排序与溢出

中间输出结果的排序与溢出

4.2 本地Reducer和Combiner

本地Reducer和Combiner

4.3 Map侧输出

获取中间输出结果(Map侧)

5. Reduce优化

5.1 Reduce任务数

见本人博客Reduce任务

5.2 获取中间输出结果(Reduce侧)

见本人博客获取中间输出结果(Reduce侧)

5.3 中间输出结果的合并与溢出

见本人博客中间输出结果的合并与溢出

5.4 Reduce端连接

使用场景:连接两个或多个大型数据集。

package com.mr.reduceSideJoin;  

import java.io.DataInput;  
import java.io.DataOutput;  
import java.io.IOException;  

import org.apache.hadoop.io.Text;  
import org.apache.hadoop.io.WritableComparable;  

public class CombineValues implements WritableComparable<CombineValues> {  
    private Text joinKey;// 链接关键字  
    private Text flag;// 文件来源标志  
    private Text secondPart;// 除了链接键外的其他部分  

    public void setJoinKey(Text joinKey) {  
        this.joinKey = joinKey;  
    }  

    public void setFlag(Text flag) {  
        this.flag = flag;  
    }  

    public void setSecondPart(Text secondPart) {  
        this.secondPart = secondPart;  
    }  

    public Text getFlag() {  
        return flag;  
    }  

    public Text getSecondPart() {  
        return secondPart;  
    }  

    public Text getJoinKey() {  
        return joinKey;  
    }  

    public CombineValues() {  
        this.joinKey = new Text();  
        this.flag = new Text();  
        this.secondPart = new Text();  
    }  

    @Override  
    public void write(DataOutput out) throws IOException {  
        this.joinKey.write(out);  
        this.flag.write(out);  
        this.secondPart.write(out);  
    }  

    @Override  
    public void readFields(DataInput in) throws IOException {  
        this.joinKey.readFields(in);  
        this.flag.readFields(in);  
        this.secondPart.readFields(in);  
    }  

    @Override  
    public int compareTo(CombineValues o) {  
        return this.joinKey.compareTo(o.getJoinKey());  
    }  

    @Override  
    public String toString() {  
        // TODO Auto-generated method stub  
        return "[flag=" + this.flag.toString() + ",joinKey="  
                + this.joinKey.toString() + ",secondPart="  
                + this.secondPart.toString() + "]";  
    }  
}
package com.mr.reduceSideJoin;  

import java.io.IOException;  
import java.util.ArrayList;  

import org.apache.hadoop.conf.Configuration;  
import org.apache.hadoop.conf.Configured;  
import org.apache.hadoop.fs.Path;  
import org.apache.hadoop.io.Text;  
import org.apache.hadoop.mapreduce.Job;  
import org.apache.hadoop.mapreduce.Mapper;  
import org.apache.hadoop.mapreduce.Reducer;  
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;  
import org.apache.hadoop.mapreduce.lib.input.FileSplit;  
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;  
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;  
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;  
import org.apache.hadoop.util.Tool;  
import org.apache.hadoop.util.ToolRunner;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  

public class ReduceSideJoin_LeftOuterJoin extends Configured implements Tool {  
    private static final Logger logger = LoggerFactory  
            .getLogger(ReduceSideJoin_LeftOuterJoin.class);  

    public static class LeftOutJoinMapper extends  
            Mapper<Object, Text, Text, CombineValues> {  
        private CombineValues combineValues = new CombineValues();  
        private Text flag = new Text();  
        private Text joinKey = new Text();  
        private Text secondPart = new Text();  

        @Override  
        protected void map(Object key, Text value, Context context)  
                throws IOException, InterruptedException {  
            // 获得文件输入路径  
            String pathName = ((FileSplit) context.getInputSplit()).getPath()  
                    .toString();  
            // 数据来自tb_dim_city.dat文件,标志即为"0"  
            if (pathName.endsWith("tb_dim_city.dat")) {  
                String[] valueItems = value.toString().split("\\|");  
                // 过滤格式错误的记录  
                if (valueItems.length != 5) {  
                    return;  
                }  
                flag.set("0");  
                joinKey.set(valueItems[0]);  
                secondPart.set(valueItems[1] + "\t" + valueItems[2] + "\t"  
                        + valueItems[3] + "\t" + valueItems[4]);  
                combineValues.setFlag(flag);  
                combineValues.setJoinKey(joinKey);  
                combineValues.setSecondPart(secondPart);  
                context.write(combineValues.getJoinKey(), combineValues);  

            }// 数据来自于tb_user_profiles.dat,标志即为"1"  
            else if (pathName.endsWith("tb_user_profiles.dat")) {  
                String[] valueItems = value.toString().split("\\|");  
                // 过滤格式错误的记录  
                if (valueItems.length != 4) {  
                    return;  
                }  
                flag.set("1");  
                joinKey.set(valueItems[3]);  
                secondPart.set(valueItems[0] + "\t" + valueItems[1] + "\t"  
                        + valueItems[2]);  
                combineValues.setFlag(flag);  
                combineValues.setJoinKey(joinKey);  
                combineValues.setSecondPart(secondPart);  
                context.write(combineValues.getJoinKey(), combineValues);  
            }  
        }  
    }  

    public static class LeftOutJoinReducer extends  
            Reducer<Text, CombineValues, Text, Text> {  
        // 存储一个分组中的左表信息  
        private ArrayList<Text> leftTable = new ArrayList<Text>();  
        // 存储一个分组中的右表信息  
        private ArrayList<Text> rightTable = new ArrayList<Text>();  
        private Text secondPar = null;  
        private Text output = new Text();  

        /** 
         * 一个分组调用一次reduce函数;相同key的数据进了同一个reduce,这样就实现了join。 
         */  
        @Override  
        protected void reduce(Text key, Iterable<CombineValues> value,  
                Context context) throws IOException, InterruptedException {  
            leftTable.clear();  
            rightTable.clear();  
            /** 
             * 将分组中的元素按照文件分别进行存放 这种方法要注意的问题: 如果一个分组内的元素太多的话,可能会导致在reduce阶段出现OOM, 
             * 在处理分布式问题之前最好先了解数据的分布情况,根据不同的分布采取最 
             * 适当的处理方法,这样可以有效的防止导致OOM和数据过度倾斜问题。 
             */  
            for (CombineValues cv : value) {  
                secondPar = new Text(cv.getSecondPart().toString());  
                // 左表tb_dim_city  
                if ("0".equals(cv.getFlag().toString().trim())) {  
                    leftTable.add(secondPar);  
                }  
                // 右表tb_user_profiles  
                else if ("1".equals(cv.getFlag().toString().trim())) {  
                    rightTable.add(secondPar);  
                }  
            }  
            logger.info("tb_dim_city:" + leftTable.toString());  
            logger.info("tb_user_profiles:" + rightTable.toString());  
            // 这里体现了左连接  
            for (Text leftPart : leftTable) {  
                for (Text rightPart : rightTable) {  
                    output.set(leftPart + "\t" + rightPart);  
                    // leftTable中有数据 rightTable中没有数据 就无法进到这一步  
                    // rightTable中有数据 leftTable中没有数据 外面的循环就进不去  
                    context.write(key, output);  
                }  
            }  
        }  
    }  

    @Override  
    public int run(String[] args) throws Exception {  
        Configuration conf = getConf(); // 获得配置文件对象  
        Job job = new Job(conf, "LeftOutJoinMR");  
        job.setJarByClass(ReduceSideJoin_LeftOuterJoin.class);  

        FileInputFormat.addInputPath(job, new Path(args[0])); // 设置map输入文件路径  
        FileOutputFormat.setOutputPath(job, new Path(args[1])); // 设置reduce输出文件路径  

        job.setMapperClass(LeftOutJoinMapper.class);  
        job.setReducerClass(LeftOutJoinReducer.class);  

        job.setInputFormatClass(TextInputFormat.class); // 设置文件输入格式  
        job.setOutputFormatClass(TextOutputFormat.class);// 使用默认的output格式  

        // 设置map的输出key和value类型  
        job.setMapOutputKeyClass(Text.class);  
        job.setMapOutputValueClass(CombineValues.class);  

        // 设置reduce的输出key和value类型  
        job.setOutputKeyClass(Text.class);  
        job.setOutputValueClass(Text.class);  
        job.waitForCompletion(true);  
        return job.isSuccessful() ? 0 : 1;  
    }  

    public static void main(String[] args) throws IOException,  
            ClassNotFoundException, InterruptedException {  
        try {  
            Tool rdf = new ReduceSideJoin_LeftOuterJoin();  
            int returnCode = ToolRunner.run(rdf, args);  
            System.exit(returnCode);  
        } catch (Exception e) {  
            System.out.println(e.getMessage());  
        }  
    }  
}

6. 其他

6.1 作业JVM堆大小设置优化

示例演示限制客户修改堆大小

<!--
mapred.task.java.opts是自定义的。
final被设置为true,不让客户修改
后面的-Xmx1000m会覆盖前面的
-->
<property>
  <name>mapred.task.java.opts</name>
  <value>-Xmx2000m</value>
</property>

<property>
  <name>mapred.child.java.opts</name>
  <value>${mapred.task.java.opts} -Xmx1000m</value>
  <final>true</final>
</property>

<!-- 分别配置作业的Map和Reduce阶段的heap的大小 -->
<property>
  <name>mapred.map.child.java.opts</name>
  <value>-Xmx512M</value>
</property>

<property>
  <name>mapred.reduce.child.java.opts</name>
  <value>-Xmx1024M</value>
</property>

通过管理员配置限制

<property>
  <name>mapreduce.admin.map.child.java.opts</name>
  <value>-Xmx1000M</value>
</property>

<property>
  <name>mapreduce.admin.reduce.child.java.opts</name>
  <value>-Xmx1000M</value>
</property>

# 修改源码
private static String getChildJavaOpts(JobConf jobConf, boolean isMapTask) {
    // 略

    // old: return adminClasspath + " " + userClasspath;
    // 修改为
    return userClasspath + " " + adminClasspath;
}

7. 参考

Hadoop中两表JOIN的处理方法

hadoop MapReduce 三种连接

hadoop核心逻辑shuffle代码分析-reduce端

hadoop作业调优参数整理及原理

MapReduce任务参数调优

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值