Mapper中map方法下context.write的流程与代码详解

本文基于Hadoop 2.4.0版本,深入解析Map任务如何在Hadoop中被执行,包括输入输出信息的获取、处理数据的保存以及任务执行的监控。详细介绍了MapContext和ReduceContext的实现,以及RecordWriter在Map和Reduce过程中的作用,特别是2.x版本对1.x版本的改进。同时,阐述了MapTask的运行流程,从初始化到数据收集、排序和输出的全过程。

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

本文的分析基于Hadoop 2.4.0版本

任何Map任务在Hadoop中都会被一个MapTask对象所详细描述,MapTask会最终调用其run方法来执行它对应的Map任务,需要执行任务就必须要有相关的输入输出信息,这些信息都包含在Map任务对应的Context对象中,Context通过RecordReader来获取输入数据,Map任务的输入文件保存在InputSplit中,这个InputSplit保存了文件的路径、范围和位置;通过RecordWriter来保存处理后的数据,在Context中还有个任务报告器TaskReporter,它不断向ApplicationMaster报告任务的执行进度

Mapper abstract class Context implements MapContext(接口)

Reducer的abstract class Context implements ReduceContext(接口)

MapContext和ReduceContext都是extends TaskInputOutputContext(接口)

interface TaskInputOutputContext的实现abstractclass TaskInputOutputContextImpl

TaskInputOutputContextImpl的write方法由abstractclass RecordWriter<KEYOUT,VALUEOUT>实现

Map和Reduce中的Context对象的write方法都是调用RecordWriter的write方法

其中RecordWriter有很多的实现类如classMapTask下的privateclass NewOutputCollector

private class NewDirectOutputCollector

NewOutputCollector举例:

public void write(K key, V value)throws IOException, InterruptedException {

      collector.collect(key, value,partitioner.getPartition(key,value, partitions));

}

其中collector的类型是MapOutputCollector(接口),最后实现类是static class MapOutputBuffer,是MapTask下的类,方法签名如下:

public synchronized void collect(K key, V value, final int partition ) throws IOException
在2.x中对1.x进行了改进,1.x中的int [] kvoffsets, int[] kvindices2.x中的IntBufferkvmeta给替换了

首选会调用publicvoid init(MapOutputCollector.Context context 方法进行初始化)

什么时候调用init的呢?

如果是使用runNewMapper的话,当getNumReduceTasks() != 0时,调用private <KEY, VALUE> MapOutputCollector<KEY,VALUE>createSortingCollector(JobConfjob, TaskReporter reporter)方法内部调用的collector.init(context),其中在NewOutputCollector类的构造方法中调用了collector =createSortingCollector(job, reporter),getNumReduceTasks() == 0时,调用另一种直接的方式

使用runOldMapper的话,是直接当numReduceTasks > 0时调用createSortingCollector(job, reporter),当numReduceTasks =0时,调用另一种直接的方式

final float spillper = job.getFloat(JobContext.MAP_SORT_SPILL_PERCENT, (float)0.8);

final int sortmb = job.getInt(JobContext.IO_SORT_MB, 100);

key和value需要进行序列化的操作

keyClass = (Class<K>)job.getMapOutputKeyClass();

valClass = (Class<V>)job.getMapOutputValueClass();

serializationFactory = new SerializationFactory(job);

keySerializer = serializationFactory.getSerializer(keyClass);

keySerializer.open(bb);

valSerializer = serializationFactory.getSerializer(valClass);

valSerializer.open(bb);

初始化操作之后就执行public synchronized void collect(K key,V value, final int partition)方法

然后执行flush方法:将缓存中的数据刷到磁盘上

 

进行spill的时候,先是对缓存中的数据进行排序

sorter.sort(MapOutputBuffer.this, mstart, mend,reporter)


MapTask的运行主要代码:

MapTask的入口是run方法publicvoid run(final JobConf job,final TaskUmbilicalProtocol umbilical)

    if (isMapTask()) {

      // 如果没有reducer的任务,map阶段会支配所有的进程

      if (conf.getNumReduceTasks() == 0) {

        mapPhase = getProgress().addPhase("map", 1.0f);

      } else {

        // 如果有reducer的任务,全部被分为map阶段和sort阶段,各自占据一定的处理过程.

        mapPhase = getProgress().addPhase("map", 0.667f);

        sortPhase  =getProgress().addPhase("sort", 0.333f);

      }

}

//判断是否使用新的api

boolean useNewApi = job.getUseNewMapper()

Configuration类中的方法,默认是false

  public boolean getUseNewMapper() {

    return getBoolean("mapred.mapper.new-api",false);

  }

 

//useNewAPi是关于committer的设置不同,

initialize(job, getJobID(), reporter,useNewApi);

    if (useNewApi) {

      if (LOG.isDebugEnabled()) {

        LOG.debug("using new api for outputcommitter");

      }

      outputFormat =

        ReflectionUtils.newInstance(taskContext.getOutputFormatClass(), job);

      committer = outputFormat.getOutputCommitter(taskContext);

    } else {

      committer = conf.getOutputCommitter();

    }

 

 

做一些处理操作

    if (jobCleanup) {

      runJobCleanupTask(umbilical, reporter);

      return;

    }

    if (jobSetup) {

      runJobSetupTask(umbilical, reporter);

      return;

    }

    if (taskCleanup) {

      runTaskCleanupTask(umbilical, reporter);

      return;

    }

然后

    if (useNewApi) {

      runNewMapper(job, splitMetaInfo, umbilical, reporter);

    } else {

      runOldMapper(job, splitMetaInfo, umbilical, reporter);

    }

runNewMapper与runOldMapper的不同,对代码的不同包装,实现效果基本相同
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.LongWritable; 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.output.FileOutputFormat; import java.io.IOException; public class PatentCitationCount { // Mapper类:提取被引用的专利ID public static class CitationMapper extends Mapper<LongWritable, Text, Text, IntWritable> { private final static IntWritable one = new IntWritable(1); private Text citedPatent = new Text(); @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { // 假设输入格式为:cited_patent,citing_patent(例如:"patent_12345,patent_67890") String[] parts = value.toString().split(","); if (parts.length >= 2) { citedPatent.set(parts[0].trim()); // 提取被引用的专利ID context.write(citedPatent, one); // 输出键值对(专利ID, 1) } } } // Reducer类:累加引用次数 public static class CitationReducer extends Reducer<Text, IntWritable, Text, IntWritable> { private IntWritable result = new IntWritable(); @Override protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { int sum = 0; for (IntWritable val : values) { sum += val.get(); // 对每个专利的引用次数求和 } result.set(sum); context.write(key, result); // 输出结果(专利ID, 总引用次数) } } // 主函数:配置作业 public static void main(String[] args) throws Exception { Configuration conf = new Configuration(); FileSystem fs = FileSystem.get(conf); // 输入输出路径参数(通过命令行传入) Path inputPath = new Path(args[0]); Path outputPath = new Path(args[1]); // 清理旧输出目录 if (fs.exists(outputPath)) { fs.delete(outputPath, true); } Job job = Job.getInstance(conf, "Patent Citation Count"); job.setJarByClass(PatentCitationCount.class); // 配置Mapper和Reducer job.setMapperClass(CitationMapper.class); job.setReducerClass(CitationReducer.class); // 设置输入输出类型 job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); // 指定输入输出路径 FileInputFormat.addInputPath(job, inputPath); FileOutputFormat.setOutputPath(job, outputPath); // 提交作业并等待完成 System.exit(job.waitForCompletion(true) ? 0 : 1); } }生成运行结果
03-18
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值