大数据Spark Shuffle三Executor是如何fetch shuffle的数据文件.docx

上传人:b****6 文档编号:3274101 上传时间:2022-11-21 格式:DOCX 页数:14 大小:145.56KB
下载 相关 举报
大数据Spark Shuffle三Executor是如何fetch shuffle的数据文件.docx_第1页
第1页 / 共14页
大数据Spark Shuffle三Executor是如何fetch shuffle的数据文件.docx_第2页
第2页 / 共14页
大数据Spark Shuffle三Executor是如何fetch shuffle的数据文件.docx_第3页
第3页 / 共14页
大数据Spark Shuffle三Executor是如何fetch shuffle的数据文件.docx_第4页
第4页 / 共14页
大数据Spark Shuffle三Executor是如何fetch shuffle的数据文件.docx_第5页
第5页 / 共14页
点击查看更多>>
下载资源
资源描述

大数据Spark Shuffle三Executor是如何fetch shuffle的数据文件.docx

《大数据Spark Shuffle三Executor是如何fetch shuffle的数据文件.docx》由会员分享,可在线阅读,更多相关《大数据Spark Shuffle三Executor是如何fetch shuffle的数据文件.docx(14页珍藏版)》请在冰豆网上搜索。

大数据Spark Shuffle三Executor是如何fetch shuffle的数据文件.docx

大数据SparkShuffle三Executor是如何fetchshuffle的数据文件

大数据:

SparkShuffle(三)Executor是如何fetchshuffle的数据文件

1.前言

Executor是如何获取到Shuffle的数据文件进行Action的算子的计算呢?

在ResultTask中,Executor通过MapOutPutTracker向Driver获取了ShuffID的Shuffle数据块的结构,整理成以BlockManangerId为Key的结构,这样可以更容易区分究竟是本地的Shuffle还是远端executor的Shuffle

2.Fetch数据

在MapOutputTracker中获取到的BlockID的地址,是以BlockManagerId的seq数组

[plain]viewplaincopy

Seq[(BlockManagerId,Seq[(BlockId,Long)])]

BlockManagerId结构

[plain]viewplaincopy

classBlockManagerIdprivate(

privatevarexecutorId_:

String,

privatevarhost_:

String,

privatevarport_:

Int,

privatevartopologyInfo_:

Option[String])

extendsExternalizable

是以ExecutorId,ExecutorHostIP,ExecutorPort标示从哪个Executor获取Shuffle的数据文件,通过Seq[BlockManagerId,Seq(BlockID,Long)]的结构,当前executor很容易区分究竟哪些是本地的数据文件,哪些是远端的数据,本地的数据可以直接本地读取,而需要不通过网络来获取。

2.1读取本Executor文件

如何认为是本地数据?

Spark认为区分是通过相同的ExecutorId来区别的,如果ExecutorId和自己的ExecutorId相同,认为是本地Local,可以直接读取文件。

[plain]viewplaincopy

for((address,blockInfos)<-blocksByAddress){

totalBlocks+=blockInfos.size

if(address.executorId==blockManager.blockManagerId.executorId){

//Filteroutzero-sizedblocks

localBlocks++=blockInfos.filter(_._2!

=0).map(_._1)

numBlocksToFetch+=localBlocks.size

}

}

这里有两种情况:

同一个Executor会生成多个Task,单个Executor里的Task运行可以直接获取本地文件,不需要通过网络

同一台机器多个Executor,在这种情况下,不同的Executor获取相同机器下的其他的Executor的文件,需要通过网络

2.2读取非本Executor文件

2.2.1构造FetchRequest请求

获取非本Executor的文件,在Spark里会生成一个FetchRequest,为了避免单个Executor的MapId过多发送多个FetchRequest请求,会合并同一个Executor的多个请求,合并的规则由最大的请求参数控制

[plain]viewplaincopy

spark.reducer.maxSizeInFlight

valtargetRequestSize=math.max(maxBytesInFlight/5,1L)

对同一个Executor,如果请求多个Block请求的数据大小未超过targetRequestSize,将会被分配到同一个FetchRequest中,以避免多次FetchRequest的请求

[plain]viewplaincopy

valiterator=blockInfos.iterator

varcurRequestSize=0L

varcurBlocks=newArrayBuffer[(BlockId,Long)]

while(iterator.hasNext){

val(blockId,size)=iterator.next()

//Skipemptyblocks

if(size>0){

curBlocks+=((blockId,size))

remoteBlocks+=blockId

numBlocksToFetch+=1

curRequestSize+=size

}elseif(size<0){

thrownewBlockException(blockId,"Negativeblocksize"+size)

}

if(curRequestSize>=targetRequestSize){

//AddthisFetchRequest

remoteRequests+=newFetchRequest(address,curBlocks)

curBlocks=newArrayBuffer[(BlockId,Long)]

logDebug(s"Creatingfetchrequestof$curRequestSizeat$address")

curRequestSize=0

}

}

//Addinthefinalrequest

if(curBlocks.nonEmpty){

remoteRequests+=newFetchRequest(address,curBlocks)

}

多个FetchRequest会被随机化后放入队列Queue中,每个Executor从Driver端获取的ShuffID对应的BlockManagerID所管理的BlockID的状态是相同的顺序,如果不对FetchRequest进行随机化,那么非常有可能存在多个Executor同时向同一个Executor获取发送FetchRequest的情况,从而导致Executor的负载增高,为了均衡每个Executor的数据获取,随机化FetchRequest是非常有必要的。

2.2.1发送FetchRequest

FetchRequest并不是并行提交的,对同一个Task来说,在Executor的做combine的时候是一个一个的BlockID块合并的,而Task本身就是一个线程运行的,所以不需要设计FetchRequest成并行提交,当一个BlockID完成计算后,才需要判断是否需要进行下一个FetchRequest请求,因为FetchRequest是多个Block提交的,为了控制Executor获取多个BlockID的shuffle数据的带宽,在提交FetchRequest的时候控制了请求的频率

在满足下面以下条件下,才允许提交下个FetchRequest

当正在请求的所有BlockId的内容和下一个FetchRequest的请求内容之和小于maxBytesInFlight的时候,才能进行下一个FetchRequest的请求

当正在请求的数量小于所设置的最大的允许请求数量的时候,才能进行下一个FetchRequest的请求,控制参数如下:

[plain]viewplaincopy

spark.reducer.maxReqsInFlight

2.2.2完整的FetchRequest流程

ExecutorA通过ExternalShuffleClient进行fetchBlocks的操作,如果配置了

[plain]viewplaincopy

io.maxRetries

最大重试参数的话,将启动一个能重试RetryingBlockFetcher的获取器

初始化TransportClient,OneForOneBlockFetcher获取器

在OneForOneBlockFetcher里首先向另一个ExecutorB发送了OpenBlocks的询问请求,里面告知ExecutorID,APPID和BlockID的集合

ExecutorB获取到BlockIDs,后通过BlockManager获取相关的BlockID的文件(通过mapid,reduceid获取相关的索引和数据文件),构建FileSegmentManagedBuffer

通过StreamManager(OneForOneStreamManager)registerStream生成streamId,和StreamState(多个ManagedBuffer,AppID)的缓存

返回所生成的StreamId

ExecutorB返回给StreamHandle的消息,里面包含了StreamId和Chunk的数量,这里chunk的数量其实就是Block的数量

ExecutorA获取到StreamHandle的消息,一个一个的发送ChunkFetchRequest里面包含了StreamId,Chunkindex,去真实的获取ExecutorB的shuffle数据文件

ExecutorB通过传递的ChunkFetchRequest消息获取到StreamId,Chunkindex,通过缓存获取到对应的FileSgementManagedBuffer,返回chunkFetchSuccess消息,里面包含着streamID,和FileSegmentManagedBuffer

在步骤3-6步骤里是堵塞在Task线程里,而步骤7一个一个发送ChunkFetchRequest后,并不堵塞等待返回结果,结果是通过回调函数来实现的,在调用前注册了一个回调函数

[plain]viewplaincopy

client.fetchChunk(streamHandle.streamId,i,chunkCallback);

privateclassChunkCallbackimplementsChunkReceivedCallback{

@Override

publicvoidonSuccess(intchunkIndex,ManagedBufferbuffer){

//Onreceiptofachunk,passitupwardsasablock.

listener.onBlockFetchSuccess(blockIds[chunkIndex],buffer);

}

@Override

publicvoidonFailure(intchunkIndex,Throwablee){

//Onreceiptofafailure,faileveryblockfromchunkIndexonwards.

String[]remainingBlockIds=Arrays.copyOfRange(blockIds,chunkIndex,blockIds.length);

failRemainingBlocks(remainingBlockIds,e);

}

}

在这里的listener就是前面fetchBlocks里注入的BlockFetchingListener

[plain]viewplaincopy

newBlockFetchingListener{

overridedefonBlockFetchSuccess(blockId:

String,buf:

ManagedBuffer):

Unit={

//Onlyaddthebuffertoresultsqueueiftheiteratorisnotzombie,

//i.e.cleanup()hasnotbeencalledyet.

ShuffleBlockFetcherIterator.this.synchronized{

if(!

isZombie){

//Incrementtherefcountbecauseweneedtopassthistoadifferentthread.

//Thisneedstobereleasedafteruse.

retain()

remainingBlocks-=blockId

results.put(newSuccessFetchResult(BlockId(blockId),address,sizeMap(blockId),buf,

remainingBlocks.isEmpty))

logDebug("remainingBlocks:

"+remainingBlocks)

}

}

logTrace("Gotremoteblock"+blockId+"after"+Utils.getUsedTimeMs(startTime))

}

overridedefonBlockFetchFailure(blockId:

String,e:

Throwable):

Unit={

logError(s"Failedtogetblock(s)from${req.address.host}:

${req.address.port}",e)

results.put(newFailureFetchResult(BlockId(blockId),address,e))

}

}

如果获取成功将封装SuccessFetchResult里面保存着blockId,地址,数据大小,以及ManagedBuffer,并保存到results的queue中

2.2.3Fetch迭代获取数据文件

Executor在BlockStoreShuffeReader的read函数中构建ShuffleBlockFetcherIterator,ShuffleBlockFetcherIterator是个InputStream的迭代器,每个BlockID生成一个InputStream,在设计里并没有区分是本地的还是远端的,每一次迭代都是从堵塞的Queue里获取到BlockID的ManagerBuffer,通过调用ManagerBuffer.createInputStream获取每个InputStream,进行读取并且反序列话,进行KV的combine.

如何判断所有的BlockID已经读取完了?

[plain]viewplaincopy

overridedefhasNext:

Boolean=numBlocksProcessed

在hasNext里判断当前的是否已经达到需要读取的block数量了,每一次读取下一个block的时候都会在numBlocksProcessed+1,在读取失败的情况下会直接抛出异常。

3.Fetch交互协议

在前面的博客里描述了很多交互协议都使用了Java的原生态的反序列化,但在上文描述的Fetch协议中,是Spark单独定义的一套协议标准,自己实现encoder和decoder

ChunkFetchRequest,ChunkFetchSuccess,RpcRequest,RpcResponse....这些都是直接使用Java进行封装,在Network-Commmon的包里,所有的消息最后都实现了基本的接口。

3.1MessageEncoder

[java]viewplaincopy

publicinterfaceMessageextendsEncodable{}

而核心的是Encodable,有点类似Java的Serializable接口,需要自己实现Encoder和Decoder的方法

[java]viewplaincopy

publicinterfaceEncodable{

/**Numberofbytesoftheencodedformofthisobject.*/

intencodedLength();

/**

*SerializesthisobjectbywritingintothegivenByteBuf.

*ThismethodmustwriteexactlyencodedLength()bytes.

*/

voidencode(ByteBufbuf);

}

核心的序列话的encode的入参数是ByteBuf很符合Netty里的NIO所暴露出的接口,同时也要注意这是Netty的ByteBuf和Netty是耦合了

如何让Netty调用Encodableencode方法呢?

在Netty里暴露出的类MessageToMessageEncoder,里暴露encode的抽象方法,这是一个可以允许对传递的消息进行一次自定义的编码

[java]viewplaincopy

MessageToMessageEncoderprotectedabstractvoidencode(ChannelHandlerContextparamChannelHandlerContext,IparamI,ListparamList)

/**/throwsException;

在Spark里自己实现MessageToMessageEncoder的encoder的方法

[java]viewplaincopy

publicfinalclassMessageEncoderextendsMessageToMessageEncoder{

privatestaticfinalLoggerlogger=LoggerFactory.getLogger(MessageEncoder.class);

/***

*EncodesaMessagebyinvokingitsencode()method.Fornon-datamessages,wewilladdone

*ByteBufto'out'containingthetotalframelength,themessagetype,andthemessageitself.

*InthecaseofaChunkFetchSuccess,wewillalsoaddtheManagedBuffercorrespondingtothe

*datato'out',inordertoenablezero-copytransfer.

*/

@Override

publicvoidencode(ChannelHandlerContextctx,Messagein,Listout)throwsException{

Objectbody=null;

longbodyLength=0;

booleanisBodyInFrame=false;

//Ifthemessagehasabody,takeitouttoenablezero-copytransferforthepayload.

if(in.body()!

=null){

try{

bodyLength=in.body().size();

body=in.body().convertToNetty();

isBodyInFrame=in.isBodyInFrame();

}catch(Exceptione){

in.body().release();

if(ininstanceofAbstractResponseMessage){

AbstractResponseMessageresp=(AbstractResponseMessage)in;

//Re-encodethismessageasafailureresponse.

Stringerror=e.getMessage()!

=null?

e.getMessage():

"null";

logger.error(String.format("Errorprocessing%sforclient%s",

in,ctx.channel().remoteAddress()),e);

encode(ctx,resp.createFailureResponse(error),out);

}else{

throwe;

}

return;

}

}

Message.TypemsgType=in.type();

//Allmessageshavetheframelength,messagetype,andmessageitself.Theframelength

//mayoptionallyincludethelengthofthebodydata,dependingonwhatmessageisbeing

//sent.

intheaderLength=8+msgType.encodedLength()+in.encodedLength();

longframeLength=headerLength+(isBodyInFrame?

bodyLength:

0);

ByteBufheader=ctx.alloc().heapBuffer(headerLength);

header.writeLong(frameLength);

msgType.encode(header);

in.encode(header);

ass

展开阅读全文
相关搜索

当前位置:首页 > 表格模板 > 书信模板

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1