Spark MapwithState stateSnapshots不缩放(Java)

时间:2017-08-16 09:09:02

标签: java apache-spark apache-kafka spark-streaming

我正在使用spark从Kafka Stream接收数据,以接收有关IOT设备的状态,这些设备正在发送定期健康更新以及设备中存在的各种传感器的状态。 My Spark应用程序侦听单个主题以使用Spark直接流从Kafka流接收更新消息。我需要根据每个设备的传感器状态触发不同的警报。然而,当我添加更多使用Kakfa将数据发送到spark的IOT设备时,尽管添加了更多的机器并且执行器数量增加,但Spark不会扩展。下面我给出了我的Spark应用程序的条带化版本,其中通知触发部分被移除,具有相同的性能问题。

   // Method for update the Device state , it just a in memory object which tracks the device state  .
private static Optional<DeviceState> trackDeviceState(Time time, String key, Optional<ProtoBufEventUpdate> updateOpt,
            State<DeviceState> state) {
            int batchTime = toSeconds(time);
            ProtoBufEventUpdate eventUpdate = (updateOpt == null)?null:updateOpt.orNull();
            if(eventUpdate!=null)
                eventUpdate.setBatchTime(ProximityUtil.toSeconds(time));
            if (state!=null && state.exists()) {
                DeviceState deviceState = state.get();
                if (state.isTimingOut()) {
                    deviceState.markEnd(batchTime);
                }
                if (updateOpt.isPresent()) {
                        deviceState = DeviceState.updatedDeviceState(deviceState, eventUpdate);
                        state.update(deviceState);
                }
            } else if (updateOpt.isPresent()) {
                DeviceState deviceState = DeviceState.newDeviceState(eventUpdate);
                state.update(deviceState);              
                return Optional.of(deviceState);
            } 

        return Optional.absent();
}
    SparkConf conf = new SparkConf()
    .set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
    .set("spark.streaming.receiver.writeAheadLog.enable", "true")
    .set("spark.rpc.netty.dispatcher.numThreads", String.valueOf(Runtime.getRuntime().availableProcessors()))
     JavaStreamingContext context= new JavaStreamingContext(conf, Durations.seconds(10));
Map<String, String> kafkaParams = new HashMap<String, String>();
        kafkaParams.put( “zookeeper.connect”, “192.168.60.20:2181,192.168.60.21:2181,192.168.60.22:2181”);
        kafkaParams.put("metadata.broker.list", “192.168.60.20:9092,192.168.60.21:9092,192.168.60.22:9092”);
        kafkaParams.put(“group.id”, “spark_iot”);
        HashSet<String> topics=new HashSet<>();
        topics.add(“iottopic”);

JavaPairInputDStream<String, ProtoBufEventUpdate> inputStream = KafkaUtils.
            createDirectStream(context, String.class, ProtoBufEventUpdate.class,  KafkaKryoCodec.class, ProtoBufEventUpdateCodec.class, kafkaParams, topics);

JavaPairDStream<String, ProtoBufEventUpdate> updatesStream = inputStream.mapPartitionsToPair(t -> {
            List<Tuple2<String, ProtoBufEventUpdate>> eventupdateList=new ArrayList<>();
            t.forEachRemaining(tuple->{
                    String key=tuple._1;
                    ProtoBufEventUpdate eventUpdate =tuple._2;                  
                    Util.mergeStateFromStats(eventUpdate);
                    eventupdateList.add(new Tuple2<String, ProtoBufEventUpdate>(key,eventUpdate));

            });
            return eventupdateList.iterator();
});

JavaMapWithStateDStream<String, ProtoBufEventUpdate, DeviceState, DeviceState> devceMapStream = null;

devceMapStream=updatesStream.mapWithState(StateSpec.function(Engine::trackDeviceState)
                             .numPartitions(20)
                             .timeout(Durations.seconds(1800)));
devceMapStream.checkpoint(new Duration(batchDuration*1000));


JavaPairDStream<String, DeviceState> deviceStateStream = devceMapStream
                .stateSnapshots()
                .cache();

deviceStateStream.foreachRDD(rdd->{
                if(rdd != null && !rdd.isEmpty()){
                    rdd.foreachPartition(tuple->{
                    tuple.forEachRemaining(t->{
                        SparkExecutorLog.error("Engine::getUpdates Tuple data  "+ t._2);
                    });
                });
                }
});

即使负载增加,我也看不到Executor实例的CPU使用率增加。大多数时候Executor实例CPU都处于空闲状态。我尝试增加kakfa分区(目前Kafka有72个分区。我确实尝试将其降低到36分区)。我还尝试增加devceMapStream分区。但我看不到任何性能改进。代码不会花费任何时间在IO上。

我正在运行我们的Spark Appication,在Amazon EMR(Yarn)上有6个执行器实例,每台机器有4个内核和32 GB Ram。它试图将执行程序实例的数量增加到9然后增加到15,但没有看到任何性能改进。还通过设置20,36,72,100在spark.default.parallelism值上玩了一下,但我可以看到20是给我更好性能的那个(也许每个执行器的核心数对此有一些影响)。

spark-submit --deploy-mode cluster --class com.ajay.Engine --supervise --driver-memory 5G --driver-cores 8 --executor-memory 4G --executor-cores 4 --conf spark.default.parallelism=20 --num-executors 36 --conf spark.dynamicAllocation.enabled=false --conf spark.streaming.unpersist=false --conf spark.eventLog.enabled=false --conf spark.driver.extraJavaOptions=-Dlog4j.configuration=log4j.properties --conf spark.executor.extraJavaOptions=-XX:+HeapDumpOnOutOfMemoryError --conf spark.executor.extraJavaOptions=-XX:HeapDumpPath=/tmp --conf spark.executor.extraJavaOptions=-XX:+UseG1GC --conf spark.driver.extraJavaOptions=-XX:+UseG1GC --conf spark.executor.extraJavaOptions=-Dlog4j.configuration=log4j.properties s3://test/engine.jar

目前Spark正在努力在10秒内完成处理(我甚至尝试过不同的批次持续时间,如5,10,15等)。它需要15-23秒才能完成一批,输入速率为每秒1600条记录,每批次有17000条记录。我需要使用statesteam定期检查设备的状态,以查看设备是否发出任何警报或任何传感器已停止响应。我不确定如何改善我的火花应用的性能?

1 个答案:

答案 0 :(得分:1)

mapWithState执行以下操作:

  

将函数应用于此流的每个键值元素,同时为每个唯一键维护一些状态数据

根据其文档:PairDStreamFunctions#mapWithState

这也意味着对于每个批次,具有相同键的所有元素都按顺序处理 ,并且因为StateSpec中的函数是任意的并由我们提供,没有状态无论您如何在mapWithState之前对数据进行分区,都无法进行任何进一步的并行化。即当密钥多样化时,并行化会很好,但是如果所有RDD元素中只有几个唯一的密钥,那么整个批处理将主要由核心数量等于唯一密钥的数量来处理。

在你的情况下,钥匙来自卡夫卡:

            t.forEachRemaining(tuple->{
                String key=tuple._1;

并且您的代码段未显示它们是如何生成的。

根据我的经验,这可能是正在发生的事情:您的批次的某些部分正在由多个核心快速处理,而另一部分,在整个部分中具有相同的密钥,需要更多时间并延迟批处理,这就是为什么你看到只有少数任务在大部分时间运行,而有不足的执行器。

要查看是否属实,请检查您的密钥分配,每个密钥有多少个元素,是否只有几个密钥占所有元素的20%?如果是这样,您有以下选择:

  • 更改密钥生成算法
  • mapWithState之前人工拆分有问题的密钥,并在以后合并状态快照以使整个
  • 有意义
  • 限制每个批次中要处理的相同键的元素数量,或者忽略每个批次中第一个N之后的元素,或者将它们发送到其他地方,进入一些“无法及时处理”的Kafka流并处理它们分别