我有一个用Java编写并使用Spark 2.1的Spark流应用程序。我正在使用KafkaUtils.createDirectStream
来阅读来自Kafka的消息。我正在使用kryo编码器/解码器用于kafka消息。我在Kafka属性中指定了这一点 - > key.deserializer,value.deserializer,key.serializer,value.deserializer
当Spark以微批方式提取消息时,使用kryo解码器成功解码消息。但是我注意到Spark执行器创建了一个kryo解码器的新实例,用于解码从kafka读取的每条消息。我通过将日志放入解码器构造函数中来检查这一点
这对我来说似乎很奇怪。不应该为每个消息和每个批次使用相同的解码器实例吗?
我正在从卡夫卡读书的代码:
JavaInputDStream<ConsumerRecord<String, Class1>> consumerRecords = KafkaUtils.createDirectStream(
jssc,
LocationStrategies.PreferConsistent(),
ConsumerStrategies.<String, Class1>Subscribe(topics, kafkaParams));
JavaPairDStream<String, Class1> converted = consumerRecords.mapToPair(consRecord -> {
return new Tuple2<String, Class1>(consRecord.key(), consRecord.value());
});
答案 0 :(得分:3)
如果我们想看看Spark如何在内部从Kafka获取数据,我们需要查看KafkaRDD.compute
,这是为每个RDD
实现的方法,它告诉框架如何,好吧,计算RDD
:
override def compute(thePart: Partition, context: TaskContext): Iterator[R] = {
val part = thePart.asInstanceOf[KafkaRDDPartition]
assert(part.fromOffset <= part.untilOffset, errBeginAfterEnd(part))
if (part.fromOffset == part.untilOffset) {
logInfo(s"Beginning offset ${part.fromOffset} is the same as ending offset " +
s"skipping ${part.topic} ${part.partition}")
Iterator.empty
} else {
new KafkaRDDIterator(part, context)
}
}
这里重要的是else
子句,它创建了KafkaRDDIterator
。内部有:
val keyDecoder = classTag[U].runtimeClass.getConstructor(classOf[VerifiableProperties])
.newInstance(kc.config.props)
.asInstanceOf[Decoder[K]]
val valueDecoder = classTag[T].runtimeClass.getConstructor(classOf[VerifiableProperties])
.newInstance(kc.config.props)
.asInstanceOf[Decoder[V]]
如您所见,对于每个底层分区,通过反射创建密钥解码器和值解码器的实例。这意味着它不会生成每条消息,而是每个Kafka分区。
为什么这样实现?我不知道。我假设因为与Spark中发生的所有其他分配相比,键和值解码器应该具有可忽略的性能影响。
如果您已对自己的应用进行了分析并发现这是一个分配热门途径,则可能会出现问题。否则,我不担心。