目录
7.2 广播变量
7.2.1 广播变量的作用
7.2.2 广播变量的API
7.2.2.1 使用广播变量的一般套路
7.2.2.2 使用 value 方法的注意点
7.2.2.3 使用 destroy 方法的注意点
7.2.3 广播变量的使用场景
7.2.4 扩展
7.2 广播变量
目标
- 理解为什么需要广播变量, 以及其应用场景
- 能够通过代码使用广播变量
7.2.1 广播变量的作用
广播变量允许开发者将一个 Read-Only
的变量缓存到集群中每个节点中, 而不是传递给每一个 Task 一个副本.
- 集群中每个节点, 指的是一个机器
- 每一个 Task, 一个 Task 是一个 Stage 中的最小处理单元, 一个 Executor 中可以有多个 Stage, 每个 Stage 有多个 Task
所以在需要跨多个 Stage 的多个 Task 中使用相同数据的情况下, 广播特别的有用
(同一个exector只需要一份缓存变量即可,不需要向每一个task传输)
7.2.2 广播变量的API
方法名 | 描述 |
| 唯一标识 |
| 广播变量的值 |
| 在 Executor 中异步的删除缓存副本 |
| 销毁所有此广播变量所关联的数据和元数据 |
| 字符串表示 |
7.2.2.1 使用广播变量的一般套路
可以通过如下方式创建广播变量
val b = sc.broadcast(1)
如果 Log 级别为 DEBUG 的时候, 会打印如下信息
DEBUG BlockManager: Put block broadcast_0 locally took 430 ms
DEBUG BlockManager: Putting block broadcast_0 without replication took 431 ms
DEBUG BlockManager: Told master about block broadcast_0_piece0
DEBUG BlockManager: Put block broadcast_0_piece0 locally took 4 ms
DEBUG BlockManager: Putting block broadcast_0_piece0 without replication took 4 ms
创建后可以使用 value
获取数据
b.value
获取数据的时候会打印如下信息
DEBUG BlockManager: Getting local block broadcast_0
DEBUG BlockManager: Level for block broadcast_0 is StorageLevel(disk, memory, deserialized, 1 replicas)
广播变量使用完了以后, 可以使用 unpersist
删除数据
b.unpersist
删除数据以后, 可以使用 destroy
销毁变量, 释放内存空间
b.destroy
销毁以后, 会打印如下信息
DEBUG BlockManager: Removing broadcast 0
DEBUG BlockManager: Removing block broadcast_0_piece0
DEBUG BlockManager: Told master about block broadcast_0_piece0
DEBUG BlockManager: Removing block broadcast_0
7.2.2.2 使用 value
方法的注意点
方法签名 value: T
在 value
方法内部会确保使用获取数据的时候, 变量必须是可用状态, 所以必须在变量被 destroy
之前使用 value
方法, 如果使用 value
时变量已经失效, 则会爆出以下错误
org.apache.spark.SparkException: Attempted to use Broadcast(0) after it was destroyed (destroy at <console>:27)
at org.apache.spark.broadcast.Broadcast.assertValid(Broadcast.scala:144)
at org.apache.spark.broadcast.Broadcast.value(Broadcast.scala:69)
... 48 elided
7.2.2.3 使用 destroy
方法的注意点
方法签名 destroy(): Unit
destroy
方法会移除广播变量, 彻底销毁掉, 但是如果你试图多次 destroy
广播变量, 则会爆出以下错误
org.apache.spark.SparkException: Attempted to use Broadcast(0) after it was destroyed (destroy at <console>:27)
at org.apache.spark.broadcast.Broadcast.assertValid(Broadcast.scala:144)
at org.apache.spark.broadcast.Broadcast.destroy(Broadcast.scala:107)
at org.apache.spark.broadcast.Broadcast.destroy(Broadcast.scala:98)
... 48 elided
7.2.3 广播变量的使用场景
假设我们在某个算子中需要使用一个保存了项目和项目的网址关系的 Map[String, String]
静态集合, 如下
val pws = Map("Apache Spark" -> "http://spark.apache.org/", "Scala" -> "http://www.scala-lang.org/")
val websites = sc.parallelize(Seq("Apache Spark", "Scala")).map(pws).collect
上面这段代码是没有问题的, 可以正常运行的, 但是非常的低效, 因为虽然可能 pws
已经存在于某个 Executor
中了, 但是在需要的时候还是会继续发往这个 Executor
, 如果想要优化这段代码, 则需要尽可能的降低网络开销
可以使用广播变量进行优化, 因为广播变量会缓存在集群中的机器中, 比 Executor
在逻辑上更 "大"
val pwsB = sc.broadcast(pws)
val websites = sc.parallelize(Seq("Apache Spark", "Scala")).map(pwsB.value).collect
上面两段代码所做的事情其实是一样的, 但是当需要运行多个 Executor
(以及多个 Task
) 的时候, 后者的效率更高
7.2.4 扩展
正常情况下使用 Task 拉取数据的时候, 会将数据拷贝到 Executor 中多次, 但是使用广播变量的时候只会复制一份数据到 Executor 中, 所以在两种情况下特别适合使用广播变量
- 一个 Executor 中有多个 Task 的时候
- 一个变量比较大的时候
而且在 Spark 中还有一个约定俗称的做法, 当一个 RDD 很大并且还需要和另外一个 RDD 执行 join
的时候, 可以将较小的 RDD 广播出去, 然后使用大的 RDD 在算子 map
中直接 join
, 从而实现在 Map 端 join
val acMap = sc.broadcast(myRDD.map { case (a,b,c,b) => (a, c) }.collectAsMap)
val otherMap = sc.broadcast(myOtherRDD.collectAsMap)
myBigRDD.map { case (a, b, c, d) =>
(acMap.value.get(a).get, otherMap.value.get(c).get)
}.collect
一般情况下在这种场景下, 会广播 Map 类型的数据, 而不是数组, 因为这样容易使用 Key 找到对应的 Value 简化使用
总结
- 广播变量用于将变量缓存在集群中的机器中, 避免机器内的 Executors 多次使用网络拉取数据
- 广播变量的使用步骤: (1) 创建 (2) 在 Task 中获取值 (3) 销毁