最近有很多同学来问我这个问题,说我的代码啥也没改呀,昨天晚上还运行的好好的,第二天早上再运行就报错了,org.apache.spark.rdd.MapPartitionsRDD cannot be cast to org.apache.spark.streaming.kafka010.HasOffsetRanges,怎么都运行不了,这个错相信大家都非常的熟悉,就是一个类型转换异常,从报错上看呢,说是MapPartitionsRD不能转换成HasOffsetRanges,这个错在什么情况下才会出现呢?,先看下面的代码(只粘贴了一部分)
val word = kafkaStreams.map(_.value()).flatMap(_.split(" ")).map((_,1))
word.foreachRDD(rdd => {
if(!rdd.isEmpty()) {
val offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
其实这个错发生在获取偏移量的时候,在spark中获取偏移量只用rdd.asInstanceOf[HasOffsetRanges].offsetRanges这一句话就可以了,简单说一下就是先把rdd强制类型转换成HasOffsetRanges,然后调用HasOffsetRanges里面的offsetRanges这个方法返回一个Array[OffsetRange]数组.下面带大家分析一下源码,就理解了为什么这个地方会报错.
首先这个地方的rdd是一个KafkaRDD,然后我们来看一下KafkaRDD的源码如下:
private[spark] class KafkaRDD[K, V](
sc: SparkContext,
val kafkaParams: ju.Map[String, Object],
val offsetRanges: Array[OffsetRange],
val preferredHosts: ju.Map[TopicPartition, String],
useConsumerCache: Boolean
) extends RDD[ConsumerRecord[K, V]](sc, Nil) with Logging with HasOffsetRanges {
可以看到呢KafkaRDD继承了(RDD[ConsumerRecord[K, V]](sc, Nil) with Logging with HasOffsetRanges),拥有了他们两个的特性,这里的RDD里面是ConsumerRecord[K,V]类型,然后我们在看一下ConsumerRecord的源码如下:
private final String topic;
private final int partition;
private final long offset;
private final long timestamp;
private final TimestampType timestampType;
private final long checksum;
private final int serializedKeySize;
private final int serializedValueSize;
private final K key;
private final V value;
可以看到这里面有topic,partition,offest,timestamp,key,value,等属性,这个value就是我们写入kafka的数据.然后我们看一下HasOffsetRanges这个类,进去会发现这是一个接口,它带有返回OffsetRange数组的单个方法,他的实现类就是KafkaRDD,允许你在每个分区的基础上获取主题和偏移量信息,如下所示:
trait HasOffsetRanges {
def offsetRanges: Array[OffsetRange]
}
KafkaRDD实现了RDD里面的一个方法叫getPartitions,如下所示:
override def getPartitions: Array[Partition] = {
offsetRanges.zipWithIndex.map { case (o, i) =>
new KafkaRDDPartition(i, o.topic, o.partition, o.fromOffset, o.untilOffset)
}.toArray
}
getPartitions方法使用数组中的每个OffsetRange,这里也意味着kafka的分区和RDD的分区是一对一的关系.
我们在回头看下上面的代码,之所以报错是因为这里需要的是RDD[ConsumerRecord[K,V]]类型,因为offest的信息都在这里面,但是上面的代码已经转换成RDD[(String,Int)]类型,这个里面没有offest的信息,所以也转换不成HasOffsetRanges这个类型,就报错了.看到这里大家应该明白了吧.
如果有写的不对的地方,欢迎大家指正,如果有什么疑问,可以加QQ群:340297350,谢谢