MapReduce框架学习

来源:互联网 发布:知乎市值多少 编辑:程序博客网 时间:2024/06/08 10:45

1.贝叶斯分类器的MapReduce实现:

训练样本可由三个MapReduce作业实现: 第一个作业( ExtractJob)抽取文档特
征, 该作业只需要Map即可完成; 第二个作业( ClassPriorJob) 计算类别的先验概率, 即统计每个类别中文档的数目, 并计算类别概率; 第三个作业( ConditionalProbilityJob) 计算单词的条件概率, 即统计<label, word>在所有文档中出现的次数并计算单词
的条件概率,分类过程由一个作业( PredictJob) 完成。 该作业的map()函数计算每个待
分类文档属于每个类别的概率, reduce()函数找出每个文档概率最高的类别, 并输出<docid, label>( 编号为docid的文档属于类别label)。

 

2.MapReduce的基础架构图:

( 1) Client
用户编写的MapReduce程序通过Client提交到JobTracker端; 同时, 用户可通过Client提供的一些接口查看作业运行状态。 在Hadoop内部用“作业”(Job) 表示MapReduce程序。 一个MapReduce程序可对应若干个作业, 而每个作业会被分解成若干个Map/Reduce任务( Task)。
( 2) JobTracker
JobTracker主要负责资源监控和作业调度。 JobTracker监控所有TaskTracker与作业的健康状况, 一旦发现失败情况后, 其会将相应的任务转移到其他节点; 同时, JobTracker会跟踪任务的执行进度、 资源使用量等信息, 并将这些信息告诉任务调度器, 而调度器会在资源出现空闲时, 选择合适的任务使用这些资源。 在Hadoop中, 任务调度器是一个可插拔的模块, 用户可以根据自己的需要设计相应的调度器。
( 3) TaskTracker
TaskTracker会周期性地通过Heartbeat将本节点上资源的使用情况和任务的运行进度汇报给JobTracker, 同时接收JobTracker发送过来的命令并执行相应的操作( 如启动新任务、 杀死任务等) 。 TaskTracker使用“slot”等量划分本节点上的资源量。 “slot”代表计算资源( CPU、内存等) 。 一个Task获取到一个slot后才有机会运行, 而Hadoop调度器的作用就是将各个TaskTracker上的空闲slot分配给Task使用。 slot分为Map slot和Reduce slot两种, 分别供Map Task和Reduce Task使用。 TaskTracker通过slot数目( 可配置参数) 限定Task的并发度。
( 4) Task
Task分为Map Task和Reduce Task两种, 均由TaskTracker启动。 从上一小节中我们知道, HDFS以固定大小的block为基本单位存储数据, 而对于MapReduce而言, 其处理单位是split。 split与block的对应关系如图2-6所示。 split是一个逻辑概念, 它只包含一些元数据信息, 比如数据起始位置、 数据长度、 数据所在节点等。 它的划分方法完全由用户自己决定。 但需要注意的是, split的多少决定了Map Task的数目, 因为每个split会交由一个Map Task处理。

 

3.MapReduce生命周期:

主要流程:

步骤1 作业提交与初始化。 用户提交作业后, 首先由JobClient实例将作业相关信息, 比如将程序jar包、作业配置文件、 分片元信息文件等上传到分布式文件系统( 一般为HDFS)上, 其中, 分片元信息文件记录了每个输入分片的逻辑位置信息。 然后JobClient通过RPC
通知JobTracker。 JobTracker收到新作业提交请求后, 由作业调度模块对作业进行初始化:为作业创建一个JobInProgress对象以跟踪作业运行状况, 而JobInProgress则会为每个Task创建一个TaskInProgress对象以跟踪每个任务的运行状态, TaskInProgress可能需要管理多个“Task运行尝试”(称为“Task Attempt”)

 

步骤2 任务调度与监控。 前面提到任务调度和监控的功能均由JobTracker完成。 TaskTracker周期性地通过Heartbeat向JobTracker汇报本节点的资源使用情况, 一旦出现空闲资源, JobTracker会按照一定的策略选择一个合适的任务使用该空闲资源, 这由任务调度器完成。 任务调度器是一个可插拔的独立模块, 且为双层架构, 即首先选择作业, 然后从该作业中选择任务, 其中, 选择任务时需要重点考虑数据本地性。 此外, JobTracker跟踪作业的整个运行过程, 并为作业的成功运行提供全方位的保障。 首先, 当TaskTracker或者Task失败时, 转移计算任务; 其次, 当某个Task执行进度远落后于同一作业的其他Task时,为之启动一个相同Task, 并选取计算快的Task结果作为最终结果。

 

步骤3 任务运行环境准备。 运行环境准备包括JVM启动和资源隔离, 均由TaskTracker实现。 TaskTracker为每个Task启动一个独立的JVM以避免不同Task在运行过程中相互影响; 同时, TaskTracker使用了操作系统进程实现资源隔离以防止Task滥用资源

 

步骤4 任务执行。 TaskTracker为Task准备好运行环境后, 便会启动Task。在运行过程中, 每个Task的最新进度首先由Task通过RPC汇报给TaskTracker,

 

步骤5 作业完成。 待所有Task执行完毕后, 整个作业执行成功。

 

4.MapReduce计算框架步骤:

1) 迭代( iteration) 。 遍历输入数据, 并将之解析成key/value对。
2) 将输入key/value对映射( map) 成另外一些key/value对。
3) 依据key对中间数据进行分组( grouping) 。
4) 以组为单位对数据进行归约( reduce) 。
5) 迭代。 将最终产生的key/value对保存到输出文件中。

 

5.MapReduce编程体系架构

6.回调机制

MapReduce的五大组件InputFormat,Mapper,Partition,Reducer,OutputFormat均采用回调机制,即只要应用程序运行就会按照一定的执行顺序执行

1)  InputFormat:

l  数据切分:以某种策略将输入数据切分成若干个Split,以便确定Map个数

l  为Map提供输入数据(key-value格式输入)

InputFormat常用API:

l  getSplits(JobConfconf,int num):尝试将输入数据切分成numSplits个InputSplits(逻辑分片,只记录分片的元数据信息)

l  getRecordReader(InputSplitvar1,JobConf var2,Reporter var3)返回一个RecordReader对象可将输入的InputSplit解析成若干key/value

由于HDFS中数据常以文件形式存在,所以基本使用FileInputFormat作为基类,FileInputFormat常见派生类包括:

TextInputFormat:处理文本文件输入

KeyValueTextInputFormat:处理key-value文件输入

SequenceFileInputFormat:处理.seq文件输入

NLineInputFormat:指定文本文件的N行作为一个InputSplit

CombineFileInputFormat:将多个小文件合并成一个InputSplit

基本上是逻辑分片功能由FileInputFormat.getSplits()完成,而解析InputSplit->Key-Value的过程由各个派生类自己定义

FileInputFormat.getSplits()逻辑分片切分的InputSplits数目由三个参数决定

splitSize=max{minSize,min{maxSize, blockSize}}

//goalSize=totalSize/numSplits:totalSize为总文件大小,numSplits=用户指定Map数

        maxSize;mapred.max.split.size配置项指定

minSize:InputSplit的最小值

blockSize:HDFS中存储的块大小

 

每个InputSplit的逻辑分片元数据信息<file,start,length,hosts>,唯一难确定的就是hosts(文件保存在哪些节点);InputSplit的host列表选择策略:Hadoop依次将数据本地性按照代价划分成三个等级:node locality(TaskTracker处理的数据就在本DataNode节点),rack locality(TaskTracker处理同一个机架上节点的数据),data center locality(处理其他机架上节点的数据)

FileInputFormat设计了一个简单有效的启发式算法: 首先按照rack包含的数据量对rack进行排序,然后在rack内部按照每个node包含的数据量对node排序, 最后取前N个node的host作为InputSplit的host列表,这里的N为block副本数。

 

为了提高Map Task的数据本地性,应尽量使InputSplit大小与block大小相同

 

RecordReader需要考虑:

l  定位记录边界:为了准确定位记录,记录与记录之间需要有同步标示,比如换行符,同步字符串等等

为了解决这种记录跨越InputSplit的读取问题, RecordReader规定每个InputSplit的第一条不完整记录划给前一个InputSplit处理。

l  解析key-value:SequenceFileInputFormat的每条记录的格式为:
[recordlength][key length][key][value]

2)  OutputFormat: 

OutputFormat.getRecordWriter(FileSystem ignored,JobConf job,Stringname,Progressable progress)//返回RecordWriter类对象,无论是map()/reduce()函数产生的结果都会传入write()

OutputFormat.checkOutputSpecs(FileSystem ignored.JobConf job)//用户作业提交到JobTracker前由JobClient自动调用,用于检查输出目录是否合法

publicvoid map( Text key, Textvalue,OutputCollector<Text, Text>output,
Reporterreporter) throws IOException{
output.collect( newKey, newValue) ;
}

output.collect( newKey, newValue)其内部实现为

RecordWriter<K, V>out=job.getOutputFormat().getRecordWriter( ……) ;
out.write( newKey, newValue) ;//用于写新的记录(key-value)

FileOutputFormat的功能包括:

1)  实现checkOutputSpecs接口:

作业提交JobTracker前由JobClient自动调用,用于检查输出目录是否合法(是否存在)

2)  处理side-effectfile:

具有特殊用途的任务专属文件,为了对“慢任务”进行优化,在另外节点开启相同的任务,最先完成任务的计算结果便是最终结果,为了防止两个任务同时往一个输出文件写入数据发生写冲突,FileOutputFormat为每个任务的数据创建一个side-effect file,并将数据临时写入该文件

 

Side-effectfile文件的相关操作由OutputCommitter完成,默认实现FileOutputCommitter

由于HDFS中数据常以文件形式存在,所以基本使用FileOutputFormat作为基类,FileOutputFormat常见派生类包括:

TextOutputFormat:用于文本文件输出

SequenceFileOutputFormat:用于.seq文件输出

MultipleOutputFormat:用于多输出

MapFileOutputFormat:Map文件输出

 

3)  Mapper/Reducer

基本流程包括初始化(setup()),Map操作(map()),清理(cleanup())三部分

       Setup:调用setup()进行初始化

Map:将参数封装在context对象中,添加run()方法用于定制map()

Cleanup:调用close()进行清理

MapReduce中已经实现的Mapper/Reducer

ChainMapper/ChainReducer:支持链式作业

IdentityMapper/IdentityReducer: 对于输入key/value不进行任何处理, 直接输出

InvertMapper:交换key/value位置

RegexMapper:正则表达式字符串匹配

TokenMapper:字符串分割成多个单词
LongSumReducer:对long类型的value累加求和

 

Mapper/Reducer API

4) Partition

只包含待实现的getPartition(key,value,numPatitions);用于对key-value进行分类,指定哪些key-value由哪个Reducer处理

NumPartitions:用户指定的Reducer数量

HashPartitioner和TotalOrderPartitioner是Partitioner的两种实现,前者采用Hash值分片法;后者采用全排序法

 

7. Hadoop Streaming

Hadoop Streaming工具包实际上是一个MapReduce作业,此作业中的Map/Reduce充当了wrapper角色,将输入文件中的key-value直接传给可执行文件或者脚本文件处理,并将结果写入HDFS中

Hadoop Streaming提供了默认的PipeMapper,实际上是C++端Mapper的wrapper,作用是向已创建好的输出流中写入数据key-value,wc_mapper处理完数据后,会直接从标准输入流中获取,HadoopStreaming支持二进制文件,RawBytes(key,value)均为字节序列,TypeBytes指key-value可以拥有的数据类型

RawByes传递给可执行文件或者脚本文件的内容编码格式为:

<4 byte length><key raw bytes><4 byte length><value raw bytes>

TypeBytes传递给可执行文件或者脚本文件的内容编码格式为(长度固定的基本数据类型byte,int,long,bool)

<1 byte type code><key bytes><1 byte type code><value bytes>

TypeBytes传递给可执行文件或者脚本文件的内容编码格式为(长度不固定的数据类型byte array,.string等)

<1 byte type code><4 byte length><key raw bytes><1 byte type code><4 byte length><value raw bytes>

 

8.Hadoop Pipes的实现原理

Hadoop Pipes和HadoopStreaming都是以ProcessBuilder单独进程启动可执行文件(脚本文件),不同之处在于Hadoop Pipe采用Socket通信,而Hadoop Streaming采用标准输入输出流

 

步骤1 用户提交Pipes作业后, Java端启动一个Socket server(等待C++端接入) ,同时以独立进程方式运行C++端代码。
步骤2 C++端以Client身份连接Java端的Socket server,连接成功后, Java端依次发送一系列指令通知C++端进行各项准备工作。
步骤3 Java端通过mapItem()函数不断向C++端传送key/value对,C++端将计算结果返回给Java端, Java端对结果进行保存。
步骤4 所有数据处理完毕后, Java端通知C++端终止计算, 并关闭C++端进程

Java端PipesMapRunner实现了MapRunner,采用DownwardProtocol(发送数据至C++端,UpwardProtocol接收来自C++端数据)

C++端Protocol接收来自Java端的数据,UpwardProtocol发送结果给Java端

 

9.Hadoop工作流

实际上通过Job.addDepending()方法添加作业间的依赖关系,比如JobA.addDepending(JobB),说明只有在JobB执行完成后,Job才能被调用

JobControl中的Job,一开始均处于WAITING模式,当没有依赖Job或者依赖Job全部执行完成,则进入RAEDY状态,便可以上传至Hadoop集群,并进入RUNNING状态,根据Job执行结果,会有SUCCESS或者FAILED状态,不同状态的Job保存在不同的哈希表中

 

JobControl包含一个线程用于周期性地监控和更新各个作业的运行状态,调度依赖作业运行完成的作业, 提交处于READY状态的作业等。 同时, 它还提供了一些API用于挂起、 恢复和暂停该线程。

 

10. ChainMapper/ChainReducer的实现原理

在Map/Reduce阶段存在多个Mapper,前一个执行完成的map结果直接作为后一个map的输入,比如下面例子的执行顺序是Mapper1,Mapper2,Reducer,Mapper3

ChainMapper.addMapper( conf, Mapper1.class, LongWritable.class, Text.class, Text.class,Text.class, true,mapper1Conf) ;
ChainMapper.addMapper( conf, Mapper2.class, Text.class,Text.class, LongWritable.class, Text.class, false,mapper2Conf) ;
ChainReducer.setReducer( conf, Reducer.class,LongWritable.class, Text.class, Text.class, Text.class, true,reduce1Conf);
ChainReducer.addMapper( conf, Mapper3.class,Text.class, Text.class, LongWritable.class, Text.class, false,null) ;

链式作业的执行流程是,将各个Mapper的JobConf对象反序列化,并构造Mapper和Reducer对象,添加到结构mappers和reduce中,ChainMapper中实现的map()会一个一个调用ChainMapper中的mapper

public void map(Object key, Object value, OutputCollector output,Reporterreporter) throws IOException{

Mapper mapper=chain.getFirstMap();
if( mapper! =null) {
mapper.map( key, value, chain.getMapperCollector( 0, output, reporter) , reporter) ;
} }

chain.getMapperCollector会返回OutputCollector的派生类ChainOutputCollector

 

11.Hadoop工作流引擎

隐式工作流引擎分为三层分别为:

Ø  功能描述层:面向用户提供简单的应用程序编写方法

Ø  作业生成器:将应用程序转换为一批MapReduce作业,DAG

Ø  调度引擎:直接构建于MapReduce环境之上,根据DAG依赖关系依次调度作业到MapReduce上执行

 

显示工作流引擎分为两层:

Ø  工作流描述语言:描述作业的依赖关系

Ø  同隐式相同

 

第3章MapReduce编程模型

3.1 Hadoop RPC框架

1)序列化层:对象->字节流便于网络传输或者持久化存储(实现Writable接口)

2)函数调用层:基于Java反射机制与动态代理实现函数调用,定位要调用的函数并执行

3)网络传输层:TCP/IPSocket机制

4) 服务器端处理框架:网络I/O模型,包括阻塞式I/O,非阻塞式I/O,事件驱动I/O,Hadoop RPC 采用基于Reactor设计模式的事件驱动I/O

 

3.2 Java动态代理(基于Java反射机制)

由于JVM使用某个类时,需要将.class文件加载到JVM中,且会创建唯一的class对象(包含类的全部信息),与Java反射机制相关的类分别为:

1)  Class类

2)  Field类:Java类中的属性

3)  Method类:Java类中的方法

4)  Constructor类:Java类中的构造方法

5)  Array类:动态创建数组,以及访问数组元素的静态方法

6)  Proxy类以及InvocationHandler接口:动态生成代理类以及实例的方法,动态代理的意义在于为其他对象提供控制对该对象访问,主要负责对委托类进行预处理(如安全检查,权限检查等等)或者执行完后的后续处理

 

Proxy类的静态方法:

//方法1: 获取指定代理对象所关联的调用处理器
static InvocationHandler getInvocationHandler( Objectproxy)
//方法2: 获取关联于指定类装载器和一组接口的动态代理类的对象
static Class getProxyClass( ClassLoader loader,Class[]interfaces)
//方法3: 判断指定的类对象是否是一个动态代理类
static boolean isProxyClass( Class cl)
//方法4: 为指定类装载器、 一组接口及调用处理器生成动态代理类实例
static Object newProxyInstance( ClassLoader loader,Class[]interfaces, InvocationHandler h)

 

InvocationHandler为调用处理器接口,定义了invoke()方法用于处理动态代理类对象上的方法调用,动态代理类基本使用步骤:

1)  创建动态代理类监控的接口,并实现其接口

2)  通过实现InvocationHandler接口从而创建自定义的调用处理器,实际上是实现invoke(),其中method.invoke()需要指定代理类监控的对象objOriginal
public Object invoke( Objectproxy, Method method, Object[]args)
throws Throwable{
//可添加一些预处理
Object result=method.invoke( this.objOriginal, args);
//可添加一些后续处理
return result;
}

3)  创建动态代理类对象proxy

CalculatorProtocol client=( CalculatorProtocol) Proxy.newProxyInstance( server.
getClass().getClassLoader(), server.getClass().getInterfaces(), handler) ;
//创建一个Cli,server为proxy监控的对象,handler为已创建的调用处理器对象,client则为proxy对象

4)  proxy对象调用监控对象的方法时执行顺序

Ø  int r=client.add( 5,3) ;//proxy调用监控对象的方法

Ø  触发handler的invoke()方法,由invoke()做预处理,并调用监控对象的实际方法得到返回值

Ø  返回proxy返回值

 

 

3.3 Java NIO(非阻塞I/O)

主要包含以下数据结构:

1)  Channel(通道):类似于stream,可以通过它读取和写入数据

Ø  SelectableChannel:即支持阻塞又支持非阻塞的I/O通道

SelectionKey register(Selector sel,int opt)throws ClosedChannelException;//为当前通道注册Selector以及监控的事件opt,返回SelectionKey用于跟踪事件

Ø  ServerSocketChannel:用于监听TCP连接

ServerSocketChannel open():创建一个ServerSocketChannel对象

ServerSocketChannel.bind():绑定监听IP+Port

ServerSocketChannel.accept();//接收连接请求,并返回SocketChannel

Ø  SocketChannel:用于通信

SocketChannel open();创建SocketChannel对象

Boolean connect(SocketAddress remote);连接服务器端的IP+Port,若为非阻塞I/O,则直接返回,否则进入阻塞状态

Int read(ByteBuffer dst);//若为非阻塞I/O,遵循能读多少是多少的原则;若为阻塞I/O则要么填满ByteBuffer,要么已到达输入流末尾

Int write(ByteBuffer src);//若为非阻塞I/O,遵循能写多少是多少的原则;若为阻塞I./O尝试将所有数据写入Channel

2)  Buffer(缓冲区):数据->缓冲区->通道(写);通道->缓冲区->数据(读)

Buffer的三个属性大小关系capacity>=limit>=position>=0,且有两种不同的工作模式:

Capacity:缓冲区末位值,即最多保存多少数据

Limit:缓冲区当前存放数据的终点

Position:下一个读写单元的位置·

Ø  写模式:limit与capacity相同,position随着写入数据增加, 逐渐增加到limit, 因此, 0到position之间的数据即为已经写入的数据

Ø  读模式:在读模式下, limit初始指向position所在位置, position随着数据的读取, 逐渐增加到limit, 则0到position之间的数据即为已经读取的数据

Buffer API方法:

Ø  flip():Buffer写模式->读模式

Ø  clear():重置Buffer,limit=capacity且position=0

Ø  hasRemaining():Buffer是否有剩余空间

Ø  remaining():Buffer的剩余空间limit-position

Ø  limit(),capacity(),position():分别设置新的值

3)  Selector(选择器):监控一个或者多个通道当前状态的机制,只要通道向Selector注册了某种特定事件,Selector就会监听这些事件是否发生,一旦发生便会通知相应的Channel

Selector open();创建Selector对象

Int select(long timeout)等待并返回发生的事件,返回值为对应的SelectionKey数目,否则一直处于阻塞状态,直到四种情况出现:

Ø  至少有一个事件发生

Ø  其他线程调用Selector的wakeuo()方法

Ø  Set selectedKeys():Selector已捕获发生事件的SelectionKey、集合

Ø  Selector wakeup():立刻唤醒当前处于阻塞状态的Selector

 

其中SelectionKey一共定义了四种事件

1)    SelectionKey.OP_ACCEPT:接收连接就绪事件

2)    SelectionKey.OP_CONNECT:连接就绪事件

3)    SelectionKey.OP_READ:读就绪事件

4)    SelectionKey.OP_WRITE:写就绪事件

 

SelectKey API方法:

1)  Object attach(Object o):为当前SelectionKey关联一个Object的对象

2)  Object attachment();获取当前SelectionKey关联的Object对象

3)  SelectableChannel channel();返回与当前SelectionKey关联的SelectableChannel对象

3.4 RPC框架

RPC通常采用C/S模型

Ø  通信模块:两个通信模块实现请求-应答协议,分为同步模式和异步模式两种

Ø  Sub程序:相当于Proxy程序,客户端Sub程序发送请求(远程方法调用);服务器端Sub程序:解码请求消息中的参数,调用相应的服务过程和编码应答结果的返回值

Ø  调度程序:调度程序接收来自通信模块的请求消息,并根据其中的标识选择一个Stub程序处理

Ø  客户程序/服务过程:;请求发出者和请求处理者

RPC通用框架:

步骤1 客户程序以本地方式调用系统产生的Stub程序;
步骤2 该Stub程序将函数调用信息按照网络通信模块的要求封装成消息包, 并交给通信模块发送到远程服务器端;
步骤3 远程服务器端接收此消息后, 将此消息发送给相应的Stub程序;
步骤4 Stub程序拆封消息, 形成被调过程要求的形式, 并调用对应的函数;
步骤5 被调用函数按照所获参数执行, 并将结果返回给Stub程序;
步骤6 Stub程序将此结果封装成消息, 通过网络通信模块逐级地传送给客户程序。

 

3.5 Hadoop RPC框架

主要提供了两种接口:

Public static VersionedProtocolgetProxy()/waitForProxy();构造客户端代理对象,用于向服务器端发送RPC请求

Public static Server getServer():构造服务器对象,处理客户端发送的请求

Hadoop RPC框架的使用步骤包括:

Ø  定义RPC协议:自定义RPC接口都需要继承VersionedProtocol(versionID必须)

Ø  实现RPC协议

Ø  构造并启动RPC Server,RPC.Server继承于Server抽象类,并利用Java反射机制实现了call接口,即根据客户端请求的调用方法名和对应参数完成方法调用

Ø  构造RPC Client,发送RPC请求

 

RPC.ClientCache类型的成员变量,根据用户提供的SocketFactory缓存Client对象,以大奥重用Client对象的目的

 

在invoke方法中,将函数调用信息(函数名、函数参数列表等)打包成可序列化的Invocation对象,并通过网络传输给服务器端,服务器端解析后通过Java反射机制调用

 

Client.Call:封装RPC请求,包含唯一标识ID,函数调用信息param,函数执行返回值value,出错或者异常信息error和执行完成标识符done,RPC.Server采用异步方式处理客户端请求

Client.Connection:Client与每个Server之间的通信连接,其连接信息以及操作会被封装到Connection类中,包括通信连接唯一标识remoteId,与Server端通信的Socket,网络输入数据流in,网络输出数据流out,保存RPC请求的哈希表calls

 

Hadoop RPC Client处理流程:

步骤1 创建一个Connection对象,并将远程方法调用信息封装成Call对象, 放到Connection对象中的哈希表calls中;
步骤2 调用Connetion类中的sendParam()方法将当前Call对象发送给Server端;
步骤3 Server端处理完RPC请求后, 将结果通过网络返回给Client端, Client端通过receiveResponse()函数获取结果;
步骤4 Client端检查结果处理状态( 成功还是失败) , 并将对应的Call对象从哈希表中删除。

 

Reactor设计模式具有两个特点:通过派发/分离I/O操作事件提供系统的并发性能;提供了粗粒度的并发控制,其工作原理图如下;

 

❑Reactor: IO事件的派发者。
❑Acceptor: 接受来自Client的连接, 建立与Client对应的Handler, 并向Reactor注册此Handler。
❑Handler: 与一个Client通信的实体, 并按一定的过程实现业务的处理。 Handler内部往往会有更进一步的层次划分, 用来抽象诸如read,decode,compute, encode和send等的过程。 在Reactor模式中,业务逻辑被分散的IO事件所打破, 所以Handler需要有适当的机制在所需的信息还不全(读到一半) 的时候保存上下文, 并在下一次IO事件到来的时候( 另一半可读了) 能继续上次中断的处理。
❑Reader/Sender: 为了加速处理速度, Reactor模式往往构建一个存放数据处理线程的线程池,这样, 数据读出后, 立即扔到线程池中等待后续处理即可。 为此, Reactor模式一般分离Handler中的读和写两个过程, 分别注册成单独的读事件和写事件, 并由对应的Reader和Sender线程处理

1)接收请求

RPC.Server基于事件驱动的Reactor设计模式,其内部主要有两个线程Listener和Reader分别处理建立连接和接收请求,前者一个Server只对应一个Listener线程,统一负责监听来自客户端的连接请求,后者当有新的请求到达时,采用轮询方式从线程池中选择一个Reader进行处理,监听客户端连接中是否有新的RPC请求到达,并将新的RPC请求封装成Call对象放入callQueue中

3)  处理请求

从共享队列callQueue中获取Call对象,执行对应的函数调用,并将结果返回客户端,均由Handler线程完成

4)  返回结果

对于特殊情况,比如返回结果过大或者网络异常情况等,会将发送任务交给Responder线程完成,Server只存在一个Responder线程(异步I/O)

 

Hadoop RPC性能调优

Ø  Reader线程数目:ipc.server.read.threadpool.size

Ø  每个Handler线程对应的最大Call数目:ipc.server.handler.queue.size指定

Ø  Handler线程数目:MapReduce由mapred.job.tracker.count,HDFS由dfs.namenode.service.handler.count指定

Ø  客户端最大重试次数:ipc.client.connect.max.retries

 

3.6 MapReduce通信协议

1)面向客户端的通信协议

Ø  JobSubmissionProtocol:Client与JobTracker之间的通信协议,提交作业,查看作业运行情况

该协议的接口分为三类:

1)  作业提交:

public JobStatussubmitJob( JobID jobName, String jobSubmitDir,Credentials ts) throws IOException;

jobName:作业ID

jobSubmitDir:作业文件(jar)所在HDFS目录

ts:作业分配的密钥

2)  作业控制

修改作业优先级(setJobPriority),杀死作业(killJob函数),杀死任务(killTask函数)

3)  查看系统状态和作业运行状态

//获取集群当前状态, 如slot总数, 所有正在运行的Task数目等
public ClusterStatus getClusterStatus( boolean detailed) throws IOException;
//获取某个作业的运行状态
public JobStatus getJobStatus( JobID jobid) throws IOException;
//获取所有作业的运行状态
public JobStatus[]getAllJobs()throws IOException;

Ø  RefreshUserMappingProtocol:更新用户与用户组的映射关系,更新超级用户代理列表

public void refreshUserToGroupsMappings()throws IOException;
public void refreshSuperUserGroupsConfiguration()throws IOException;

Ø  RefreshAuthorizationPolicyProtocol;更新MapReduce服务级别访问控制列表

RefreshServiceAcl

Ø  AdminOperationsProtocol:更新队列访问控制列表和节点列表

2)MapReduce内部通信协议

Ø  InterTrackerProtocol:TaskTracker和JobTracker之间的通信协议,TaskTracker利用此接口汇报本节点的资源使用情况和任务运行状态,并执行JobTracker的命令

HeartbeatResponse heartbeat(TaskTrackerStatus status,boolean restarted,boolean initialContact,booleanacceptNewTasks,short responseId)throws IOException;

Status:TaskTracker的资源使用情况和任务的运行情况

HeartbeatResponse:JobTracker对于TaskTracker的命令

Ø  TaskUmbilcalProtocol:Task与TaskTracker之间的通信协议,向TaskTracker汇报Task的运行状态和运行进度

1)  周期性被调用的方法

//Task向TaskTracker汇报自己的当前状态,状态信息被封装到TaskStatus中
boolean statusUpdate( TaskAttemptID taskId, TaskStatustaskStatus,
JvmContext jvmContext) throws IOException,InterruptedException;
//Task周期性探测TaskTracker是否活着
boolean ping( TaskAttemptID taskid, JvmContextjvmContext) throws IOException;

2)  按需调用的方法:

2.1)Task初始化:当创建新的子进程(启动新的Task)时,调用getTask()获得对应的Task

2.2)Task运行中

汇报错误及异常:

汇报记录的范围

获取Map Task完成列表

2.3)Task运行完成

第五章 作业提交和初始化过程分析

MapReduce作业提交和初始化过程可分为四个步骤:

步骤1:使用Hadoop Shell命令/waitforcomplete()提交作业至JobClient

步骤2:JobClient根据作业配置信息将作业运行所需的全部文件(jar文件,配置文件,Splits)上传至HDFS

步骤3:JobClient调用RPC接口提交作业至JobTracker

步骤4:JobTracker接收到作业后添加,并交由TaskScheduler进行作业初始化过程

 

5.1 Hadoop Shell命令/waitforcomplete()提交作业至JobClient

main函数经解压jar包和设置环境变量后,将运行参数传递给MapReduce程序,用户的MapReduce程序已经配置了作业运行时需要的各种信息(如Mapper类, Reducer类, Reduce Task个数等) , 它最终main函数中调用MapReduce API 的job.waitForCompletion( true) 函数提交作业

 

5.2 作业文件上传

由JobClient.submitJobInternal( job)函数完成提交作业到JobTracker前的准备,包括获取Job ID,创建HDFS目录,上传作业所需文件,生成Splits

对于一个典型的Java MapReduce作业, 可能包含以下资源。
❑程序jar包:用户用Java编写的MapReduce应用程序jar包。
❑作业配置文件: 描述MapReduce应用程序的配置信息( 根据JobConf对象生成的xml文件) 。
❑依赖的第三方jar包:应用程序依赖的第三方jar包, 提交作业时用参数“-libjars”指定。
❑依赖的归档文件: 应用程序中用到多个文件, 可直接打包成归档文件( 通常为一些压缩文件) ,提交作业时用参数“-archives”指定。
❑依赖的普通文件: 应用程序中可能用到普通文件, 比如文本格式的字典文件, 提交作业时用参数“-files”指定。

DistributedCache将上传的文件分为private和public两种级别,其中private只能被当前用户使用,public级别则会在每个节点上保存1份,可被本节点上所有的作业和用户共享

 

5.3 生成splits

JobClient调用InputFormat的getSplits()生成InputSplit相关信息,包括InputSplit的元数据信息<file,offset,length,hosts>和原始Splits,前者会被JobTracker使用,生成Task相关的数据结构;后者会被Map Task使用,作为源数据输入

 

Ø  SplitMetaInfo:描述InputSplits的元数据信息,保存在文件job.splitmetainfo中

privatelong startOffset; //该InputSplit元信息在job.split文件中的偏移量
private long inputDataLength;//该InputSplit的数据长度
private String[]locations;//该InputSplit所在的host列表

JobTracker使用splitmetainfo信息初始化Job,并创建Map/Reduce Task

Ø  TaskSplitMetaInfo:用于保存InputSplit元信息的数据结构

privateTaskSplitIndex splitIndex; //Split元信息在job.split文件中的位置
privatelong inputDataLength; //InputSplit的数据长度
privateString[]locations; //InputSplit所在的host列表

TaskTracker收到TaskSplitMetaInfo信息后,便可以从job.split文件中读取源数据

 

5.4 作业提交给JobTracker

JobClient通过RPC接口提交作业至JobTracker后,JobTracker.submitJob()会依次执行:

1)  为作业创建JobInProgress对象

用于维护作业运行时的信息

2)  检查用户是否具有指定队列的作业提交权限

3)  检查作业配置的内存管理量是否合理

4)  通知TaskScheduler初始化作业

可自定义TaskScheduler,默认的调度器是JobQueueTaskScheduler(FIFO),FairScheduler和CapacityScheduler

JobTracker采用了观察者的设计模式,将提交新作业事件告知TaskScheduler

publicsynchronized void start()throws IOException{
super.start();
//此处的taskTrackerManager实际上就是JobTracker对象, 向JobTracker注册一个
//JobQueueJobInProgressListener
taskTrackerManager.addJobInProgressListener( jobQueueJobInProgressListener) ;
eagerTaskInitializationListener.setTaskTrackerManager( taskTrackerManager) ;
eagerTaskInitializationListener.start();
//向JobTracker注册EagerTaskInitializationListener
taskTrackerManager.addJobInProgressListener(
eagerTaskInitializationListener) ;
}

比如上述源码,TaskScheduler向JobTracker注册了两个JobInProgressListener观察者,只要JobTracker中Job事件发生(添加,删除,更新)都会通知jobQueueJobInProgressListener(排序),eagerTaskInitializationListener(初始化)并执行相应的处理

 

TaskTracker初始化Job实际上是创建Task(SetupTask,MapTask,Reduce Task,CleanupTask)

 

上述4种Task的作用以及创建过程:

Ø  Setup Task:作业初始化标识性任务,比如将运行状态设置为“setup”,调用OutputCommitter.setupJob()函数等,该任务执行完成后,作业由PREP状态变为RUNNING状态,并开始运行Map Task/Reduce Task,分别占用一个Map Slot/Reduce Slot

Ø  Map Task:Map阶段处理数据的任务

Ø  Reduce Task:Reduce阶段处理数据的任务

Ø  Cleanup Task:作业结束标志性任务,主要完成一些清理工作以及把作业由RUNNING->SUCCESSED

 

4种任务的调用顺序Setup Task->Map Task->ReduceTask->Cleanup Task

5.5 Hadoop Distributed Cache

允许用户分发归档文件和普通文件,一般使用步骤包括:

1)  准备文件,即将普通文件上传至public目录,归档文件上传至private目录,两个目录的权限不同

$bin/hadoopfs-copyFromLocal dictionary.zip/data/private/
$bin/hadoopfs-copyFromLocal third-party.jar/data/private/
$bin/hadoopfs-copyFromLocal blacklist.txt/data/public/

2)  配置JobConf

JobConf job=new JobConf();
DistributedCache.addCacheFile( new URI( "/data/public/blacklist.txt#blacklist") , job) ;

DistributedCache.addCacheArchive( new URI( "/data/private/dictionary.zip", job) ;

3)Map/Reduce中使用文件

localArchives=DistributedCache.getLocalCacheArchives( job) ;
localFiles=DistributedCache.getLocalCacheFiles( job) ;

DistributedCache工作原理图:

步骤1 用户提交作业后, DistributedCache将作业文件上传到HDFS上的固定目录中, 具体见5.2.2节。
步骤2 JobTracker端的任务调度器将作业对应的任务发派到各个TaskTracker上。
步骤3 任何一个TaskTracker收到该作业的第一个任务后, 由DistributedCache自动将作业文件缓存到本地目录下( 对于后缀为.zip、.jar、.tar、.tgz或者.tar.gz的文件, 会自动对其进行解压缩) , 然后开始启动该任务。
步骤4 对于TaskTracker接下来收到的任务, DistributedCache不会再重复为其下载文件, 而是直接运行。

 

❑DistributedCache类: 可供用户直接使用的外部类。 它提供了一系列addXXX、 setXXX和getXXX方法以配置作业需借用DitributedCache分
发的只读文件。
❑TaskDistributedCacheManager类: Hadoop内部使用的类, 用于管理一个作业相关的缓存文件。
❑TrackerDistributedCacheManager类: Hadoop内部使用的类, 用于管理一个TaskTracker上所有的缓存文件。 它只用于缓存public可见级别
的文件, 而对于private可见级别的文件, 则由org.apache.hadoop.mapred包中的JobLocalizer类进行缓存。

 

第6章JobTracker内部实现

6.1JobTracker概述

JobTracker主要作用:容错和为任务调度提供决策依据

(1)      作业控制:

JobTracker以三层多叉树方式描述每个作业的运行状态,作业被抽象成三层:作业监控层,任务监控层,任务执行层

作业监控层:每个作业由一个JobInProgress对象描述和跟踪其整体运行状态以及每个任务的运行情况

任务监控层:每个任务由一个TaskInProgress对象描述和跟踪其运行状态

任务执行层:每个任务可能尝试多次,任务运行实例Task Attempt

 

JobTracker中存在两个Map数据结构,jobs(JobID->JobInProgress),trackerToTaskMap(trackerID->TaskID)

(2)      资源管理:

根据每个TaskTracker汇报的心跳信息,从而为TaskTracker分配合适的Task

 

6.2 JobTracker启动过程详解

核心代码:

JobTracker tracker=startTracker(new JobConf()); //创建并初始化JobTracker
tracker.offerService();//启动各个服务

 

l  StartTracker()需要创建JobTracker时需要初始化的变量:

Ø  ACLsManager类:调用checkaccess()对用户各种操作进行权限检查,涉及到两种权限:队列权限(QueueManager)和作业权限(JobACLsManager)

1)     QueueManager:队列权限包括作业提交权限和作业管理权限

2)     JobACLsManager:用户提交作业时,可设定作业的查看权限和修改权限

Ø  HttpServer类

封装了jetty,可提供HTTP服务

Ø  DNSToSwitchMapper

定义了DNS名称或者节点IP地址转换成网络位置的规则,比如dcX/rackX/nodeX可表示一个节点的网络位置即在数据中心dcX上的rackX机架上的nodeX节点

 

l  offerService()启动JobTracker后台服务线程

Ø  expireTrackerThread:主要用于发现和清理挂掉的TaskTracker,基于心跳检测

Ø  retireJobsThread:主要用于清理长时间驻留在内存中的已经运行完成的作业信息

 

当满足条件1,2或者条件1,3时,驻留在内存中的job会从jobs结构中移到过期作业队列

条件1 作业已经运行完成, 即运行状态为SUCCEEDED、 FAILED或KILLED。
条件2 作业完成时间距现在已经超过24小时( 可通过参数mapred.jobtracker.retirejob.interval配置) 。
条件3 作业拥有者已经完成作业总数超过100( 可通过参数mapred.jobtracker.completeuserjobs.maximum配置) 个

 

过期作业被统一保存到过期队列中。 当过期作业超过1 000个(可通过参数mapred.job.tracker.retiredjobs.cache.size配置) 时,将会从内存中彻底删除。

Ø  expireLaunchingTaskThread:主要用于发现和清理已分配给TaskTracker但一直未汇报信息的Task

Ø  completedJobsStoreThread:主要通过保存运行日志的方式,使得用户可以查询任意时间提交的作业和还原作业的运行信息

 

6.3 作业恢复

JobTracker会利用JobHistory记录关键事件,

Ø  对于作业而言的关键事件:

作业提交,作业创建,作业开始运行,作业运行完成,作业运行失败,作业被杀死,

Ø  对于任务而言的关键事件:

任务创建,任务开始运行,任务运行完成,任务运行失败,任务被杀死

若管理员启用了作业恢复功能,则JobTracker会检查是否需要恢复运行状态的作业,如果有则通过日志恢复作业的运行状态,并重新调度未运行完成的任务、

 

6.4 心跳接收与应答

TaskTracker会周期性的向JobTracker发送心跳信息(包括TaskTracker的工作状态,资源使用情况),心跳检测的目的在于:

1)  检查TaskTracker是否活着

2)  让JobTracker获取各个节点的资源使用情况和任务运行状态

3)  为TaskTracker分配任务

 

public synchronized HeartbeatResponse heartbeat( TaskTrackerStatus status,boolean restarted,boolean initialContact,boolean acceptNewTasks,short responseId)

status:封装了TaskTracker的各种状态信息

restarted:TaskTracker是否刚刚重新启动

initalContact:TaskTracker是否初次连接JobTracker

acceptNewTasks:TaskTracker是否可以接收新任务,取决于剩余slot以及节点健康状况

responseId:心跳响应编号,防止重复发送心跳

 

返回值HeartbeatResponse,封装了JobTracker对TaskTracker的命令

class HeartbeatResponse implements Writable, Configurable{
……
short responseId; //心跳响应编号
int heartbeatInterval;//下次心跳的发送间隔
TaskTrackerAction[]actions;/*来自JobTracker的命令, 可能包括杀死作业、 杀死任务、 提
交任务、 运行任务等, 具体参考6.3.2节*/
Set<JobID>recoveredJobs=new HashSet<JobID>();//恢复完成的作业列表, }

 

heartbeat函数内部实现逻辑:更新状态和下达命令

Ø  更新状态,即更新TaskTracker/Job/Task的状态信息

Heartbeat函数内部更新TaskTracker的信息,包括restarted,responseId等等

updateTaskStatuses( trackerStatus) ; //更新Task状态信息
updateNodeHealthStatus( trackerStatus,timeStamp) ; //更新节点健康状态

Ø  下达命令(TaskTrackerAction)

包括:ReinitTrackerAction( 重新初始化) 、LaunchTaskAction( 运行新任务) 、 KillTaskAction( 杀死任务) 、 KillJobAction( 杀死作业) 和CommitTaskAction( 提交任务)

  ReinitTrackerAction:JobTracker向TaskTracker发出该命令的情况有以下两种

1)  JobTracker丢失最近心跳应答信息

2)  JobTracker丢失TaskTracker状态信息

  LaunchTaskAction:封装了JobTracker给TaskTracker新分配的任务,TaskTracker接收到该命令后会启动子进程运行,其中JobTracker任务选择顺序为job-cleanup task->task-cleanuptask->job-setup task->map/reduce task

  KillTaskAction:封装了需要杀死的Task,TaskTracker收到该命令后会杀死Task,清理工作目录和释放slot

❑用户使用命令“bin/hadoop job-kill-task”或者“bin/hadoop job-fail-task”杀死一个任务或者使一个任务失败。
❑启用推测执行机制后, 同一份数据可能同时由两个Task Attempt处理。 当其中一个Task Attempt执行成功后, 另外一个处理
相同数据的Task Attempt将被杀掉。
❑某个作业运行失败, 它的所有任务将被杀掉。
❑TaskTracker在一定时间内未汇报心跳, 则JobTracker认为其死掉, 它上面的所有Task均被标注为死亡。

  KillJobAction:封装了TaskTracker待清理的作业,清理作业的工作目录

❑用户使用命令“bin/hadoop job-kill”或者“bin/hadoop job-fail”杀死一个作业或者使一个作业失败。
❑作业运行完成, 通知TaskTracker清理该作业的工作目录。
❑作业运行失败, 即同一个作业失败的Task数目超过一定比例

  CommitTaskAction:封装了TaskTracker需提交的任务,Hadoop将一个成功运行完成的Task Attempt结果文件从临时目录“提升”至最终目录的过程, 称为“任务提交”。

调整心跳间隔:JobTracker为每个TaskTracker计算下一次汇报心跳的时间间隔, 并通过心跳机制告诉TaskTracker,其计算方法如下:

intheartbeatInterval=Math.max((int)( 1000*HEARTBEATS_SCALING_FACTOR*
Math.ceil( ( double) clusterSize/NUM_HEARTBEATS_IN_SECOND) ),HEARTBEAT_INTERVAL_MIN) ;

6.5 Job和Task运行时信息维护

JobTracker最为重要的便是状态监控,基于三层多叉树监控Job/Task工作状态

JobID:job_201208071706_0009

TaskID:task_201208071706_0009_m_000000

AttemptID:attempt_201208071706_0009_m_000000_0

Ø  JobInProgress

1)  作业静态信息:作业提交时已经确定好的属性信息

//maptask, reduce task, cleanup task和setup task对应的TaskInProgress
TaskInProgress maps[]=new TaskInProgress[0];
TaskInProgress reduces[]=new TaskInProgress[0];
TaskInProgress cleanup[]=new TaskInProgress[0];
TaskInProgress setup[]=new TaskInProgress[0];
int numMapTasks=0; //Map Task个数
int numReduceTasks=0;//Reduce Task个数
final long memoryPerMap;//每个Map Task需要的内存量
final long memoryPerReduce;//每个Reduce Task需要的内存量
volatile int numSlotsPerMap=1;//每个Map Task需要的slot个数
volatile int numSlotsPerReduce=1;//每个Reduce Task需要的slot个数
/*允许每个TaskTracker上失败的Task个数, 默认是4, 通过参数mapred.max.tracker.failures
设置。 当该作业在某个TaskTracker上失败的个数超过该值时, 会将该节点添加到该作业的黑名单中, 调度
器便不再为该节点分配该作业的任务*/
final int maxTaskFailuresPerTracker
private static float DEFAULT_COMPLETED_MAPS_PERCENT_FOR_REDUCE_SLOWSTART=0.05f;
//当有5%的Map Task完成后, 才可以调度Reduce Task
int completedMapsForReduceSlowstart=0;//多少Map Task完成后开始调度Reduce Task
……
//允许的Map Task失败比例上限, 通过参数mapred.max.map.failures.percent设置
final int mapFailuresPercent;
//允许的Reduce Task失败比例上限, 通过参数mapred.max.reduce.failures.percent设置
final int reduceFailuresPercent;
……
JobPriority priority=JobPriority.NORMAL;//作业优先级

2)  作业动态信息

intrunningMapTasks=0; //正在运行的Map Task数目
int runningReduceTasks=0;//正在运行的Reduce Task数目
int finishedMapTasks=0;//运行完成的Map Task数目
int finishedReduceTasks=0;//运行完成的Reduce Task数目
int failedMapTasks=0;//失败的Map Task Attempt数目
int failedReduceTasks=0;//失败的Reduce Task Attempt数目
……
int speculativeMapTasks=0;//正在运行的备份任务( MAP) 数目
int speculativeReduceTasks=0;//正在运行的备份任务( REDUCE) 数目
int failedMapTIPs=0;/*失败的TaskInProgress(MAP)数目, 这意味着对应的输入数据将被丢弃,
不会产生最终结果*/
int failedReduceTIPs=0;//失败的TaskInProgress(REDUCE)数目
private volatile boolean launchedCleanup=false; //是否已启动Cleanup Task
private volatile boolean launchedSetup=false; //是否已启动Setup Task
private volatile boolean jobKilled=false;//作业是否已被杀死
private volatile boolean jobFailed=false;//作业是否已失败
//节点与TaskInProgress的映射关系, 即TaskInProgress输入数据位置与节点对应关系
Map<Node, List<TaskInProgress>>nonRunningMapCache;
//节点及其上面正在运行的Task映射关系
Map<Node, Set<TaskInProgress>>runningMapCache;
/*不需要考虑数据本地性的Map Task, 如果一个Map Task的InputSplit Location为空, 则进行任
务调度时不需考虑本地性*/
final List<TaskInProgress>nonLocalMaps;
//按照失败次数进行排序的TIP集合
final SortedSet<TaskInProgress>failedMaps;
//未运行的Map Task集合
Set<TaskInProgress>nonLocalRunningMaps;
//未运行的Reduce Task集合
Set<TaskInProgress>nonRunningReduces;
//正在运行的Reduce Task集合
Set<TaskInProgress>runningReduces;
//待清理的Map Task列表, 比如用户直接通过命令“bin/hadoop job-kill”杀死的Task
List<TaskAttemptID>mapCleanupTasks=new LinkedList<TaskAttemptID>();
List<TaskAttemptID>reduceCleanupTasks=new LinkedList<TaskAttemptID>();
long startTime; //作业提交时间
long launchTime; //作业开始执行时间
long finishTime; //作业完成时间

Ø  TaskInProgress:维护了Task运行的全部信息,由于同一时间多个相同任务可能同时执行,只要有一个Task执行成功,便标记该任务成功

privatefinal TaskSplitMetaInfo splitInfo;//Task要处理的Split信息
private int numMaps;//Map Task数目, 只对Reduce Task有用
private int partition;//该Task在task列表中的索引
private JobTracker jobtracker;//JobTracker对象, 用于获取全局时钟
private TaskID id; //task ID, 其后面加下标构成Task Attempt ID
private JobInProgress job;//该TaskInProgress所在的JobInProgress
private final int numSlotsRequired;//运行该Task需要的slot数目
private int successEventNumber=-1;
private int numTaskFailures=0;//Task Attempt失败次数
private int numKilledTasks=0;//Task Attempt被杀死次数
private double progress=0;//任务运行进度
private String state="";//运行状态
private long startTime=0;//TaskInProgress对象创建时间
private long execStartTime=0;//第一个Task Attempt开始运行时间
private long execFinishTime=0;//最后一个运行成功的Task Attempt完成时间
private int completes=0;//Task Attempt运行完成数目, 实际只有两个值: 0和1
private boolean failed=false;//该TaskInProgress是否运行失败
private boolean killed=false;//该TaskInProgress是否被杀死
private boolean jobCleanup=false;//该TaskInProgress是否为Cleanup Task
private boolean jobSetup=false;//该TaskInProgress是否为Setup Task
//该TaskInProgress的下一个可用Task Attempt ID
int nextTaskId=0;
//使得该TaskInProgress运行成功的那个Task ID
private TaskAttemptID successfulTaskId;
//第一个运行的Task Attempt的ID
private TaskAttemptID firstTaskId;
//正在运行的Task ID与TaskTracker ID之间的映射关系
private TreeMap<TaskAttemptID,String>activeTasks=newTreeMap<TaskAttemptID,String>();
//该TaskInProgress已运行的所有TaskAttempt ID, 包括已经运行完成的和正在运行的
private TreeSet<TaskAttemptID>tasks=new TreeSet<TaskAttemptID>();
//Task ID与TaskStatus映射关系
private TreeMap<TaskAttemptID,TaskStatus>taskStatuses=
new TreeMap<TaskAttemptID,TaskStatus>();
//Cleanup Task ID与TaskTracker ID映射关系
private TreeMap<TaskAttemptID,String>cleanupTasks=
new TreeMap<TaskAttemptID,String>();
//所有已经运行失败的Task所在的节点列表
private TreeSet<String>machinesWhereFailed=new TreeSet<String>();
//某个Task Attempt运行成功后, 其他所有正在运行的Task Attempt保存在该集合中
private TreeSet<TaskAttemptID>tasksReportedClosed=new TreeSet<TaskAttemptID>();
//待杀死的Task列表
private TreeMap<TaskAttemptID,Boolean>tasksToKill=newTreeMap<TaskAttemptID,
Boolean>();
//等待被提交的Task Attempt, 该Task Attempt最终使得TaskInProgress运行成功
privateTaskAttemp

 

6.6 作业和任务状态转换图

❑PREP→RUNNING: 作业的Setup Task( job-setup Task)成功执行完成。
❑RUNNING→SUCCEEDED:作业的Cleanup Task( job-cleanup Task) 执行成功。
❑PREP→FAILED/KILLED:人为使用Shell命令杀死作业, 即bin/hadoopjob[-kill|-fail]<jobid>。
❑RUNNING→FAILED:多种情况可导致该状态转移, 包括人为使用Shell命令杀死作业( 使用“bin/hadoopjob-fail<jobid>”命
令) , 作业的Cleanup/Setup Task运行失败和作业失败的任务数超过了一定比例。
❑RUNNING→KILLED:人为使用Shell命令杀死作业, 比如使用“bin/hadoopjob-kill<jobid>”命令。

❑UNASSIGNED→RUNNING: 任务初始化状态为UNASSIGNED,当JobTracker将任务分配给某个TaskTracker后, 该TaskTracker会为它准备运行环境并启动它, 之后该任务进入RUNNING状态。
❑RUNNING→COMMIT_PENDING: 该状态转换存在于产生最终结果的任务( Reduce Task或者map-only类型作业 [1]的MapTask)中, 当任务处理完最后一条记录后进入COMMIT_PENDING状态, 以等待JobTracker批准其提交最后结果。
❑RUNNING→SUCCEEDED:该状态转换只存在于Map Task( 且这些Map Task的结果将被Reduce Task进一步处理) 中, 当Map Task处理完最后一条记录后便意味着任务运行成功。
❑RUNNING/COMMIT_PENDING→KILLED_UNCLEAN: TaskTracker收到来自JobTracker的KillTaskAction命令后, 会将对应任务由RUNNING/COMMIT_PENDING状态转化为KILLED_UNCLEAN状态, 通常产生的场景是人为杀死任务, 同一个TIP的多个同时运行的Task Attempt中有一个成功运行完成而杀死其他Task Attempt, TaskTracker因超时导致其上所有任务状态变为KILLED_UNCLEAN等。
❑RUNNING/COMMIT_PENDING→FAILED_UNCLEAN: 多种情况下会导致该状态转移, 包括本地文件读写错误、 Shuffle阶段错误、 任务在一定时间内未汇报进度( 从而被TaskTracker杀掉) 、内存使用量超过期望值或者其他运行过程中出现的错误。
❑UNASSIGNED→FAILED/KILLED:人为杀死任务。
❑KILLED_UNCLEAN/FAILED_UNCLEAN→FAILED/KILLED:一旦任务进入KILLED_UNCLEAN/FAILED_UNCLEAN状态,接下来必然进入FAILED/KILLED状态, 以清理已经写入HDFS上的部分结果。
❑SUCCEEDED→KILLED:一个TIP已有一个Task Attempt运行完成, 而备份任务也汇报成功, 则备份任务将被杀掉或者用户人、杀死某个Task, 而TaskTracker刚好汇报对应Task执行成功。
❑SUCCEEDED/COMMIT_PENDING→FAILED:ReduceTask从Map Task端远程读取数据时, 发现数据损坏或者丢失, 则将对应Map Task状态标注为FAILED以便重新得到调度。

 

6.7 容错机制

(1)JobTracker容错关注如何保存和恢复作业的运行时信息,有三种方案选择:

      作业级别:以作业为单位进行恢复,只关注两种作业状态:运行完成或者未运行完成,当JobTracker重启后,凡是未运行完成的作业会自动提交给Hadoop运行

任务级别;以任务为单位进行恢复,基于日志把作业和任务状态记录下来,当JobTracker重启后,可从日志中恢复作业的运行状态,未开始运行或者运行中的任务需要重新运行

记录级别:从失败作业的第一条尚未处理的记录开始恢复一个任务

(2)TaskTracker由三种容错方案选择:

超时机制:

❑TaskTracker第一次汇报心跳后, JobTracker会将其放入过期队列trackerExpiryQueue中, 并将其加入网络拓扑结构中。
❑TaskTracker以后每次汇报心跳, JobTracker均会记录最近心跳时间(TaskTrackerStatus.lastSeen)
❑线程expireTrackersThread周期性地扫描过期队列trackerExpiryQueue, 如果发现某个TaskTracker在10分钟( 可通过参数
mapred.tasktracker.expiry.interval配置) 内未汇报心跳, 则将其从集群中移除。

移除TaskTracker之前, JobTracker会将该TaskTracker上所有满足以下两个条件的任务杀掉, 并将它们重新加入任务等待队列中, 以便被调度到其他健康节点上重新运行。
条件1 任务所属作业处于运行或者等待状态。
条件2 未运行完成的Task(包括Map Task和Reduce Task) 或者Reduce Task数目不为零的作业中已运行完成的Map Task。

灰名单和黑名单:
黑名单:TaskTracker黑名单生成的方法是, 作业在运行过程中记录每个TaskTracker使其失败的Task Attempt数目, 一旦该数目超过mapred.max.tracker.failures( 默认是4), 对应的TaskTracker会被加入该作业的黑名单中

灰名单:JobTracker将记录每个TaskTracker被作业加入黑名单的次数#blacklist。当某个TaskTracker同时满足以下条件时, 将被加入JobTracker的灰名单中:

条件1 #blacklist大小超过mapred.max.tracker.blacklists值( 默认为4)。

条件2 该TaskTracker的#blacklists大小超过所有TaskTracker的#blacklist平均值的mapred.cluster.average.blacklist.threshold( 默认是50%)倍。
条件3 当前灰名单中TaskTracker的数目小于所有TaskTracker数目的50%。

Exclude listIncludelist

Exclude list:列表内的节点禁止与JobTracker连接

Include list:允许与JobTracker连接

(3)      Job容错:

mapred.max.map.failures.percent和mapred.max.reduce.failures.percent设定允许失败的Map和Reduce任务数占总任务数的比例,当超过该比例,则表示该Job失败

(4)      Task容错机制

mapred.map.max.attempts和mapred.reduce.max.attempts设定允许最大尝试运行次数,若超过该值则TaskInProgress标注该任务为FAILED,未运行成功的Task Attempt包括:killed task和failed task

(5)      Record容错:它会记录前几次任务尝试中导致任务失败的Record, 并在下次运行时自动跳过这些坏记录

(6)      磁盘容错:

TaskTracker允许用户设定mapred. local.dir.minspacestart(TaskTracker需要保证的最小可用磁盘空间)和mapred.local.dir.minspacekill(TaskTracker会周期性检查所在节点的剩余磁盘空间, 一旦低于该阈值, 便会按照一定策略杀掉正在运行的任务以释放磁盘空间)

JobTracker

当Map/Reduce输出的数据量大于当前节点磁盘剩余容量,则JobTracker不会把该任务分配给此TaskTracker

 

6.8 任务推测执行原理

推测执行机制为了解决慢任务问题,在新节点上运行备份任务,最先完成任务的计算结果为最终结果,mapred.map.tasks.speculative.execution和mapred.reduce.tasks.speculative.execution分别启动Map推测执行和Reduce推测执行

 

配置选项:

Ø  mapreduce.job.speculative.slownodethreshold

这个参数用于定义该作业在任意一个TaskTracker上已运行完成任务的平均进度增长率( 一个任务在单位时间内运行进度的增长量) 与所有已运行完成任务的平均进度增长率的最大允许差距( 标准方差的倍数) 。如果超过该阈值, 则认为对于该作业而言, 该TaskTracker性能过低, 不会在其上启动一个备份任务

Ø  mapreduce. job.speculative.slowtaskthreshold

这个参数用于定义该作业的任意一个任务平均进度增长率与所有正在运行任务的平均进度增长率的最大允许差距( 标准方差的倍数) 。当超过该阈值时, 则认为该任务运行过慢, 需为之启动一个备份任务。

Ø  mapreduce.job.speculative.speculativecap

这个参数用于限定该作业允许启动备份任务的任务数目占正在运行任务的百分比。 其默认值为0.1,表示可为一个作业启动推测执行功能的任务数不能超过正在运行任务的10%

 

estimatedEndTime=estimatedRunTime+taskStartTime
estimatedRunTime=( currentTimestamp-taskStartTime) /progress
estimatedEndTime'=currentTimestamp+averageRunTime

根据estimatedEndTime- estimatedEndTime'的差值最大的任务作为启动备份任务

 

6.9 Hadoop资源管理

Hadoop资源管理分为资源表示模型和资源分配模型,

资源表示模型为slot,分为Map Slot和Reduce Slot,通过mapred.tasktracker.map.tasks.maximum

和mapred.tasktracker.map.tasks.maximum指定节点Map Slot和Reduce Slot的最大数

资源分配模型决定把资源分配给各个作业/任务,由调度器完成

(1)     任务调度框架分析:

abstract class TaskScheduler implements Configurable{
protected TaskTrackerManager taskTrackerManager; //实际就是JobTracker
public synchronized void setTaskTrackerManager(
TaskTrackerManager taskTrackerManager) {
this.taskTrackerManager=taskTrackerManager;
} /
/初始化函数
public void start()throws IOException{
//do nothing
} /
/结束函数
public void terminate()throws IOException{
//do nothing
} /
/为TaskTracker分配新任务
public abstract List<Task>assignTasks( TaskTracker taskTracker)
throws IOException
}

TaskScheduler调度器的两大作用:作业初始化,任务调度

  JobInProgress.initJob( JobInProgress):作业初始化得到若干Task

  assignTasks(TaskTrackertaskTracker):为taskTracker分配Task,采用三级调度策略

不同调度器队列选择策略和作业选择策略不同,但任务选择策略相同,即均考虑数据本地性(尽量把任务调度到存放数据的节点),失败任务,备份任务的调度顺序

Ø 数据本地性

为了实现数据本地性,Hadoop网络拓扑结构一般采用三层架构数据中心->机架->物理节点,当无法满足数据本地性时,调度器尽量选择距离数据最近的节点分配其任务,因此Hadoop进行任务选择时采用node-local(数据资源和计算资源在同一个节点)、 rack-local(同机架)和off-switch(跨机架)。

Ø Map Task选择策略

步骤1 合法性检查。 如果一个作业在某个节点上失败任务数目超过一定阈值或者该节点剩余磁盘容量不足, 则不再将该作业的任何任务分配给该节点。
步骤2 从failedMaps列表中选择任务。 failedMaps保存了按照Task Attempt失败次数排序的TIP集合。 失败次数越多的任务, 被调度的机会越大。 需要注意的是, 为
了让失败的任务快速得到重新运行的机会, 在进行任务选择时不再考虑数据本地性。
步骤3 从nonRunningMapCache列表中选择任务。 采用的任务选择方法完全遵循数据本地性策略, 即任务选择优先级从高到低依次为node-local, rack-local和offswitch类型的任务。
步骤4 从nonLocalMaps列表中选择任务。 由于nonLocalMaps中的任务没有输入数据, 因此无须考虑数据本地性。
步骤5 从runningMapCache列表中选择任务。 遍历runningMapCache列表, 查找是否存在正运行且“拖后腿”的任务, 如果有, 则为其启动一个备份任务。
步骤6 从nonLocalRunningMaps列表中选择任务。 同步骤5,从nonLocalRunningMaps列表中查找“拖后腿”任务, 并为其启动备份任务。

Ø  Reduce Task选择策略

步骤1 合法性检查。 同Map Task一样, 对节点可靠性和磁盘空间进行检查。
步骤2 从nonRunningReduces列表中选择任务。 无须考虑数据本地性, 只需依次遍历该列表中的任务, 选出第一个满足条件( 未曾在对应节点上失败过)
步骤3 从runningReduces列表中选择任务, 为“拖后腿”的任务启动备份任务。

 

6.10 Hadoop资源管理优化

Ø 基于动态slot的资源管理SlotsAdjuster

Ø  基于无类别slot的资源管理

Ø  基于真实资源需求量的资源管理方案
 

 

第七章 TaskTracker内部分析

7.1 TaskTracker概述

主要功能包括发送心跳信息以及执行JobTracker命令

7.2 TaskTracker启动过程分析

TaskTracker实现了四种接口包括MRConstants(常量),TaskUmbilicalProtocol(Task与TaskTracker之间的RPC协议),Runnable(以java线程方式启动TaskTracker),TaskTrackerMXBean(外界监控工具以获取TaskTracker运行时的信息)

TaskTracker启动线程过程:

Ø  参数初始化:

volatile boolean running=true; //TaskTracker是否正在运行
InterTrackerProtocol jobClient;//RPC Client,用于与JobTracker通信
short heartbeatResponseId=-1;//心跳响应ID
TaskTrackerStatus status=null;//TaskTracker状态信息
Map<TaskAttemptID,TaskInProgress>tasks=new HashMap<TaskAttemptID,
TaskInProgress>(); //该节点上TaskAttemptID与TIP对应关系
Map<TaskAttemptID,TaskInProgress>runningTasks=null; /*该节点上正在运行的
TaskAttemptID与TIP对应关系*/
//该节点正运行的作业列表。 如果一个作业中的任务在节点上运行, 则把该作业加入该数据结构
Map<JobID, RunningJob>runningJobs=new TreeMap<JobID, RunningJob>();
boolean acceptNewTasks=true;//是否接受新任务, 每次汇报心跳时要将该值告诉JobTracker
private int maxMapSlots;//TaskTracker上配置的Map slot数目
private int maxReduceSlots;//TaskTracker上配置的Reduce slot数目
private volatile int heartbeatInterval=HEARTBEAT_INTERVAL_MIN; //心跳间隔

Ø  重要对象初始化

Ø  连接JobTracker

TaskTracker初次启动服务后,会向JobTracker发送第一个心跳信息,经过一系列安全检查后添加到Hadoop集群中。

 

7.3 心跳机制

Ø  TaskTracker判断是否到达心跳发送时间,由集群规模和任务运行情况共同决定

  集群规模:JobTracker可以根据TaskTracker的数量动态调整心跳间隔

  任务运行情况:TaskTracker一旦发现某个任务运行完成或者失败,立刻缩短心跳间隔,以便将任务信息尽快告知JobTracker,进而快速重新分配任务,这种称为带外心跳,由两个参数决定

Mapreduce.tasktracker.outofband.heartbeat:决定是否启动带外心跳

Mapreduce.tasktracker.outofband.heartbeat.damper:心跳收缩因子,当启用带外心跳机制时, 如果某时刻有X个任务运行完成, 则心跳间隔变为heartbeatInterval/( X*oobHeartbeatDamper+1)

Ø  如果TaskTracker刚启动,则需要检查代码编译版本与JobTracker是否一致

Ø  检查是否有磁盘损坏

TaskTracker会周期性的检查mapred.local.dir指定的磁盘目录列表

Ø  发送心跳

Ø  执行接收命令

 

状态发送:基于TaskInternelProtocol.heartbeat(TaskTrackerStatusstatus)

askForNewTask=(( status.countOccupiedMapSlots()<maxMapSlots||status.countOccupiedReduceSlots()<maxReduceSlots) &&acceptNewTasks); //存在空闲的Map slot或者Reduce slot, 且磁盘剩余空间大于mapred.local.dir.minspacekill

只有当askForNewTask时才会发送节点资源使用信息

TaskTrackerStatus包含的信息:

String trackerName;//TaskTracker名称
String host; //TaskTracker主机名
int httpPort; //TaskTracker对外的HTTP端口号
int failures; //TaskTracker上已经失败任务总数
List<TaskStatus>taskReports; //当前TaskTracker上各个任务运行状态
volatile long lastSeen;//上次汇报心跳的时间
private int maxMapTasks;//Map slot总数, 即允许同时运行的Map Task总数, 由参数mapred.
tasktracker.map.tasks.maximum设定
private int maxReduceTasks;//Reduce slot总数, 即允许同时运行的Reduce Task总数, 由参
数mapred.tasktracker.reduce.tasks.maximum设定
private TaskTrackerHealthStatus healthStatus; //TaskTracker健康状态
private ResourceStatus resStatus;//TaskTracker资源( 内存, CPU等) 信息

 

其中List<TaskStatus>taskReports保存当前TaskTracker上所有任务的运行状态

public abstract class TaskStatus implements Writable, Cloneable{
……
private final TaskAttemptID taskid;//Task Attempt ID
private float progress;//任务执行进度, 范围为0.0~1.0
private volatile State runState;/*任务运行状态, 包括RUNNING, SUCCEEDED,FAILED,
UNASSIGNED, KILLED, COMMIT_PENDING, FAILED_UNCLEAN, KILLED_UNCLEAN*/
private String diagnosticInfo;//诊断信息, 一般为错误信息和运行异常信息
private String stateString;//字符串形式的运行状态
private String taskTracker;//该TaskTracker名称, 可唯一表示一个TaskTracker, 形式如
tracker_mymachine: 50010
private int numSlots;//运行该Task Attempt需要的slot数目, 默认值是1
private long startTime;//Task Attempt开始时间
private long finishTime;//Task Attempt完成时间
private long outputSize=-1L;//Task Attempt输出数据量
private volatile Phase phase=Phase.STARTING; //任务运行阶段, 包括STARTING, MAP,
SHUFFLE, SORT, REDUCE, CLEANUP
private Counters counters;//该任务中定义的所有计数器( 包括系统自带计数器和用户自定义计数
器两种)
private boolean includeCounters;//是否包含计数器, 计数器信息每隔60 s发送一次
//下一个要处理的数据区间, 用于定位坏记录所在区间
private SortedRanges.Range nextRecordRange=new SortedRanges.Range();
……
}

 

healthStatus保存当前节点的健康状况,对应于TaskTrackerHealthStatus

static class TaskTrackerHealthStatus implements Writable{
private boolean isNodeHealthy;//节点是否健康
private String healthReport;//如果节点不健康, 则记录导致不健康的原因
private long lastReported;//上次汇报健康状况的时间}

healthStatus由NodeHealthCheckerService线程计算得到,一旦认为节点不健康,则标记unhealthy并通过心跳信息告知JobTracker,JobTracker将此TaskTracker拉黑,不再为其分配任务

 

resStatus保存了当前TaskTracker的资源使用情况

static class ResourceStatus implements Writable{
private long totalVirtualMemory;//总的可用虚拟内存量, 单位为byte
private long totalPhysicalMemory;//总的可用物理内存量
private long mapSlotMemorySizeOnTT;//每个Map slot对应的内存量
private long reduceSlotMemorySizeOnTT;//每个Reduce slot对应的内存量
private long availableSpace;//可用磁盘空间
private long availableVirtualMemory=UNAVAILABLE; //可用的虚拟内存量
private long availablePhysicalMemory=UNAVAILABLE; //可用的物理内存量
private int numProcessors=UNAVAILABLE;//节点总的处理器个数
private long cumulativeCpuTime=UNAVAILABLE; //运行以来累计的CPU使用时间
private long cpuFrequency=UNAVAILABLE;//CPU主频, 单位为kHz
private float cpuUsage=UNAVAILABLE;//CPU使用率, 单位为%
}

 

命令执行(HeartbeatResponse)

主要包括两个部分,作业集合recoveredJobs(由于关闭了JobTracker正在运行的作业集合,重启JobTracker后需要恢复这些作业的运行状态,另外是需要执行的命令列表,总共五种命令,重新初始化,杀死作业,杀死任务,启动新任务,提交任务

 

7.4TaskTracker行为分析

(1)启动新任务


1) TaskTracker出现空闲资源, 将资源使用情况通过心跳汇报给JobTracker;
2) JobTracker收到信息后, 解析心跳信息, 发现TaskTracker上有空闲的资源, 则调用调度器模块的assignTasks()函数为该TaskTracker分配任务;
3) JobTracker将新分配的任务封装成一个或多个LaunchTaskAction对象, 将其添加到心跳应答中返回给TaskTracker;
4) TaskTracker收到心跳后, 解析出LaunchTaskAction对象, 并创建JVM启动任务;
5) 当Counter值或者进度发生变化时, 任务通过RPC函数将最新Counter值和进度汇报给TaskTracker;
6) TaskTracker通过心跳将任务的最新Counter值和进度汇报给JobTracker。

 

(2)提交任务

MapReduce采用两段提交协议,第一阶段( 准备阶段) :各个参与者执行完自己的操作后, 将状态变为“可以提交”,并向协调者发送“准备提交”请求。第二阶段( 提交阶段) : 协调者按照一定原则决定是否允许参与者提交, 如果允许, 则向参与者发出“确认提交”请求, 参与者收到请求后, 把“可以提交”状态改为“提交完成”状态, 然后返回应答;

1) Task Attempt处理完最后一条记录后( 此时数据写在临时目录${mapred.output.dir}/_temporary/_${taskid}下) ,运行状态由RUNNING变为COMMIT_PENDING,并通过RPC将该状态和任务提交请求发送给TaskTracker;
2) TaskTracker得知一个Task Attempt状态为COMMIT_PENDING后, 立刻缩短心跳间隔, 以便快速将任务状态汇报给JobTracker;
3) JobTracker收到心跳信息后, 检查它是否为某个TaskInProgress中第一个状态变为COMMIT_PENDING的Task Attempt,如果是, 则批准它进行任务提交, 即在心
跳应答中添加CommitTaskAction命令, 以通知TaskTracker准许该任务提交最终结果;
4) TaskTracker收到CommitTaskAction命令后, 将对应任务加入可提交任务列表commitResponses中;
5) Task Attempt通过RPC检测到自己位于列表commitResponses中后, 进行结果提交, 即将数据从临时目录转移到最终目录( 参数${mapred.output.dir}对应的目录)
中, 并告诉TaskTracker任务提交完成;
6) TaskTracker得知任务提交完成后, 将该任务运行状态由COMMIT_PENDING变为SUCCEEDED, 并在下次心跳中将该任务状态汇报给JobTracker。
综合以上几个步骤, 并结合2PC的定义, 很容易知道: 上面第1步对应2PC的第一阶段, 而其余各步骤对应2PC的第二阶段

 

(2)      杀死任务

1)   JobTracker收到来自JobClient的杀死任务请求后, 将该任务添加到待杀死任务列表tasksToKill中。
2) 之后某个时刻, Task Attempt所在的TaskTracker向JobTracker发送心跳, JobTracker收到心跳后, 将该任务封装到KillTaskAction命令中, 并通过心跳应答发送给
TaskTracker。
3) TaskTracker收到KillTaskAction命令后, 将该任务从作业列表runningJobs中清除, 并将运行状态从RUNNING转化为KILLED_UNCLEAN, 同时通知directoryCleanupThread线程清理其工作目录, 释放所占槽位, 最后缩短心跳时间间隔, 以便将该任务状态迅速通过带外心跳告诉JobTracker。
4) JobTracker收到状态为KILLED_UNCLEAN的Task Attempt后, 将其类型改为task-cleanup task( 这种任务的输入数据为空, 但ID与被杀Task Attempt的ID相同, 其
目的是清理被杀Task Attempt已经写入HDFS的临时数据) , 并添加到待清理任务队列mapCleanupTasks/reduceCleanupTasks中, 在接下来的某个TaskTracker的心跳
中, JobTracker将其封装到LaunchTaskAction中发送给TaskTracker。
5) TaskTracker收到LaunchTaskAction后, 启动JVM(或重用已启动的JVM) 执行该任务。 由于该任务属于task-cleanup task, 因此它只需清理被杀死的Task Attempt
已写入HDFS的临时数据( 如果没有则直接跳过) ,之后其运行状态变为SUCCEEDED, 并由TaskTracker通过下一次心跳告诉JobTracker。
6) JobTracker收到运行状态为SUCCEEDED的Task Attempt后, 首先检查它是否位于任务列表tasksToKill中,显然该任务在该列表中, 这表明它已经被杀死, 于是
将其状态转化为KILLED, 同时修改相应的各个数据结构。

 

7.5 作业目录管理

(1)数据目录:保存作业运行的数据和产生的临时数据,由mapred.local.dir指定

(3)      日志目录:保存TaskTracker和Task的运行日志,由hadoop.log.dir指定,分为系统日志和用户日志,其中系统日志是TaskTracker服务的运行日志,用户日志则放在userlog

mapreduce.cluster.map.userlog.retain-size和mapreduce.cluster.reduce.userlog.retain-size, 分别用于配置Map Task和Reduce Task最大可保留的日志文件大小

TaskTracker允许一个作业日志可在磁盘上的保留时间为mapred.userlog.retain.hours

7.6 启动新任务

主要包括:作业本地化和启动任务

Ø  作业本地化:创建作业和任务的工作目录,并从HDFS上下载运行所需要的文件,TaskTracker采用多线程提升性能,即一个任务一个线程处理,需要加同步锁

Ø  启动任务:TaskRunner. run()方法首先准备启动任务需要的各种信息, 包括启动命令、 启动参数、 环境变量、 标准输出流、 标准错误输出流等信息, 然后交给JvmManager对象启动一个JVM(jvmRunner)

 

7.6.2 资源隔离

  1.MapReduce作业的配置参数

(1)用户配置参数:mapred. job.{map|reduce}.memory.mb: 作业的Map Task或Reduce Task最多使用的内存量( 单位: MB)。

(2)管理员配置参数:

❑mapred.cluster.max.{map|reduce}.memory.mb:用户可设置的Map Task或Reduce Task最多使用内存量的上限值( 单位: MB)。
❑mapred.job.{map|reduce}.memory.mb: TaskTracker上每个Map slot或Reduce slot代表的内存量( 单位: MB) 。注意, 有些调度器会根据该参数值和任务内存需求
为任务分配多个slot, 比如Capacity Scheduler。
2.MapReduce作业内存监控

askMemoryManagerThread线程每隔一段时间( 由参数mapred.tasktracker.taskmemorymanager.monitoring-interval指定, 默认是5 s)扫描所有正在运行的任务, 并按照以下步骤检查它们使用的内存量是否超过上限值。

步骤1 构造进程树。

步骤2 判断单个任务内存使用量是否超过内存量最大值。

步骤3 判断任务总内存使用量是否超过总可用内存量

第8章 Task内部分析

Map Task将中间结果写入磁盘,Reduce Task则通过HTTP请求从各个Map Task端拖取(pull)相应的输入数据,其中HTTP Server为Jetty Server

 

8.1 基本数据结构与算法

Hadoop采用了分布式排序策略: 先由各个Map Task对输出数据进行一次局部排序, 然后由Reduce Task进行一次全局排序。

(1)      IFile支持压缩的存储格式:

❑mapred. compress.map.output: 是否支持中间输出结果压缩, 默认为false。
❑mapred.map.output.compression.codec:压缩器( 默认是基于Zlib算法的压缩器DefaultCodec)。 任何一个压缩器需实现CompressionCodec接口以提供压缩输出流和解压缩输入流

IFile的数据格式(每行):

<key-len, value-len,key, value>

IFile.Reader从Map端读数据至Reduce端磁盘,IFile.InMemoryReader从Map端读数据至Reduce端内存

Map端缓冲区数据排序使用Hadoop自己的快速排序算法,而IFile文件合并则使用基于堆实现的优先队列

Map端快速排序算法:

(1)      枢轴选择:将序列首尾和中间元素中的中位数作为枢轴

(2)      子序列划分:和普通快排一样,选择i==j处作为子序列划分的临界点q

(3)      对相同元素优化:即将序列划分成三部分: 小于枢轴、 等于枢轴和大于枢
轴,等于枢轴的区间不参与递归

(4)      减少递归次数

当子序列元素的数目小于13时,直接采用插入排序算法

IFile文件合并(基于堆的优先级队列)

基于Merge类实现,采用多轮递归合并,每轮选择最小的前io.sort.factor个文件进行合并,并将产生的文件加入待合并列表,直到剩下的文件数量小于io.sort.factor,假设io.sort.factor=3

(2)      Reporter

Map/ReduceTask中使用TaskReporter汇报任务执行进度和任务计数器值

对于Map Task而言,我们直接将已读取数据量占总数据量的比例作为任务当前执行进度值。

对于Reduce Task而言,将其分成Shuffle,Sort和Reduce阶段,每个阶段占任务总进度的1/3,由于需要从M个Map Task上读取一片数据,因此可以分解成M个阶段,每个阶段占Shuffle进度的1/M

TaskReporter线程仅当出现下面两种情况时才会汇报:

❑任务执行进度发生变化;
❑任务的某个计数器值发生变化

为了防止某条记录因处理时间过长导致被杀,用户可采用:

❑每隔一段时间调用一次TaskReporter.progress()函数, 以告诉TaskTracker自己仍然活着。
❑增大任务超时参数mapred.task.timeout( 默认是10 min)对应的值。

 

任务计数器(counter),用于实现跟踪任务运行进度的全局计数功能,由两部分组成<name,value>,其中name为计数器名称,value为计数器值,计数器以组为单位管理,可通过mapreduce.job.counters.limit设置作业的计数器个数

MapReduce内置计数器包括FileInputFormatCounter(读入计数),FileOutputFormatCounter(写入计数)以及MapReduceFramework(跟踪任务执行进度)

 

8.2 Map Task内部实现

Map Task分为4种,分别为Job-setup task(运行启动作业的第一个任务),Job-cleanup task(运行启动作业的最后一个任务),Task-cleanup task(任务失败或者被杀死后用于清理已写入临时目录中数据的任务),Map task

Map Task一般分为5个阶段:

Ø  Read:从输入的InputSplit中通过RecordReader读取一个个key-value

Ø  Map:执行map(),得到一个个新的key-value

Ø  Collect:OutputCollector.collect()输出结果,它会将生成的key-value,通过调用partitioner写入环形内存缓冲区,即以<key,value,partition>格式写入MapOutputBuffer中

Ø  Split:溢写,当环形内存缓冲区满后,MapReduce会将数据写入本地磁盘,生成     IFile文件,写入文件前会进行Sort,此过程通过splitThread完成

SpillThread线程调用函数sortAndSpill()将环形缓冲区kvbuffer中区间[bufstart, bufend) 内的数据写到磁盘上。 函数sortAndSpill()内部工作流程如下:

  利用快速排序算法对缓冲区kvbuffer中区间[bufstart, bufend) 内的数据进行排序, 排序方式是, 先按照分区编号partition进行排序, 然后按照key进行排序

  按照分区编号由小到大依次将每个分区中的数据写入任务工作目录下的临时文件output/spillN.out,如果用户设置了Combiner,则写入文件之前, 对每个分区中的数据进行一次聚集操作

  将分区数据的元信息写到内存索引数据结构SpillRecord中, 其中每个分区的元信息包括在临时文件中的偏移量、 压缩前数据大小和压缩后数据大小

Ø  Combine:Map Task会对所有的临时文件IFile进行一个合并,以确保最终只生成一个数据文件

8.3 Reduce Task的内部实现

❑Shuffle阶段: 也称为Copy阶段。 Reduce Task从各个Map Task上远程拷贝一片数据, 并针对某一片数据, 如果其大小超过一定阈值, 则写到磁盘上, 否则直接放内存中。
❑Merge阶段: 在远程拷贝数据的同时, Reduce Task启动了两个后台线程对内存和磁盘上的文件进行合并, 以防止内存使用过多或磁盘上文件过多。

Shuffle和Merge阶段都是基于ReduceCopier实现,可进一步划分为3个子阶段:

  准备运行完成的Map Task列表:

GetMapEventsThread线程周期性通过RPC从TaskTracker获取已完成Map Task列表, 并保存到映射表mapLocations。 为防止出现网络热点, Reduce Task通过对所有TaskTracker Host进行“混洗”操作以打乱数据拷贝顺序, 并将调整后的Map Task输出数据位置保存到
scheduledCopies列表中。

  远程拷贝数据:

Reduce Task同时启动多个MapOutputCopier线程, 这些线程从scheduledCopies列表中获取Map Task输出位置, 并通过HTTP Get远程拷贝数据。 对于获取的数据分片, 如果大小超过一定阈值, 则存放到磁盘上, 否则直接放到内存中。

  合并内存文件和磁盘文件:

为了防止内存或者磁盘上的文件数据过多, Reduce Task启动了LocalFSMerger和InMemFSMergeThread两个线程分别对内存和磁盘上的文件进行合并


❑Sort阶段: 按照MapReduce语义, 用户编写的reduce()函数输入数据是按key进行聚集的一组数据。 为了将key相同的数据聚在一起, Hadoop采用了基于排序的策
。 由于各个Map Task已经实现对自己的处理结果进行了局部排序, 因此, Reduce Task只需对所有数据进行一次归并排序即可

由于在Map阶段已经对数据进行了局部排序,因此在Reduce过程的Sort阶段只需要进行一次归并排序即可保证整体数据有序,即相同key的数据聚集在一次作为Reduce阶段输入,在Sort阶段,Reduce Task对内存和磁盘中的文件建立小顶堆,保存了指向该小顶堆根节点的迭代器
❑Reduce阶段: 在该阶段中, Reduce Task将每组数据依次交给用户编写的reduce()函数处理。

❑Write阶段: reduce()函数将计算结果写到HDFS上。

 

8.4 Map/Reduce Task优化

(1)参数调优:

(3)      系统优化:

Ø  避免排序:

  在Map Collect阶段,只需比较partition,可采用更快的计数排序

  在Map Combine阶段,只需按照字节合并数据块即可

  去掉排序后,shuffle和reduce可同时进行

Ø  Shuffle内部优化

  Map端使用netty代替jetty

  Reduce端采用批拷贝

  将shuffle阶段从Reduce Task中分离,可采用Copy-Compute Splitting或者将Shuffle变为独立服务

 

第九章 Hadoop性能调优

9.1 管理员角度进行调优

管理员需从硬件选择、 操作系统参数调优、 JVM参数调优和Hadoop参数调优等四个方面入手, 为Hadoop用户提供一个高效的作业运行环境

Ø  硬件选择

Master节点性能要优于Slave节点

Ø  操作系统参数调优

  增大同时打开的文件描述符和网络连接上限

管理员在启动Hadoop集群时, 应使用ulimit命令将允许同时打开的文件描述符数目上限增大至一个合适的值, 同时调整内核参数net.core.somaxconn至一个足够大的值

  关闭swap分区

调整/etc/sysctl.conf文件中的vm.swappiness参数

  设置合理的预读取缓冲区大小

管理员可使用Linux命令blockdev设置预读取缓冲区的大小

  文件系统选择和配置(ext3/ext4)

  I/O调度器选择

Ø  JVM参数调优

管理员可通过调整JVM FLAGS和JVM垃圾回收机制提高Hadoop性能

Ø  Hadoop参数调优

  合理规划资源

1)     设置合理的槽位数目

Map Slot和Reduce Slot,每种Slot封装了一定量的资源,槽位数目是在各个TaskTracker上的mapred-site.xml中配置的

2)   编写节点健康监测脚本

  调整心跳配置

  启用带外心跳

  磁盘块设置

  设置合理的RPC Handler和HTTP线程数目

  慎用黑名单机制

  启用批量任务调度,即一次将所有空闲任务分配下去, 而不是一次只分配一个

  选择合适的压缩算法,, LZO和Snappy在压缩比和压缩效率两方面的表现都比较优秀,让Hadoop压缩Map Task中间输出数据结果( 在mapred-site.xml中配置)

<property>
<name>mapred.compress.map.output</name>
<value>true</value>
</property>
<property>
<name>mapred.map.output.compression.codec</name>
<value>org.apache.hadoop.io.compress.SnappyCodec</value>
</property>

  启用预读取机制

 

9.2 用户角度进行调优

Ø  应用程序编写规范

  设置Combiner,可减少Map Task输出结果

  选择合理的Writable

Ø  作业级别参数调优

  规划合理的任务数目

用户可根据估算的Map Task数目,设置Reduce Task数目

  增加输入文件副本数

<property>
<name>dfs.replication</name>
<value>5</value>
</property>

  启用推测执行机制

  设置失败容忍度(作业级别和任务级别)

  适当打开JVM重用功能,这样一个JVM可连续启动多个同类型任务

  设置任务超时时间

  合理使用DistributedCache

  合理控制Reduce Task的启动时机

  跳过坏记录

  提高作业优先级

Ø  任务级别参数调优

  Map Task调优

设置环形缓冲区大小(io.sort.mb),设置io.sort.split.percent(溢写比例),设置io.sort.record.percent(减少中间文件数量)

  Reduce Task调优

设置mapred.reduce.parallel.copies(拷贝线程数),io.sort.factor(合并因子),mapred. child.java.opts(子JVM选项),mapred. job.shuffle.input.buffer.percent(内存使用率),mapred.inmem.merge.threshold(内存中的合并阈值)

 

第十章 Hadoop多用户作业调度器

10.1 Hadoop多用户作业调度器分类

Ø  HOD(Hadoop on Demand)调度器:在一个物理集群上虚拟多个Hadoop集群,这些集群各自拥有独立的Hadoop服务

Ø  扩展Hadoop调度器:支持多用户多队列包括Capacity Schedule和Fair Schedule

 

  Hadoop作业分类:

Ø  批处理作业:作业耗时较长,对完成时间没有严格要求,如数据挖掘,机器学习

Ø  交互式作业:作业期望能及时返回结果

Ø  生产性作业:作业要求一定量的资源保证

10.2 HOD调度器

HOD调度器首先使用Torque资源管理器 [2]为一个虚拟Hadoop集群分配节点, 然后在分配的节点上启动MapReduce和HDFS中的各个守护进程, 并自动为Hadoop守护进程和客户端生成合适的配置文件( 包括mapred-site.xml, core-site.xml和hdfs-site.xml等)

10.2.1 Torque资源管理器

一个Torque集群由一个头节点和若干个计算节点组成,头节点运行pbs_server的守护进程,主要用于管理计算节点和监控各个作业的运行状态,每个计算节点上运行一个pbs_mom的守护进程,用于执行主节点分配的作业,除此之外头节点还运行一个调度器守护进程,会和pbs_server进行通信,以决定对资源使用和作业分配的本地策略(默认FIFO)

 

步骤1 当pbs_server收到新作业后, 会进一步通知调度器。
步骤2 调度器采用一定的策略为该作业分配节点, 并将节点列表与节点对应的作业执行命令返回给pbs_server。
步骤3 pbs_server将作业发送给第一个节点。
步骤4 第一个节点启动作业, 作业开始运行( 该作业会通知其他节点执行相应命令) 。
步骤5 作业运行完成或者资源申请到期后, Torque会回收资源。

10.3 Hadoop队列管理机制

主要包括用户权限管理和系统资源管理
1.用户权限管理:

管理员可以为队列设置用户和用户组,也可以配置每个队列的管理员,所有队列的配置文件在mapred-site.xml(静态)

<property>
<name>mapred.queue.names</name>
<value>queueA, queueB, queueC, default</value>
<description>Hadoop中所有队列名称</description>
</property>
<property>
<name>mapred.acls.enabled</name>
<value>true</value>
<description>是否启用权限管理功能</description>
</property>

队列权限的配置选项在mapred-queue-acls.xml(动态加载)

<configuration>
<property>
<name>mapred.queue.queueA.acl-submit-job</name>
<value>linux_userA linux_groupA</value>
</property>
<property>
<name>mapred.queue.queueA.acl-administer-jobs</name>
<value>linux_groupA_admin</value>
</property>
<! --配置其他队列.-->
</configuration>

 

2.系统资源管理

由调度器完成

 

10.4 Capacity Schedule实现

Capacity Schedule是一个多用户调度器,有多层级别的资源限制条件(队列资源限制,用户资源限制,用户作业数目限制)

 

<configuration>
<property>
<name>mapred.capacity-scheduler.maximum-system-jobs</name>
<value>3000</value>
<description>系统中最多可被初始化的作业数目</description>
</property>
<property>
<name>mapred.capacity-scheduler.maximum-system-jobs</name>
<value>3000</value>
<description>Hadoop集群中最多同时被初始化的作业</description>
</property>
<property>
<name>mapred.capacity-scheduler.queue.myQueue.capacity</name>
<value>30</value>
<description>default队列的可用资源( 百分比) </description>
</property>
<! --配置myQueue队列.-->
<property>
<name>mapred.capacity-scheduler.queue.myQueue.maximum-capacity</name>
<value>40</value>
<description>default队列的资源使用上限( 百分比) </description>
</property>
<property>
<name>mapred.capacity-scheduler.queue.myQueue.supports-priority</name>
<value>false</value>
<description>是否考虑作业优先级</description>
</property>
<property>
<name>mapred.capacity-scheduler.queue.myQueue.minimum-user-limit-percent</name>
<value>100</value>
<description>每个用户最低资源保障( 百分比) </description>
</property>
<property>
<name>mapred.capacity-scheduler.queue.myQueue.user-limit-factor</name>
<value>1</value>
<description>每个用户最多可使用的资源占队列总资源的比例</description>
</property>
<property>
<name>mapred.capacity-scheduler.queue.myQueue.maximum-initialized-active-tasks</name>
<value>200000</value>
<description>default队列可同时被初始化的任务数目</description>
</property>
<property>
<name>mapred.capacity-scheduler.queue.myQueue.maximum-initialized-active-tasks-peruser</name>
<value>100000</value>
<description>default队列中每个用户可同时被初始化的任务数目</description>
</property>
<property>
<name>mapred.capacity-scheduler.queue.myQueue.init-accept-jobs-factor</name>
<value>10</value>
<description>default队列中可同时被初始化的作业数目, 即该值与( maximum-system-jobs*
queue-capacity)的乘积</description>
</property>
<! --配置myQueue队列.-->
</configuration>

 

1.     Capacity Schedule实现

org.apache.hadoop.mapred.CapacityTaskScheduler( 管理员需在参数mapred.jobtracker.taskScheduler中指定)而CapacityTaskScheduler启动时会加载自己的配置文件capacityscheduler.xml, 并向JobTracker注册监听器以随时获取作业变动信息。 待调度器启动完后, 用户可以提交作业。

Capacity Schedule主要功能是初始化作业和作业调度,Capacity Scheduler通过优先初始化那些最可能被调度器调度的作业和限制用户初始化作业数目来限制内存使用量

 

CapacityScheduler中作业初始化由线程JobInitializationPoller完成,每个线程负责一个或者多个队列的作业初始化,作业初始化流程:

步骤1:用户将作业提交到JobTracker端后, JobTracker会向所有注册的监听器广播该作业信息; Capacity Scheduler中的监听器JobQueuesManager收到新作业添加的
信息后, 检查是否能够满足以下三个约束, 如果不满足, 则提示作业初始化失败

步骤2:在每个队列中, 按照以下策略对未初始化的作业进行排序: 如果支持作业优先级( supports-priority为true), 则按照FIFO策略( 先按照作业优先级排序,再按照到达时间排序) 排序, 否则, 按照作业到达时间排序。 每个工作线程每隔一段时间( 可通过参数mapred.capacity-scheduler.init-poll-interval设定, 默认是3 000毫秒) 遍历其对应的作业队列, 并选出满足以下几个条件的作业

步骤3:调用JobTracker.initJob()函数对筛选出来的作业进行初始化。

 

Capacity Schedule的作业调度流程(CapacityTaskScheduler.assignTasks()):

步骤1:更新队列资源使用量

步骤2:选择Map Task,即依次选择一个队列、 该队列中的一个作业和该作业中的一个任务

选择队列:选择资源使用率最低的队列

选择作业:按照FIFO和作业到达时间,并检查当前TaskTracker的剩余资源是否满足当前作业的一个任务
Capacity‘Schedule调度过程用到了以下机制:

1)  大内存任务调度:允许根据任务内存量分配一个或者多个slot

2)  通过任务延迟调度提高数据本地性:如果作业未找到满足数据本地性的任务,则调度器会让该任务跳过一定数量的调度机会,直到找到一个满足本地性的任务或者达到跳过次数

3)  批量任务分配:’TaskTracker一次性最多分配的任务数·,更倾向于优先发送心跳的TaskTracker

步骤3:选择Reduce Task,仅采用了大内存任务调度策略

 

10.5 Fair Schedule功能

Fair Schedule的配置信息包含在mapred-site.xml和conf/fairschedule.xml,其基本思想是基于资源池的最小资源量和公平共享量进行任务调度,最小资源量由管理员配置,共享公平量则会根据队列或者作业权重计算得到,其资源分配的基本过程:

步骤1:根据管理员配置的最小资源量将所有系统中所有slot分配给各个资源池,如果某个资源池实际需要的资源量小于它的最小资源量,则按照实际需求量分配即可

步骤2:根据资源池的权重将剩余资源分配给各个资源池

步骤3:在各个资源池中,按照作业权重将资源分配给各个作业,最终每个作业可分到的资源量即为作业·的公平共享量,作业权重由作业优先级转换而来。

 

Fair Schedule实现原理:

❑配置文件加载模块: 由类PoolManager完成, 负责将配置文件fair-scheduler.xml中的信息加载到内存中。
❑作业监听模块: Fair Scheduler启动时会向JobTracker注册作业监听器JobListener, 以便能够随时获取作业变化信息。
❑状态更新模块: 由线程UpdateThread完成, 该线程每隔mapred.fairscheduler.update.interval( 默认是500毫秒) 时间更新一次队列和作业的信息, 以便将最新的信息
提供给调度模块进行任务调度。
❑调度模块: 当某个TaskTracker通过心跳请求任务时, 该模块根据最新的队列和作业的信息为该TaskTracker选择一个或多个任务。

 

Fair Schedule采用三级调度策略,资源池->作业->任务

1)选择资源池:当存在资源使用量小于最小资源量的资源池时,优先选择资源使用率最低的资源池,否则选择任务权重比最小的资源池
    2)选择作业:Fair Schedule优先将资源分配给资源池中任务权重比最小的作业(正在运行任务数/作业权重)

3)选择任务:延时调度机制

 

Fair Schedule优化机制包括延迟调度机制,负载均衡(LoadManager),资源抢占(最小资源量抢占和公平共享量抢占)

 

第12章下一代MapReduce框架

1.     Apache YARN架构

(1)      ResourceManager:资源管理和分配,主要由调度器(Schedule)和应用管理器(ApplicationManager)

调度器仅仅把资源分配给应用程序,资源分配的抽象概念是Container(容器)

应用管理器负责管理所有的应用程序,包括应用程序提交、与调度器协商资源以启动ApplicationMaster、监控ApplicationMaster运行状态并在失败时重启

(2)      ApplicationMaster==简化版JobTracker(MRAppMaster):主要负责与ResourceManager的Schedule协商以获取资源;与NodeManager通信以启动/停止任务;监控所有任务的运行状态,并在运行失败后重新为任务申请资源以重启任务

(3)      NodeManager:每个节点的任务管理和资源管理,主要负责定时向ResourceManager汇报资源使用情况和Container运行状态;接收并处理来自AM的任务启动/停止请求

(4)      Container:资源分配单元,每个任务对应一个Container

 

2.     YARN的RPC接口(C/S架构,pull-based)和序列化框架

Ø  ClientRMProtocol:Client提交应用程序,查询应用程序状态

Ø  RMAdminProtocol:Administrator更新配置文件

Ø  AMRMProtocol:AM向RM注册并撤销自己,申请资源

Ø  ContainerManager:AM要求NM启动或者停止Container,获取Container使用状态等

Ø  ResourceTracker:NM向RM注册,并定时发送心跳信息汇报当前节点的资源使用情况和Container运行情况

 

3.     Apache YARN工作流程:

步骤1:用户向YARN中提交应用程序,包括ApplicationMaster程序,启动ApplicationMaster命令,用户程序等等

步骤2:ResourceManager为该应用程序分配第一个Container,并与对应的NodeManager通信,要求它在Container中启动应用程序的ApplicaitonMaster

步骤3:ApplicationMaster首先向ResourceManager注册,用户可以直接通过ResourceManager查看应用程序的运行状态,然后为各个任务申请资源,并监控其运行状态,直到运行结束

步骤4:ApplicaitonMaster采用轮询方式,通过RPC接口向ResourceManager申请和领取资源

步骤5:一旦ApplicationMaster申请到资源,就与NodeManager通信,要求启动任务

步骤6:NodeManager为任务设置运行环境,将任务启动命令写到一个脚本,并通过运行此脚本启动任务

步骤7:各个任务通过RPC接口向ApplicationMaster汇报自己的状态和进度,从而让ApplicationMaster掌握各个任务的运行状态,从而可以在任务失败时重新启动任务

步骤8:应用程序运行完成后,ApplicationMaster向ResourceManager注销并关闭自己

 

4.     Apache YARN设计细节

(1)      基于服务的对象管理模型

  每个被服务化的对象分为4种状态:NOTINITED( 被创建) 、 INITED( 已初始化) 、 STARTED( 已启动) 、 STOPPED( 已停止)

  任何服务状态变化都可以触发另外一些动作

  通过组合的方式对任何服务进行组合,以便统一管理


ResourceManager是一个组合服务,组合了多个服务对象包括ClientRMService、ApplicaitonMasterLautcher等等,继承于CompositeService

(2)      基于事件驱动的并发模型

(3)      基于真实资源需求量的调度模型

ApplicationMaster发送的资源请求:

<Priority, Hostname,Resource, #Container>

ApplicaitonMaster收到的Container列表,每个Container包含

<ContainerId,NodeId, NodeHttpAddress, Resource, ContainerToken>

ApplicationMaster与NodeId节点通信利用ContainerToken进行安全验证,利用Resource启动Container,然后在该Coontainer中启动服务

 

5.     MapReduce与YARN结合

新的MapReduce功能全部集中在MRAppMaster

MRAppMaster工作流程:一共有三种作业运行模式:

Ø  Uber(本地模式):适用于小作业,所有Map Task和Reduce Task位于同一个Container中顺序执行

Ø  Non-Uber,MRAppMaster先为Map Task申请资源,当Map Task运行完成的数目达到一定比例后再为Reduce Task申请资源,作业的Map Task和Reduce Task分为四种状态:

❑pending: 刚启动但尚未向ResourceManager发送资源请求。
❑scheduled: 已经向ResourceManager发送资源请求但尚未分配到资源。
❑assigned: 已经分配到了资源且正在运行。
❑completed: 已经运行完成。

 

原创粉丝点击