Spark Broadcast中writeBlocks为啥put两次?
有兄弟在看代码的时候发现一个现象,在TorrentBroadcast广播实现类中为啥wirteBlocks方法中会向BlockManager put两次值呢?
现象如下所示:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/5b647fd1fe52d56a7302c7acb23824e1.png)
要解释上面的问题,我需要从三个方面做详细描述:
- 解释什么是broadcast;
- 剖析putSingle与putBytes;
- 跑个例子来总结一下具体现象;
1 broadcast
什么是broadcast?
顾名思义,broadcast 就是将数据从一个节点发送到其他各个节点上去。这样的场景很多,比如 driver 上有一张表,其他节点上运行的 task 需要 lookup 这张表,那么 driver 可以先把这张表 copy 到这些节点,这样 task 就可以在本地查表了。
官方定义:
Broadcast variables allow the programmer to keep a read-only variable cached on each machine rather than shipping a copy of it with tasks. They can be used, for example, to give every node a copy of a large input dataset in an efficient manner. Spark also attempts to distribute broadcast variables using efficient broadcast algorithms to reduce communication cost.
先回答下面几个问题就会明白什么是broadcast:
1、在spark什么东西能广播呢?换句话说广播有什么用呢?
Spark中因为算子中的真正逻辑是发送到Executor中去运行的,所以当Executor中需要引用外部变量时,需要使用广播变量。
–变量参数可以被广播,比如FileSourceScanExec在构造inputRDD时调用到OrcFileFormat#buildReaderWithPartitionValues内部就会将HadoopConf进行广播,如下图所示:
– 在提交任务之前会将序列化好的***task可以进行广播***,DAGScheduler#submitMissingTasks,如下图所示:
2、broadcast 到节点而不是 broadcast 到每个 task?
因为每个 task 是一个线程,而且同在一个进程运行 tasks 都属于同一个 application。因此每个节点(executor)上放一份就可以被所有 task 共享。
3、怎么实现 broadcast?
分发task时先分发的是data的元数据 ? 当调用val bdata= sc.broadcast(data)
时就把 data 写入文件夹(Driver 先建一个本地文件夹用以存放需要 broadcast 的 data),同时写入 driver 自己的 blockManger 中(StorageLevel 为MEMORY_AND_DISK**),获得一个 blockId,类型为 BroadcastBlockId。当调用rdd.transformation(func)
时,如果 func 用到了 data,那么 driver submitTask() 的时候会将 data 一同 func 进行序列化得到 serialized task,注意序列化的时候不会序列化bdata中包含的 data。
什么时候传送真正的 data?在 executor 反序列化 task 的时候,会同时反序列化 task 中的 bdata 对象,这时候会调用 bdata 的 readObject() 方法。该方法先去本地 blockManager 那里询问 bdata 的 data 在不在 blockManager 里面,如果不在就使用下面TorrentBroadcast** fetch 方式之一去将 data fetch 过来。得到 data 后,将其存放到 blockManager 里面,这样后面运行的 task 如果需要 bdata 就不需要再去 fetch data 了。如果在,就直接拿来用了。
4、TorrentBroadcast是个啥?
TorrentBroadcast,这个类似于大家常用的BitTorrent 技术,基本思想就是将 data 分块成 data blocks,然后假设有 executor fetch 到了一些 data blocks,那么这个 executor 就可以被当作 data server 了,随着 fetch 的 executor 越来越多,有更多的 data server 加入,data 就很快能传播到全部的 executor 那里去了。
在 TorrentBroadcast 里面使用blockManager.getRemote() => NIO ConnectionManager 传数据的方法来传递。
行了上面哔哔了那么多,很多兄弟肯定看得概念性的东西难受,不多说了,咱们撸一把代码,来点儿真实的,看看细节是如何实现的。
2 剖析putSingle与putBytes
此方法的来源是什么?当然是Driver端发起,以提交任务为例子来看一下主线流程:
Driver端:先把 data 序列化到 byteArray,然后切割成 BLOCK_SIZE(由 spark.broadcast.blockSize = 4MB
设置)大小的 data block,完成分块切割后,就将分块信息(称为 meta 信息)存放到 driver 自己的 blockManager 里面,StorageLevel 为内存+磁盘,同时会通知 driver 自己的 blockManagerMaster 说 meta 信息已经存放好。
blockManager.putSingle(broadcastId, value, MEMORY_AND_DISK, tellMaster = false)
blockManager.putBytes(pieceId, bytes, MEMORY_AND_DISK_SER, tellMaster = true)
代码中tellMaster=true则会通知blockManagerMaster。通知 blockManagerMaster 这一步很重要,因为 blockManagerMaster 可以被 driver 和所有 executor 访问到,信息被存放到 blockManagerMaster 就变成了全局信息。
之后将每个分块 data block 存放到