Shuffle 与 Storage 模块间的交互

来源:互联网 发布:sql 镜像服务器 编辑:程序博客网 时间:2024/06/08 05:21

 

7.6   Shuffle 与 Storage 模块间的交互 

在Spark中存储模块被抽象成Storage,顾名思义,Storage是存储的意思,代表着Spark中的数据存储系统,负责管理和实现数据块(Block)的存放。其中存取数据的最小单元是Block,数据由不同的Block组成,所有操作都是以Block为单位进行的。本质上讲RDD中的Partition和Storage中的Block是等价的,只是所处的模块不同看待的角度不一样而已。

 Storage抽象模块的实现分为两个层次,如下图所示。

图 7- 13 Storage存储模块

1)        通信层:通信层是典型的Master-Slave结构,Master和Slave之间传输控制和状态信息。通信层主要由BlockManager、BlockManagerMaster、BlockManagerMasterEndpoint、BlockManagerSlaveEndpoint等类实现。

2)        存储层:负者把数据存储到内存、磁盘或者堆外内存中,有时还需要为数据在远程节点上生成副本,这些都由存储层提供的接口实现。具体的存储层的实现类有抽象类BlockStore,实现类DiskStore、MemoryStore、ExternalBlockStore等。

Shuffle模块若要和Storage模块进行交互,需要通过调用统一的操作类BlockManager来完成。如果把整个存储模块看成一个黑盒,BlockManager就是黑盒上留出的一个供外部调用的接口。

7.6.1    Shuffle 注册的交互  

Spark中 BlockManager在Driver端的创建,在SparkContext创建的时候会根据具体的配置创建SparkEnv对象。源代码如下所示。

SparkContext.scala源码:

1.    _env =createSparkEnv(_conf, isLocal, listenerBus)

2.       SparkEnv.set(_env)

3.   .......

4.   private[spark] def createSparkEnv(

5.         conf: SparkConf,

6.         isLocal: Boolean,

7.         listenerBus: LiveListenerBus): SparkEnv ={

8.   //创建Driver端的运行环境

9.       SparkEnv.createDriverEnv(conf, isLocal,listenerBus, SparkContext.numDriverCores(master))

10.   }

createSparkEnv方法中,传入SparkConf配置对象、isLocal标志、以及LiveListenerBus,方法中使用SparkEnv对象的createDriverEnv方法创建SparkEnv并返回。在SparkEnv的createDriverEvn方法中,将会创建BlockManager、BlockManagerMaster等对象,完成Storage在Driver端的部署。

SparkEnv中创建BlockManager、BlockManagerMaster关键源代码如下所示。

SparkEnv.scala源码:

1.      valblockTransferService =

2.         new NettyBlockTransferService(conf,securityManager, bindAddress, advertiseAddress,

3.           blockManagerPort, numUsableCores)

4.   //创建BlockManagerMasterEndpoint

5.       valblockManagerMaster = new BlockManagerMaster(registerOrLookupEndpoint(

6.         BlockManagerMaster.DRIVER_ENDPOINT_NAME,

7.     //创建BlockManagerMasterEndpoint

8.         new BlockManagerMasterEndpoint(rpcEnv,isLocal, conf, listenerBus)),

9.         conf, isDriver)

10.  //创建BlockManager

11.     //NB: blockManager is not valid until initialize() is called later.

12.     valblockManager = new BlockManager(executorId, rpcEnv, blockManagerMaster,

13.       serializerManager, conf, memoryManager, mapOutputTracker,shuffleManager,

14.       blockTransferService, securityManager,numUsableCores)

使用new关键字实例化出BlockManagerMaster,传入BlockManager的构造函数,实例化出BlockManager对象。这里的BlockManagerMaster和BlockManager属于聚合关系。BlockManager主要对外提供统一的访问接口,BlockManagerMaster主要对内提供各节点之间的指令通信服务。

在构建BlockManager的,传入shuffleManager参数,shuffleManager是在SparkEnv中创建的,将shuffleManager传入到BlockManager中,BlockManager就拥有shuffleManager的成员变量,从而可以调用shuffleManager的相关方法。

SparkEnv.scala源码

1.    valshortShuffleMgrNames = Map(

2.         "sort" -> classOf[org.apache.spark.shuffle.sort.SortShuffleManager].getName,

3.         "tungsten-sort" ->classOf[org.apache.spark.shuffle.sort.SortShuffleManager].getName)

4.       valshuffleMgrName = conf.get("spark.shuffle.manager", "sort")

5.       valshuffleMgrClass = shortShuffleMgrNames.getOrElse(shuffleMgrName.toLowerCase,shuffleMgrName)

6.       valshuffleManager = instantiateClass[ShuffleManager](shuffleMgrClass)

 

BlockManagerMaster在Driver端和Executors中的创建稍有差别。首先来看在Driver端创建的情形。创建BlockManagerMaster传入的isDriver参数,isDriver为true表示在Driver端创建,否则视为在Slave节点上创建。

当SparkContext中执行_env.blockManager.initialize(_applicationId)代码时,会调用Driver端BlockManager的initialize方法。Initialize方法源代码如下所示。

SparkContext.scala源码:

1.       _env.blockManager.initialize(_applicationId)

BlockManager.scala源码:

1.    definitialize(appId: String): Unit = {

2.    //调用blockTransferService的init方法,blockTransferService用于在不同节点fetch数据,传送数据

3.      blockTransferService.init(this)

4.    //shuffleClient用于读取其他Executor上的shuffle files

5.       shuffleClient.init(appId)

6.    

7.       blockReplicationPolicy = {

8.         val priorityClass = conf.get(

9.           "spark.storage.replication.policy",classOf[RandomBlockReplicationPolicy].getName)

10.       val clazz =Utils.classForName(priorityClass)

11.       val ret =clazz.newInstance.asInstanceOf[BlockReplicationPolicy]

12.       logInfo(s"Using $priorityClass forblock replication policy")

13.       ret

14.     }

15.  

16.     valid =

17.       BlockManagerId(executorId,blockTransferService.hostName, blockTransferService.port, None)

18.  

19.   //向blockManagerMaster注册BlockManager。在registerBlockManager方法中传入了slaveEndpoint,slaveEndpoint为BlockManager中的RPC对象,用于和blockManagerMaster通信

20.     validFromMaster = master.registerBlockManager(

21.       id,

22.       maxMemory,

23.       slaveEndpoint)

24.   //得到blockManagerId

25.     blockManagerId = if (idFromMaster != null)idFromMaster else id

26.   

27. //得到shuffleServerId

28.     shuffleServerId = if(externalShuffleServiceEnabled) {

29.       logInfo(s"external shuffle serviceport = $externalShuffleServicePort")

30.       BlockManagerId(executorId,blockTransferService.hostName, externalShuffleServicePort)

31.     }else {

32.       blockManagerId

33.     }

34.  //注册shuffleServer

35.     //Register Executors' configuration with the local shuffle service, if one shouldexist.

36.     if(externalShuffleServiceEnabled && !blockManagerId.isDriver) {

37.       registerWithExternalShuffleServer()

38.     }

39.  

40.     logInfo(s"Initialized BlockManager:$blockManagerId")

41.   }

如上面源代码所示,initialize方法使用appId初始化BlockManager。主要完成:

1)        初始化BlockTransferService。

2)        初始化ShuffleClient。

3)        创建BlockManagerId。

4)        将BlockManager注册到BlockManagerMaster上。

5)        若ShuffleService可用,注册ShuffleService。

在BlockManager的initialize方法上点击右键 Find Usages,可以看到initialize方法在两个地方得到调用,一个是SparkContext,另一个是Executor。在启动Executor时,会调用BlockManager的initialize方法。Executor中调用initialize方法源代码如下所示。

Executor.scala源码:

1.    //CoarseGrainedExecutorBackend中实例化Executor,isLocal设置成false,即Executor中isLocal始终为fasle

2.    

3.    if(!isLocal) {

4.   //向度量系统注册

5.       env.metricsSystem.registerSource(executorSource)

6.    //调用BlockManager的initialize方法, initialize方法将向BlockManagerMaster注册,完成Executor中的BlockManager向Driver中的BlockManager的注册

7.       env.blockManager.initialize(conf.getAppId)

8.     }

上面代码中,调用了env.blockManager.initialize方法。在initialize方法中,完成BlockManger向Master端BlockManagerMaster的注册。使用方法master.registerBlockManager(id,maxMemory,slaveEndpoint)完成注册,registerBlockManager方法中传入Id、maxMemory、salveEndPoint引用,分别表示Executor中的BlockManager、最大内存、BlockManger中的BlockMangarSlaveEndpoint。BlockManagerSlaveEndpoint是一个RPC端点,使用它完成同BlockManagerMaster的通信。BlockManager收到收到注册请求后将Executor中注册的BlockManagerInfo存入哈希表中,以便通过BlockManagerSlaveEndpoint向Executor发送控制命令。

 

ShuffleManager是一个用于shuffle系统的可插拔接口。在driver端SparkEnv中创建ShuffleManager创建,在每一个executor上也会创建。基于spark.shuffle.manager进行设置。driver 使用ShuffleManager注册到shuffles系统,executors(或driver在本地运行的任务)可以请求读取和写入数据。这将被SparkEnv的SparkConf和isDriver布尔值作为参数。

ShuffleManager.scala源码:

1.    private[spark]trait ShuffleManager {

2.    

3.     /**

4.      *Register a shuffle with the manager and obtain a handle for it to pass totasks.

5.      */

6.     defregisterShuffle[K, V, C](

7.         shuffleId: Int,

8.         numMaps: Int,

9.         dependency: ShuffleDependency[K, V, C]):ShuffleHandle

10.  

11.   /**Get a writer for a given partition. Called on executors by map tasks. */

12.   defgetWriter[K, V](handle: ShuffleHandle, mapId: Int, context: TaskContext):ShuffleWriter[K, V]

13.  

14.   /**

15.    *Get a reader for a range of reduce partitions (startPartition toendPartition-1, inclusive).

16.    *Called on executors by reduce tasks.

17.    */

18.   defgetReader[K, C](

19.       handle: ShuffleHandle,

20.       startPartition: Int,

21.       endPartition: Int,

22.       context: TaskContext): ShuffleReader[K,C]

23.  

24.   /**

25.    *Remove a shuffle's metadata from the ShuffleManager.

26.    *@return true if the metadata removed successfully, otherwise false.

27.    */

28.   defunregisterShuffle(shuffleId: Int): Boolean

29.  

30.   /**

31.    * 返回一个能够根据块坐标来检索shuffle 块数据的解析器。

32.    */

33.   defshuffleBlockResolver: ShuffleBlockResolver

34.  

35.   /** 关闭ShuffleManager.*/

36.   defstop(): Unit

37. }

 

Spark Shuffle Pluggable框架ShuffleBlockManager在Spark 1.6.0之后改成了ShuffleBlockResolver。 ShuffleBlockResolver具体读取shuffle数据,是一个trait。在ShuffleBlockResolver中已无getBytes方法。getBlockData(blockId:ShuffleBlockId)方法返回的是ManagedBuffer,这个是核心。

ShuffleBlockResolver源码:

1.      trait ShuffleBlockResolver {

2.     typeShuffleId = Int

3.    

4.     /**

5.      *Retrieve the data for the specified block. If the data for that block is notavailable,

6.      *throws an unspecified exception.

7.      */

8.     defgetBlockData(blockId: ShuffleBlockId): ManagedBuffer

9.    

10.   defstop(): Unit

11. }

 

spark 2.0版本中通过SortShuffleWriterIndexShuffleBlockResolver来具体实现ShuffleBlockResolver(SortBasedShuffl方式),已无FileShuffleBlockManager(Hashshuffle方式)。SortShuffleWriterIndexShuffleBlockResolver创建和维护逻辑块和物理文件位置之间的shuffle blocks映射关系。来自于相同map task任务的shuffle blocks数据存储在单个合并数据文件中;数据文件中的数据块的偏移量存储在单独的索引文件中。将 shuffleBlockId + reduce ID set to 0 + ".后缀" 作为数据shuffledata的shuffleBlockId名字。其中, 文件名后缀为".data"的是数据文件;文件名后缀为".index"的是索引文件。

7.6.2    Shuffle 写数据的交互   

     基于Sort的Shuffle实现的ShuffleHandle包含BypassMergeSortShuffleHandle与BaseShuffleHandle。两种ShuffleHandle写数据的方法可以参考SortShuffleManager类的getWriter方法,关键代码如下所示:

SortShuffleManager的getWriter源码:

1.              override def getWriter[K, V](

2.           …….

3.               case bypassMergeSortHandle:BypassMergeSortShuffleHandle[K @unchecked, V @unchecked] =>

4.                 newBypassMergeSortShuffleWriter(

5.                   env.blockManager,

6.                   shuffleBlockResolver.asInstanceOf[IndexShuffleBlockResolver],

7.                 ……..

8.               case other:BaseShuffleHandle[K @unchecked, V @unchecked, _] =>

9.                 new SortShuffleWriter(shuffleBlockResolver,other, mapId, context)

10.          }

11.        }

 

在对应构建的两种数据写入器类BypassMergeSortShuffleWriter与SortShuffleWriter中,都是通过变量shuffleBlockResolver对逻辑数据块与物理数据块的映射进行解析。  BypassMergeSortShuffleWriter写数据的具体实现位于实现的write方法中,其中调用的createTempShuffleBlock()方法描述了各个分区所生成的中间临时文件的格式与对应的BlockId;SortShuffleWriter写数据的具体实现位于实现的write方法中。

7.6.3    Shuffle 读数据的交互   

       SparkEnv.get.shuffleManager.getReader是SortShuffleManager的getReader,是获取数据的阅读器,getReader方法中创建了一个BlockStoreShuffleReader实例。BlockStoreShuffleReader.scala的read()方法源码:

1.           override def getReader[K, C](

2.               handle: ShuffleHandle,

3.               startPartition: Int,

4.               endPartition: Int,

5.               context: TaskContext):ShuffleReader[K, C] = {

6.             new BlockStoreShuffleReader(

7.               handle.asInstanceOf[BaseShuffleHandle[K,_, C]], startPartition, endPartition, context)

8.           }

          BlockStoreShuffleReader实例的read()方法,首先实例化newShuffleBlockFetcherIterator。ShuffleBlockFetcherIterator是一个阅读器,里面有一个成员blockManager,blockManager是内存和磁盘上数据读写的统一管理器;ShuffleBlockFetcherIterator.scala的initialize方法中splitLocalRemoteBlocks()划分本地和远程的blocks, Utils.randomize(remoteRequests)把远程请求通过随机的方式添加到队列中, fetchUpToMaxBytes()发送远程请求获取我们的block,fetchLocalBlocks()获取本地的blocks。

阅读全文
0 0
原创粉丝点击