spark broadcast广播原理优缺点示例源码权威讲解点击这里看全文
文章目录
广播原理
Spark广播(broadcast)的原理是通过将一个只读变量从驱动程序发送到集群上的所有工作节点,以便在运行任务时能够高效地访问这个变量。广播变量只会被发送一次,并且在工作节点上缓存,以供后续任务重用。
下面是Spark广播的实现方法:
- 驱动程序将要广播的变量划分为小块,并对每个块进行序列化。
- 驱动程序将序列化的块发送给各个工作节点。
- 每个工作节点接收到序列化的块后,将其反序列化并存储在内存中。
- 在执行任务时,每个工作节点可以直接访问已经缓存的广播变量,而不需要从驱动程序再次获取。
这种方式可以避免在任务执行期间多次传输相同的数据,从而提高性能和效率。
在Spark中,广播变量的实现主要依赖于DriverEndpoint和ExecutorEndpoint之间的通信机制。具体来说,当驱动程序将广播变量发送给工作节点时,它会使用BlockManager将序列化的块存储在内存中,并将块的元数据注册到BlockManagerMaster。然后,当工作节点执行任务时,它会向BlockManagerMaster请求获取广播变量的块,并从本地BlockManager中获取这些块的数据。这样,每个工作节点都可以在本地快速访问广播变量的数据。
总结起来,Spark广播的实现涉及驱动程序对广播变量进行序列化和发送,以及工作节点接收、反序列化和缓存广播变量的块。这种机制有效地将只读数据分发到集群上的所有工作节点,提高了任务执行的性能和效率。
适用场景
广播变量在以下场景中非常有用:
-
广播较大的只读数据集:当需要在多个任务中共享一个较大的只读数据集时,广播变量可以避免将该数据集复制到每个任务中。这样可以减少网络传输和内存消耗。
-
提高任务执行效率:如果一个任务需要频繁地使用相同的只读数据,通过广播变量可以将数据缓存在工作节点上,避免重复传输数据,从而提高任务的执行效率。
-
减少数据传输开销:广播变量将只读数据发送到工作节点一次,并在本地进行缓存,避免了多次传输相同的数据,减少了网络开销。
-
避免内存溢出:对于大规模的只读数据集,将其广播到工作节点并在本地缓存可以避免驱动程序的内存溢出问题。
-
共享全局配置信息:如果有全局的配置信息需要在不同任务之间共享,可以使用广播变量将其发送到工作节点,方便任务访问。
总之,广播变量适用于需要在多个任务之间共享只读数据,并且能够提供更高效的数据访问和减少网络传输开销的情况。通过使用广播变量,可以提高Spark应用程序的性能和效率。
缺点
虽然广播在分布式计算中有很多优点,但它也存在一些缺点:
-
内存消耗:广播变量需要将数据集复制到每个工作节点的内存中进行缓存。对于较大的数据集,这可能导致内存消耗较高,特别是当集群规模较大时。
-
延迟问题:由于广播变量需要将数据集发送到每个工作节点并进行缓存,所以在开始任务之前可能会有一定的延迟。这可能会对实时性要求较高的应用程序产生影响。
-
传输开销:广播变量的数据需要通过网络传输到工作节点,并且每个节点都需要接收和存储这些数据。对于大规模数据集,传输开销可能会比较大,特别是在网络带宽有限的情况下。
-
只读限制:广播变量是只读的,无法在任务执行过程中进行修改。如果需要对数据进行更新或变换,广播变量可能不适合。
-
需要额外管理:使用广播变量需要在驱动程序中显式创建和管理,包括序列化、发送和缓存。这增加了编码和维护的复杂性。
因此,在使用广播变量时需要考虑其局限性和适用场景。如果数据集较大,实时性要求高,或者需要频繁修改数据,可能需要考虑其他替代方案来避免广播的缺点。
示例
import org.apache.spark.{
SparkConf, SparkContext}
object BroadcastExample {
def main(args: Array[String]): Unit = {
// 创建SparkConf对象
val conf = new SparkConf().setAppName("Broadcast Example").setMaster("local[*]")
// 创建SparkContext对象
val sc = new SparkContext(conf)
try {
// 创建要广播的只读数据集
val data = Map("A" -> 1, "B" -> 2, "C" -> 3)
val broadcastData = sc.broadcast(data)
// 创建RDD并在任务中访问广播变量
val rdd = sc.parallelize(Seq("A", "B", "C"))
val result = rdd.map(key => (key, broadcastData.value.getOrElse(key, -1)))
// 打印结果
result.foreach(println)
} finally {
// 关闭SparkContext对象
sc.stop()
}
}
}
源码
broadcast方法
功能:将只读变量广播到集群,返回一个Broadcast对象以在分布式函数中进行读取变量将仅发送一次到每个执行器,同时调用了内部的方法broadcastInternal
/**
* 将只读变量广播到集群,返回一个 [[org.apache.spark.broadcast.Broadcast]] 对象以在分布式函数中进行读取。
* 变量将仅发送一次到每个执行器。
*
* @param value 要广播到 Spark 节点的值
* @return `Broadcast` 对象,一个在每台机器上缓存的只读变量
*/
def broadcast[T: ClassTag](value: T): Broadcast[T] = {
broadcastInternal(value, serializedOnly = false)
}
基础类Broadcast抽象类
Broadcast
是 Spark 中的一个广播变量类。广播变量允许程序员在每台机器上缓存一个只读的变量,而不是将它与任务一起传输。通过使用广播变量,可以以高效的方式为每个节点提供大型输入数据集的副本。
Broadcast
类的构造函数接收一个唯一标识符 id
,用于标识广播变量。
Broadcast
类是一个抽象类,有以下几个主要方法:
value
方法:获取广播变量的值。unpersist
方法:异步删除执行器上此广播变量的缓存副本。可以选择阻塞等待操作完成。destroy
方法:销毁与此广播变量相关的所有数据和元数据。一旦广播变量被销毁,就不能再次使用它。也可以选择阻塞等待操作完成。
Broadcast
类还定义了一些受保护的方法,用于实际获取广播变量的值、取消持久化广播变量的值以及销毁广播变量的状态。
Broadcast
类还具有 _isValid
和 _destroySite
两个私有变量,分别表示广播变量是否有效(即尚未销毁)以及销毁广播变量的位置信息。
总体来说,Broadcast
类提供了管理广播变量的功能,并确保广播变量的正确使用和销毁。
/**
* 广播变量。广播变量允许程序员在每台机器上缓存一个只读变量,而不是将其与任务一起传输。它们可以用于以高效的方式为每个节点提供大型输入数据集的副本。
* Spark 还尝试使用高效的广播算法分发广播变量,以减少通信成本。
*
* 广播变量是通过调用 [[org.apache.spark.SparkContext#broadcast]] 从变量 `v` 创建的。
* 广播变量是对 `v` 的包装,可以通过调用 `value` 方法来访问其值。下面的解释器会话显示了这一点:
*
* {
{
{
* scala> val broadcastVar = sc.broadcast(Array(1, 2, 3))
* broadcastVar: org.apache.spark.broadcast.Broadcast[Array[Int]] = Broadcast(0)
*
* scala> broadcastVar.value
* res0: Array[Int] = Array(1, 2, 3)
* }}}
*
* 创建广播变量后,在集群上运行的任何函数中都应该使用广播变量,而不是值 `v`,以便 `v` 不会多次发送到节点。
* 此外,在广播之后不应修改对象 `v`,以确保所有节点获得相同的广播变量的值(例如,如果稍后将变量发送到新节点)。
*
* @param id 广播变量的唯一标识符。
* @tparam T 广播变量中包含的数据的类型。
*/
abstract class Broadcast[T: ClassTag](val id: Long) extends Serializable with Logging {
/**
* 表示广播变量是否有效(即尚未销毁)的标志。
*/
@volatile private var _isValid = true
private var _destroySite = ""
/** 获取广播变量的值。 */
def value: T = {
assertValid()
getValue()
}
/**
* 异步删除执行器上此广播的缓存副本。
* 如果在调用此方法后继续使用广播变量,则需要将其重新发送到每个执行器。
*/
def unpersist(): Unit = {
unpersist(blocking = false)
}
/**
* 删除执行器上此广播的缓存副本。
* 如果在调用此方法后继续使用广播变量,则需要将其重新发送到每个执行器。
* @param blocking 是否阻塞,直到取消持久化完成
*/
def unpersist(blocking: Boolean): Unit = {
assertValid()
doUnpersist(blocking)
}
/**
* 销毁与此广播变量相关的所有数据和元数据。请谨慎使用此方法;一旦销毁了广播变量,就无法再次使用它。
*/
def destroy(): Unit = {
destroy(blocking = false)
}
/**
* 销毁与此广播变量相关的所有数据和元数据。请谨慎使用此方法;一旦销毁了广播变量,就无法再次使用它。
* @param blocking 是否阻塞,直到销毁完成
*/
private[spark] def destroy(blocking: Boolean): Unit = {
assertValid()
_isValid = false
_destroySite = Utils.getCallSite().shortForm
logInfo("正在销毁 %s(来自 %s)".format(toString, _destroySite))
doDestroy(blocking)
}
/**
* 此广播变量是否可用。一旦从驱动程序中删除了持久状态,这个值应该为 false。
*/
private[spark] def isValid: Boolean = {
_isValid
}
/**
* 实际获取广播的值。Broadcast 类的具体实现必须定义自己的获取值的方法。
*/
protected def getValue(): T
/**
* 在执行器上异步取消持久化广播的值。Broadcast 类的具体实现必须定义自己的取消持久化逻辑。
*/
protected def doUnpersist(blocking: Boolean): Unit
/**
* 实际销毁与此广播变量相关的所有数据和元数据。Broadcast 类的实现必须定义自己的销毁状态的逻辑。
*/
protected def doDestroy(blocking: Boolean): Unit
/** 检查此广播变量是否有效。如果无效,则抛出异常。 */
protected def assertValid(): Unit = {
if (!_isValid) {
throw new SparkException(
"在销毁后尝试使用 %s(%s)".format(toString, _destroySite))
}
}
override def toString: String = "Broadcast(" + id + ")"
}
实现类TorrentBroadcast
TorrentBroadcast
是使用类似 BitTorrent 协议实现的 Broadcast
的具体实现(目前spark中只有一种实现)。它继承自 Broadcast
类,并提供以下功能:
- 将对象分成多个块并将这些块存储在驱动程序的块管理器中。
- 在每个执行器上,首先尝试从其块管理器获取对象。如果不存在,则使用远程获取从驱动程序和/或其他执行器获取小块。获取到块后,将块放入自己的块管理器中,以便其他执行器可以获取。
- 这样可以防止驱动程序成为发送多个副本的广播数据的瓶颈。
- 当初始化时,
TorrentBroadcast
从 SparkEnv 获取配置。
TorrentBroadcast
包含以下主要成员变量和方法:
_value
:在执行器上的广播对象的值。通过调用readBroadcastBlock
方法从驱动程序和/或其他执行器读取块来重建该值。compressionCodec
:用于压缩的压缩编解码器。blockSize
:每个块的大小,默认为4MB。isLocalMaster
:是否在本地模式下执行。checksumEnabled
:是否生成块的校验和。writeBlocks(value: T): Int
:将对象分成多个块并将这些块存储在块管理器中。readBlocks(): Array[BlockData]
:从驱动程序和/或其他执行器获取 torrent 块。readBroadcastBlock(): T
:读取广播块,重建广播对象的值。unpersist(id: Long, removeFromDriver: Boolean, blocking: Boolean): Unit
:从执行器中移除与指定 ID 相关的所有持久化块。
TorrentBroadcast
通过将广播数据分成小块并使用类似 BitTorrent 的协议进行分布式传输,以提高广播性能和可靠性。它允许在集群中高效地广播大量数据,并减少了驱动程序的负载。
/**
* 使用类似 BitTorrent 的协议实现的 [[org.apache.spark.broadcast.Broadcast]]。
*
* 具体机制如下:
*
* 驱动程序将序列化对象分成小块,并将这些块存储在驱动程序的块管理器中。
*
* 在每个执行器上,执行器首先尝试从自己的块管理器获取对象。如果不存在,则使用远程获取从驱动程序和/或其他执行器获取小块。获取到块后,将块放入自己的块管理器中,以便其他执行器可以获取。
*
* 这样可以防止驱动程序成为发送多个副本的广播数据(每个执行器一个)的瓶颈。
*
* 初始化时,TorrentBroadcast 对象会读取 SparkEnv.get.conf。
*
* @param obj 要广播的对象
* @param id 广播变量的唯一标识符
* @param serializedOnly 如果为 true,则不在驱动程序上缓存未序列化的值
*/
private[spark] class TorrentBroadcast[T: ClassTag](obj: T, id: Long, serializedOnly: Bo