主要想回答兩個問題:
- map端(shuffle-write)如何對數據進行分片?
- reduce端(shuffle-read)如何讀取數據?
ShuffleMapTask中,指定此task運算真對上游RDD的那個partition,即map端的partition,writer.write操作的時候,根據RDD的partitioner生成新的partitionId,然后寫入,完成shuffle-write,下游shuffle-read的時候,拉取相應得partition數據即可;
下面插入一段說一下Spark中netty block server的實現:
- NettyRpcEnv :: TransportContext-> createServer -> new TransportServer
- TransportServer中appRpcHandler就是上層處理邏輯,默認沒有安全配置的情況下,bootstraps集合為空;
- TransportServer -> init 初始化bootstrap,其中childHandler定義了對請求的處理邏輯,即context.initializePipeline(ch, rpcHandler);
- TransportContext :: initializePipeLine 定義了處理請求的pipeline,pipeline中包括對req,rap的encoder,decoder,TransportChannelHandler;
- TransportChannelHandler :: channelRead0 根據message的不同,分別調用requestHandler和responseHandler進行處理,上層的RpcHandler就包含在RequestHandler中;
當reduce端讀取數據的時候,ShuffleBlockFetcherIterator :: sendRequest 調用 NettyBlockTransferService :: fetchBlocks 調用OneForOneBlockFetcher::start 首先調用TransportClient :: sendRpcSync 發送OpenBlocks發送到上面提到的netty block server,然后發送ChunkFetchRequest,獲取對應的chunk,這里面的chunk其實就是一個一個的block,一個(shuffleId, mapId, bucketId(reduceId))唯一確定一個block,也即下游RDD的一個partition;
shuffle-read其實是從上游executor以block為單位獲取數據,這里就遇到了一個問題,如果數據分布不均勻,導致下游某個partition過大,即這個block過大,就會出現OOM,Netty會報錯direct buffer out of memory;
上面說的OOM是Netty處理數據時堆外內存的OOM,如果限制使用堆外內存(為Executor增加配置-Dio.netty.noUnsafe=true,就可以讓shuffle不使用堆外內存),會報堆內內存OOM,java.lang.OutOfMemoryError: Java heap space;
如何解決?
其實在對Block處理過程中,無論是Client端還是Server端,都是以ManagedBuffer來處理的,具體實現類有FileSegmentManagedBuffer,NettyManagedBuffer等,Server端收到請求之后,會將返回的Block封裝在FileSegmentmanagedBuffer,這個類內部不cache數據,提供從文件中讀取block data的方法,但是過rpc server時通過encoder會進行封裝,從FIleChannel零拷貝寫入SocketChannel,具體實現就是在MessageEncoder里面將FileSegmentBuffer converToNetty,其實生成時FileRegion,后面封裝到MessageWithHeader也是FileRegion,寫出到List<Object> out,Netty會調用FileRegion中的transferTo,將內容寫到目標channel,寫入是直接調用file.transfer,實現零拷貝;
所以是否可以嘗試添加一個新的協議,在OneForOneBlockFetcher中,判斷,如果一個block小于某值,比如100M,使用原來的方式fetch數據,否則,服務端收到請求之后返回數據流,客戶端收到數據流之后,將數據寫到本地文件,形成新的FileSegmentManagedBuffer,供后續處理,對比原來的實現,就是將客戶端直接處理NettyManagedBuffer變成直接處理FileSegmentManagedBuffer;