关于java:Apache Spark:如何构造Spark应用程序的代码(尤其是在使用Broadcasts时)

Apache Spark: How to structure code of a Spark Application (especially when using Broadcasts)

我有一个关于Java Spark应用程序中代码结构的通用问题。我想将实现Spark转换的代码与调用RDD分开,因此即使使用很多包含很多代码行的转换,应用程序的源代码也保持清晰。

我先给你一个简短的例子。在这种情况下,flatMap转换的实现作为匿名内部类提供。这是一个简单的应用程序,它读取整数的RDD,然后将每个元素乘以一个整数数组,该数组在以下之前广播到所有工作程序节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void main(String[] args) {

    SparkConf conf = new SparkConf().setMaster("local").setAppName("MyApp");
    JavaSparkContext sc = new JavaSparkContext(conf);

    JavaRDD<Integer> result = sc.parallelize(Arrays.asList(5, 8, 9));

    final Broadcast<int[]> factors = sc.broadcast(new int[] { 1, 2, 3 });

    result = result.flatMap(new FlatMapFunction<Integer, Integer>() {
        public Iterable<Integer> call(Integer t) throws Exception {
            int[] values = factors.value();
            LinkedList<Integer> result = new LinkedList<Integer>();
            for (int value : values) result.add(t * value);
            return result;
        }
    });

    System.out.println(result.collect());   // [5, 10, 15, 8, 16, 24, 9, 18, 27]

    sc.close();
}

为了构造代码,我已经将Spark函数的实现提取到另一个类中。类SparkFunctions提供了flatMap转换的实现,并具有setter方法来获取对广播变量的引用(...在我的实际场景中,该类中将有很多操作都可以访问广播数据) 。

我已经体验到表示Spark转换的方法可以是静态的,只要它不访问Broadcast变量或Accumulator变量即可。为什么?静态方法只能访问静态属性。对Broadcast变量的静态引用始终为null(可能是因为Spark将类SparkFunctions发送到辅助节点时未序列化)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@SuppressWarnings("serial")
public class SparkFunctions implements Serializable {

    private Broadcast<int[]> factors;

    public SparkFunctions() {
    }

    public void setFactors(Broadcast<int[]> factors) {
        this.factors = factors;
    }

    public final FlatMapFunction<Integer, Integer> myFunction = new FlatMapFunction<Integer, Integer>() {
        public Iterable<Integer> call(Integer t) throws Exception {
            int[] values = factors.value();
            LinkedList<Integer> result = new LinkedList<Integer>();
            for (int value : values) result.add(t * value);
            return result;
        }
    };

}

这是使用类SparkFunctions

的应用程序的第二个版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void main(String[] args) {

    SparkConf conf = new SparkConf().setMaster("local").setAppName("MyApp");
    JavaSparkContext sc = new JavaSparkContext(conf);

    JavaRDD<Integer> result = sc.parallelize(Arrays.asList(5, 8, 9));

    final Broadcast<int[]> factors = sc.broadcast(new int[] { 1, 2, 3 });

    // 1) Initializing
    SparkFunctions functions = new SparkFunctions();

    // 2) Pass reference of broadcast variable
    functions.setFactors(factors);

    // 3) Implementation is now in the class SparkFunctions
    result = result.flatMap(functions.myFunction);

    System.out.println(result.collect());   // [5, 10, 15, 8, 16, 24, 9, 18, 27]

    sc.close();
}

应用程序的两个版本都可以正常工作(在本地和群集设置中),但是我问它们是否同样有效。

问题1:在我看来,Spark会序列化包含Broadcast变量的类SparkFunctions并将其发送到辅助节点,以便节点可以在其任务中使用该函数。数据是否两次发送到工作程序节点,首先使用SparkContext进行广播,然后再次使用类SparkFunctions进行序列化?还是每个元素发送一次(广播加1)?

问题2:您能为我提供一些有关如何以其他方式构造源代码的建议吗?

请不要提供如何防止广播的解决方案。我有一个实际的应用程序,它要复杂得多。

我发现的类似问题并没有真正帮助:

  • Spark Java代码结构
  • Spark中的BroadCast变量
  • Spark:将广播变量传递给执行者

在此先感谢您的帮助!


这与Question1

有关

提交Spark作业时,这些作业被分为阶段->任务。这些任务实际上是在工作程序节点上执行转换和操作的执行。驱动程序的sumbitTask()会将有关广播变量的功能和元数据序列化到所有节点。

广播工作原理的剖析。

驱动程序创建一个本地目录来存储要广播的数据,并启动具有对该目录访问权限的HttpServer。调用广播时,数据实际上已写入目录(val bdata = sc.broadcast(data))。同时,该数据还将通过StorageLevel存储磁盘写入驱动程序的blockManger中。块管理器为数据分配一个blockId(类型BroadcastBlockId)。

仅当执行程序反序列化接收到的任务时,才广播实际数据,并且还以广播对象的形式获取广播变量的元数据。然后,它调用元数据对象(bdata变量)的readObject()方法。此方法将首先检查本plot管理器,以查看是否已存在本地副本。否则,将从驱动程序中获取数据。提取数据后,会将其存储在本plot管理器中以备后用。