YarnChild,由NodeManager通过脚本启动
YarnChild的main方法中调用Task的run方法
task可以是maptask也可以是reducetask
163 taskFinal.run(job, umbilical); // run the task
在MapTask中,run方法调用了它自己的runNewMapper方法
784行MapTask调用了mapper的run方法
该run方法在哪里?
在MapTask的runNewMapper中,
创建了任务上下文对象
创建了Mapper对象,这个mapper对象是我们自己设置的
创建了FileInputFormat对象,默认是TextInputFormat.class的对象
创建了切片对象,用于读取输入的内容
创建了读取切片内容的输入流对象:NewTrackingRecordReader
创建了输出对象
reduce任务可以没有 runNewMapper 763行
创建Map任务的上下文件对象
开始调用map方法进行map任务的计算
重写的map方法中的context参数的几个方法
context
context.nextKeyValue()
mapContext.nextKeyValue();
context.getCurrentKey()
mapContext.getCurrentKey();
context.getCurrentValue()
mapContext.getCurrentValue();
context.write(key, value)
mapContext.write(key, value);
为什么不直接调用mapContext的方法而要转一下?
MapContext mapContext
Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context
因为Mapper中run方法要求必须是Context类型的,而Mapper中Context
是一个抽象类,可以将MapContext的实现跟Mapper中的Context进行解耦
此时四个方法如何实现,只需要看MapContext中是如何实现的。
KeyValueLineRecordReader
"hello bjsxt\tI'm OK"
key="hello bjsxt"
value="I'm OK"
如果有多个制表符,则以第一个为准
<hello, 4>
<bjsxt, 3>
hello\t4
bjsxt\t3
LineRecordReader(默认的方式?)
以行的偏移量为key,行的字符串为value的读取方式
MapTask中755行,input输入流是NewTrackingRecordReader对象
NewTrackingRecordReader是如何读取的?
mapper.run(mapperContext);中用到nextKeyValue()方法
这个方法是mapperContext的,mapperContext代理了mapContext的
mapContext代理了input的,input是NewTrackingRecordReader对象
所以要看NewTrackingRecordReader对象中的nextKeyValue方法
MapTask 512行:real是真正读取的
748行说明了默认的InputFormat是哪个实现类
JobContextImpl类中175行说明了默认情况下使用TextInputFormat作为
inputFormat的实现类
LineRecordReader
该类中116行,判断,如果当前map处理的切片不是第一个切片
则永远丢掉第一行
该类180行说明,只要不是最后一个切片,则永远多读一行
将block块有可能切开的一行拼成整体。
结论:map读取数据的时候,使用LineRecordReader来读取
在nextKeyValue方法中读取
第四个方法:
context.write方法要找MapContextImpl的父类
TaskInputOutputContextImpl
该类87行:write方法
该write方法用的是output的write方法,该output是MapContextImpl
构造器中实例化父类的时候传过去的参数
在MapContextImpl构造器中的该参数是谁?
在实例化MapContextImpl的时候传的参数是哪个
MapTask类767行实例化出来的NewOutputCollector
NewOutputCollector类中的write方法:
711行:
collector.collect(key, value,
partitioner.getPartition(key, value, partitions));
NewOutputCollector类中697行
如果分区数是1,则直接返回0分区
否则使用默认分区器计算分区:
默认分区器是哪个?
在JobContextImpl的235行,如果用户设置,则使用用户设置的
否则使用HashPartitioner
默认如何计算分区?
(key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
NewOutputCollector类用于写map的输出数据到环形缓冲区
这个类通过collector的对象输出数据
collector是谁?在NewOutputCollector类695行进行初始化
MapTask第382行的方法用于创建collector对象,默认情况下,这个对象
是MapOutputBuffer类的对象 387行
MapOutputBuffer类的collect方法:
MapTask 965行 环形缓冲区默认阈值0.8 80%,
当环形缓冲区写到80%后,开始向磁盘溢写数据
MapTask 967行,sortmb指的是环形缓冲区大小,默认100MB
981行将MB单位换算为Byte单位,在983行实例化出了缓存,byte数组。
MapOutputBuffer类的collect方法:
1065行开始执行collect方法,将键值对序列化好之后写到缓存中
在MapOutputBuffer的1255行compare方法先按照分区比较,再按照
key比较,要排序就要比较大小
看comparator如何实现
在1001行有赋值:
比较器:要么key的类型系统提供并注册到WritableComparator类的map
集合中,要么自己定义,自己注册
Text.class 本身提供了比较器,并且注册到WritableComparator类map
集合中了
Writable接口
WritableComparable接口
WritableComparator类中65行
如果没有注册过比较器,比如像自定义的key类型
WritableComparator会直接创建一个WritableComparator对象,其中
用到了自定义的key的类型
要求自定义的key类型实现WritableComparable<T>接口
自定义key类型的时候需要实现compareTo方法,在该方法中自定义比较
规则。
排序是如何排序的?
MapOutputBuffer的978行,默认使用QuickSort快排
1593行进行排序
进行快排的时候,调用了MapOutputBuffer的compare方法
compare方法用到了key类型注册的比较器,或者自定义的比较器
还可以继承WritableComparator类,重写compare方法
调用父类的有参构造器