spark streaming 广播变量空指针异常问题&广播变量更新

本文深入探讨Spark广播变量在集群环境下的正确使用方法,解决空指针异常问题,并介绍如何实现实时更新。通过实例代码展示如何避免Driver端与Executor端的数据不同步,确保广播变量在Spark Streaming中的稳定运行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这两天在使用spark中的用到了广播变量,大致逻辑是从Redis中读取黑名单配置,然后广播到各个节点用于异常监控,但是在使用过程中总是报空指针异常,后面百度了很多资料,发现有说Yarn集群中不支持广播变量的,有说Sparkstreaming不支持广播变量更新的,有说是spark闭包问题的等等各种,最后笔者去查了sparkstreaming官方文档才学会了广播变量的正确使用方法,并将过程记录下来。

先上一版报空指针的异常代码(简化版)

public class TetsBroadcast {

    private static volatile Broadcast<List<String>> broadcast = null;//定义广播变量

    public static void main(String[] args) throws Exception {


        /**初始化SparkContext**/
        String brokers = ParmUtil.getBrokers();
        String topics = "test";
        SparkConf conf = new SparkConf()
                .setAppName("test-broadcast")
                .set("spark.shuffle.blockTransferService", "nio");
        JavaSparkContext sc = new JavaSparkContext(conf);
        JavaStreamingContext jssc = new JavaStreamingContext(sc, new Duration(60000));

        /**初始化广播变量**/
        String[] arr = new String[3];
        //查Redis获取配置信息写到数组arr...
        broadcast = sc.broadcast(Arrays.asList(arr));

        /**读Kafka数据**/
        Set<String> topicsSet = new HashSet<>(Arrays.asList(topics.split(",")));
        Map<String, Object> kafkaParams = new HashMap<>();
        kafkaParams.put("metadata.broker.list", brokers);
        kafkaParams.put("group.id", "test");
        JavaInputDStream<ConsumerRecord<String, String>> messages = KafkaUtils.createDirectStream(
                jssc,
                LocationStrategies.PreferConsistent(),
                ConsumerStrategies.Subscribe(topicsSet, kafkaParams));
        JavaDStream<String> lines = messages.map(ConsumerRecord::value);

        /**使用广播变量**/
        lines.foreachRDD(new VoidFunction<JavaRDD<String>>() {
            @Override
            public void call(JavaRDD<String> stringJavaRDD) throws Exception {
                System.out.println("广播变量值:"+broadcast.value());
                JavaRDD<String> alertRdd = stringJavaRDD.filter(new Function<String, Boolean>() {
                    @Override
                    public Boolean call(String s) throws Exception {
                        String[] arg = s.split("\\u0001");
                        String ip = arg[2];
                        if (!broadcast.value().contains(ip))
                            return false;
                        return true;
                    }
                });
                alertRdd.take(10);
            }
        });

        jssc.start();
        jssc.awaitTermination();
    }
}

如上,在类中定义了广播变量,并在main方法中进行了值得初始化广播,在foreachRDD中使用广播变量,但是在yarn集群中运行总会在if (!broadcast.value().contains(ip)) 这一行报空指针异常,且奇怪的是在System.out.println(“广播变量值:”+broadcast.value()) 这一行是可以正常打印出来广播变量的值的,以上说明了广播变量只在Driver端有效,而filter算子是在Excutor端指定并未拿到初始化的广播变量值。

然后在StackOverflow中找到这么一段解释

This is because your broadcast variable is in class level. And since when the class is initialized in the worker node it will not see the value you assigned in the main method. It will only see a null since the broadcast variable is not initialized to anything. The Solution i found was to pass the broadcast variable to the method when calling the method. This is also the case for Accumulators

大致意思是说,广播变量是在类中定义,在main函数在driver端初始化赋值的时候work节点并不能看到复制后的广播变量,只能看到在类中定义的null,也就报了我们上述代码运行时的异常。

然后为了彻底搞明白广播变量到底怎么用、怎么更新,特去翻看了spark streaming的官方文档,照着官方文档的写法更新了自己的代码,实现无空指针异常,并在spark streaming中更新广播变量,代码如下

public class TetsBroadcast {

    public static void main(String[] args) throws Exception {
        /**初始化SparkContext**/
        String brokers = ParmUtil.getBrokers();
        String topics = "test";
        SparkConf conf = new SparkConf()
                .setAppName("test-broadcast")
                .set("spark.shuffle.blockTransferService", "nio");
        JavaSparkContext sc = new JavaSparkContext(conf);
        JavaStreamingContext jssc = new JavaStreamingContext(sc, new Duration(60000));

        /**读Kafka数据**/
        Set<String> topicsSet = new HashSet<>(Arrays.asList(topics.split(",")));
        Map<String, Object> kafkaParams = new HashMap<>();
        kafkaParams.put("metadata.broker.list", brokers);
        kafkaParams.put("group.id", "test");
        JavaInputDStream<ConsumerRecord<String, String>> messages = KafkaUtils.createDirectStream(
                jssc,
                LocationStrategies.PreferConsistent(),
                ConsumerStrategies.Subscribe(topicsSet, kafkaParams));
        JavaDStream<String> lines = messages.map(ConsumerRecord::value);

        /**调用广播变量初始化方法使用广播变量**/
        lines.foreachRDD(new VoidFunction<JavaRDD<String>>() {
            @Override
            public void call(JavaRDD<String> stringJavaRDD) throws Exception {
                Broadcast<List<String>> broadcast = IpList.getInstance(new JavaSparkContext(stringJavaRDD.context()));//获取广播变量
                JavaRDD<String> alertRdd = stringJavaRDD.filter(new Function<String, Boolean>() {
                    @Override
                    public Boolean call(String s) throws Exception {
                        String[] arg = s.split("\\u0001");
                        String ip = arg[2];
                        if (!broadcast.value().contains(ip))
                            return false;
                        return true;
                    }
                });
                alertRdd.take(10);
            }
        });

        jssc.start();
        jssc.awaitTermination();
    }
}

class IpList {
    private static volatile Broadcast<List<String>> instance = null;

    /**
     * 实现初始化并更新广播变量
     **/
    public static Broadcast<List<String>> getInstance(JavaSparkContext jsc) {
        String[] arr = new String[3];
        //查Redis获取配置信息写到数组arr...
        if (instance == null) {
            synchronized (IpList.class) {
                instance = jsc.broadcast(Arrays.asList(arr));

            }
        } else {
            synchronized (IpList.class) {
                instance.unpersist();
                instance = jsc.broadcast(Arrays.asList(arr));
            }
        }
        return instance;
    }
}

如上,在方法中初始化变量,在driver端拿到广播变量并在excutor端使用,便可实现广播变量的正常使用,避免空指针异常,且每次调用前会先unpersist释放再获取最新的配置值重新广播,实现了广播变量的动态更新。

### 回答1: 动态广播变量Spark Streaming中非常有用的功能。它可以让我们在流处理过程中动态地更新广播变量的值,从而提高程序的性能和灵活性。 在Spark Streaming中,我们可以使用SparkContext的broadcast方法来创建广播变量。然后,我们可以在DStream的foreachRDD方法中使用广播变量来进行一些计算。 当我们需要动态地更新广播变量的值时,我们可以使用Spark Streaming的transform方法。这个方法可以让我们在DStream中使用任意的RDD转换操作,包括更新广播变量的值。 例如,我们可以使用transform方法来读取一个外部的配置文件,并将其转换为一个广播变量。然后,我们可以在DStream的foreachRDD方法中使用这个广播变量来进行一些计算。当配置文件发生变化时,我们可以重新读取它,并使用transform方法来更新广播变量的值。 总之,动态广播变量Spark Streaming中非常有用的功能,可以帮助我们提高程序的性能和灵活性。 ### 回答2: Spark Streaming中的动态广播变量允许我们将一个可变的变量发送到Spark集群的每个节点上,并在每个节点上更新它。这使得我们能够在流数据处理过程中共享和更新全局状态。 动态广播变量的使用步骤如下: 1. 创建一个广播变量:使用SparkContext的broadcast方法将一个可变的变量广播到整个集群。例如,可以将一个关键字列表广播Spark Streaming的每个节点上。 2. 在转换操作中使用广播变量:在Spark Streaming的转换操作中可以通过使用广播变量的value属性来访问广播变量的值。例如,在DStream的foreachRDD操作中可以访问广播变量并执行与广播变量相关的计算。 3. 更新广播变量:通过在driver程序中修改广播变量的值,然后使用新值再次调用广播方法来更新广播变量的内容。这样,新值将在下一次广播时传播到集群的每个节点。 使用动态广播变量的好处是可以将一些全局状态共享到整个Spark Streaming应用程序中,而无需将其传递给每个节点。这样可以减少网络传输的开销,并提高应用程序的性能。 总结起来,动态广播变量Spark Streaming中管理全局状态的一个强大工具。它可以实现在流数据处理过程中对全局状态进行共享和更新,从而提高应用程序的性能和效率。 ### 回答3: Spark Streaming中的动态广播变量是一种在Spark Streaming作业中共享变量的机制。它可以用于将某个变量广播给所有的工作节点,这样每个节点都可以在本地访问该变量而不需要通过网络传输。动态广播变量在一些需要频繁更新的场景中特别有用。 在Spark Streaming中,要使用动态广播变量,需要首先创建一个Broadcast变量,并通过前端驱动程序将其广播到所有工作节点。然后,在每个工作节点的任务中,可以直接引用该变量而不需要序列化和传输。 动态广播变量的使用步骤如下: 1. 在Spark Streaming应用程序的驱动程序中,通过创建一个共享的变量Broadcast来定义需要广播变量。 2. 使用Spark Streaming的dstream.foreachRDD方法迭代每一个RDD。 3. 在每一个RDD的foreachPartition方法内,通过调用Broadcast.value方法访问广播变量。 这样,每个工作节点都可以在本地获取广播变量,而无需将变量从驱动程序传输到工作节点。 动态广播变量Spark Streaming中的应用场景非常广泛,例如在进行实时机器学习或实时数据分析时,可以使用动态广播变量来保存模型参数或预定义的规则等,以便在每个工作节点上进行使用,提高计算的效率和性能。 总的来说,Spark Streaming中动态广播变量的使用可以帮助我们在作业中共享变量,并且在处理实时数据时提高作业的效率和性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值