Spark Streaming:为什么内部处理成本如此之高以处理几MB的用户状态?

时间:2015-09-10 12:15:18

标签: java performance apache-spark spark-streaming

根据我们的实验,我们发现,当状态变为超过一百万个对象时,有状态的Spark Streaming内部处理成本会花费大量时间。因此延迟会受到影响,因为我们必须增加批处理间隔以避免不稳定的行为(处理时间>批处理间隔)。

它与我们的应用程序的细节无关,因为它可以通过以下代码复制。

那些花费大量时间来处理用户状态的Spark内部处理/基础架构成本究竟是什么?除了简单地增加批处理间隔之外,还有其他方法可以减少处理时间吗?

我们计划广泛使用状态:在每个节点上至少100MB左右,以便将所有数据保存在内存中,并且只在一小时内转储一次。

增加批处理间隔会有所帮助,但我们希望将批处理间隔保持在最小。

原因可能不是状态占用的空间,而是大对象图,因为当我们将列表更改为大型基元时,问题就消失了。

只是一个猜测:它可能与Spark内部使用的org.apache.spark.util.SizeEstimator有关,因为它会在不时进行分析时显示。

enter image description here

以下是在现代iCore7上重现上述图片的简单演示:

  • 小于15 MB的州
  • 根本没有输入流
  • 最快可能(虚拟)' updateStateByKey'功能
  • 批处理间隔1秒
  • 检查点(Spark必须具备)到本地磁盘
  • 在本地和YARN进行了测试

代码:

package spark;

import org.apache.commons.lang3.RandomStringUtils;
import org.apache.spark.HashPartitioner;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.streaming.Durations;
import org.apache.spark.streaming.api.java.JavaPairDStream;
import org.apache.spark.streaming.api.java.JavaStreamingContext;
import org.apache.spark.util.SizeEstimator;
import scala.Tuple2;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

public class SlowSparkStreamingUpdateStateDemo {

    // Very simple state model
    static class State implements Serializable {
        final List<String> data;
        State(List<String> data) {
            this.data = data;
        }
    }

    public static void main(String[] args) {
        SparkConf conf = new SparkConf()
                // Tried KryoSerializer, but it does not seem to help much
                //.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
                .setMaster("local[*]")
                .setAppName(SlowSparkStreamingUpdateStateDemo.class.getName());

        JavaStreamingContext javaStreamingContext = new JavaStreamingContext(conf, Durations.seconds(1));
        javaStreamingContext.checkpoint("checkpoint"); // a must (if you have stateful operation)

        List<Tuple2<String, State>> initialRddGeneratedData = prepareInitialRddData();
        System.out.println("Estimated size, bytes: " + SizeEstimator.estimate(initialRddGeneratedData));
        JavaPairRDD<String, State> initialRdd = javaStreamingContext.sparkContext().parallelizePairs(initialRddGeneratedData);

        JavaPairDStream<String, State> stream = javaStreamingContext
                .textFileStream(".") // fake: effectively, no input at all
                .mapToPair(input -> (Tuple2<String, State>) null) //  fake to get JavaPairDStream
                .updateStateByKey(
                        (inputs, maybeState) -> maybeState, // simplest possible dummy function
                        new HashPartitioner(javaStreamingContext.sparkContext().defaultParallelism()),
                        initialRdd); // set generated state

        stream.foreachRDD(rdd -> { // simplest possible action (required by Spark)
            System.out.println("Is empty: " + rdd.isEmpty());
            return null;
        });

        javaStreamingContext.start();
        javaStreamingContext.awaitTermination();
    }

    private static List<Tuple2<String, State>> prepareInitialRddData() {
        // 'stateCount' tuples with value = list of size 'dataListSize' of strings of length 'elementDataSize'
        int stateCount = 1000;
        int dataListSize = 200;
        int elementDataSize = 10;
        List<Tuple2<String, State>> initialRddInput = new ArrayList<>(stateCount);
        for (int stateIdx = 0; stateIdx < stateCount; stateIdx++) {
            List<String> stateData = new ArrayList<>(dataListSize);
            for (int dataIdx = 0; dataIdx < dataListSize; dataIdx++) {
                stateData.add(RandomStringUtils.randomAlphanumeric(elementDataSize));
            }
            initialRddInput.add(new Tuple2<>("state" + stateIdx, new State(stateData)));
        }
        return initialRddInput;
    }

}

1 个答案:

答案 0 :(得分:2)

火花1.6的国家管理有所改善 请参考SPARK-2629改进的Spark Streaming状态管理;

在详细设计规范中:
Improved state management in Spark Streaming

以下提到了一个性能缺陷:

需要更多优化的状态管理,不会扫描每个密钥 当前updateStateByKey扫描每个批处理间隔中的每个键,即使该键没有数据也是如此。虽然这种语义对某些工作负载很有用,但大多数工作负载只需要“扫描并更新有新数据的状态”。并且在每个批次间隔中只需要触及所有状态的一小部分。 The cogroup-based implementation of updateStateByKey is not designed for this; cogroup scans all the keys every time. In fact, this causes the batch processing times of updateStateByKey to increase with the number of keys in the state, even if the data rate stays fixed. enter image description here