使用Kafka-Spark Streaming API处理流数据时出现重复

时间:2019-02-04 19:58:57

标签: java apache-kafka spark-streaming offset kafka-consumer-api

以下代码在数据处理后有效并提交偏移量。 但是问题是,在以下情况下它正在处理重复项:

使用者作业正在运行,并且配置单元表具有0条记录,当前偏移量为(FORMAT- fromOffest,直到Offset,Difference): 512512 0

然后我产生了1000条记录,当它读取34条记录但没有提交时,我将其杀死了 512546 34

我看到这一次,这34个记录已经加载到Hive表中

接下来,我重新启动了应用程序。

我看到它再次读取了34条记录(而不是读取1000-34 = 76 recs),尽管它已经处理了它们并加载到Hive中 512 1512 1000 然后几秒钟后,它会更新。 1512 1512 0 蜂巢现在有(34 + 1000 = 1034)

这将导致表中的记录重复(额外34)。 如代码中所述,我仅在处理/加载到Hive表之后才提交偏移量。

public void method1(SparkConf conf,String app) 
    spark = SparkSession.builder().appName(conf.get("")).enableHiveSupport().getOrCreate();
    final JavaStreamingContext javaStreamContext = new JavaStreamingContext(context,
            new Duration(<spark duration>));
    JavaInputDStream<ConsumerRecord<String, String>> messages = KafkaUtils.createDirectStream(javaStreamContext,
            LocationStrategies.PreferConsistent(),
            ConsumerStrategies.<String, String> Subscribe(<topicnames>, <kafka Params>));

            JavaDStream<String> records = messages.map(new Function<ConsumerRecord<String, String>, String>() {
                @Override
                public String call(ConsumerRecord<String, String> tuple2) throws Exception {
                    return tuple2.value();
                }
            });

            records.foreachRDD(new VoidFunction<JavaRDD<String>>() {
                @Override
                public void call(JavaRDD<String> rdd) throws Exception {
                    if(!rdd.isEmpty()) {
                        methodToSaveDataInHive(rdd, <StructTypeSchema>,<OtherParams>);
                    }
                }
             });

             messages.foreachRDD(new VoidFunction<JavaRDD<ConsumerRecord<String, String>>>() {
              @Override
              public void call(JavaRDD<ConsumerRecord<String, String>> rdd) {
                    OffsetRange[] offsetRanges = ((HasOffsetRanges) rdd.rdd()).offsetRanges();
                    ((CanCommitOffsets) messages.inputDStream()).commitAsync(offsetRanges);                     
                    for (OffsetRange offset : offsetRanges) {
                        System.out.println(offset.fromOffset() + " " + offset.untilOffset()+ "  "+offset.count());
                    }
                     }
              });             
    javaStreamContext.start();
    javaStreamContext.awaitTermination();
}

1 个答案:

答案 0 :(得分:0)

通常来说,在构建Spark Streaming作业时,您不必担心重复项,而应在下游进行处理。别误会,您想要构建应用程序来防止重复,但是当发生灾难性的事情时,您将得到重复,这就是为什么最好稍后再进行管理。

我看到的第一个问题是保存偏移量的位置。您应该在保存数据之后立即保存它们,而不是之后的方法。当records.foreachRDD完成methodToSaveData时,它应进行调用以保存偏移量。您可能需要重新构造映射记录的方式,以便获得偏移量详细信息,但这是最佳选择。

        records.foreachRDD(new VoidFunction<JavaRDD<String>>() {
            @Override
            public void call(JavaRDD<String> rdd) throws Exception {
                if(!rdd.isEmpty()) {
                    methodToSaveDataInHive(rdd, <StructTypeSchema>,<OtherParams>);
                    **{commit offsets here}**
                }
            }
         });

也就是说,将偏移量保存在哪里都没有关系。如果作业在将数据写入配置单元之后并且在提交偏移范围之前被杀死,那么您将重新处理记录。您可以通过某些方法来构建应用程序,使其具有优美的关闭挂钩(Google挂钩),这些挂钩会尝试捕获kill命令并正常关闭它,但同样容易受到应用程序被杀死或崩溃的影响。如果运行执行程序的计算机在保存到配置单元之后但在提交偏移量之前失去了电源,则您有重复项。如果该应用在Linux上被杀死-9(在Linux中),则它不会在乎是否正常关机,并且您会重复运行。