美文网首页
spark源码阅读-KafkaUtils代码-Direct方式

spark源码阅读-KafkaUtils代码-Direct方式

作者: pcqlegend | 来源:发表于2018-04-04 23:41 被阅读0次

    KafkaUtils 用于创建一个从Kafka Brokers 拉取数据的输入数据流。
    之前有一个文章介绍了sparkstream创建kafka的数据流有两种方式,一种是Receiver 一种是Direct方式。我们先看下Direct方式,具体的区别可以参考我的另一篇文章https://www.jianshu.com/p/88862316c4db
    代码深入:
    KafkaUtils->DirectKafkaInputDStream

    def createDirectStream[
        K: ClassTag,
        V: ClassTag,
        KD <: Decoder[K]: ClassTag,
        VD <: Decoder[V]: ClassTag,
        R: ClassTag] (
          ssc: StreamingContext,
          kafkaParams: Map[String, String],
          fromOffsets: Map[TopicAndPartition, Long],
          messageHandler: MessageAndMetadata[K, V] => R
      ): InputDStream[R] = {
        val cleanedHandler = ssc.sc.clean(messageHandler)
        new DirectKafkaInputDStream[K, V, KD, VD, R](
          ssc, kafkaParams, fromOffsets, cleanedHandler)
      }
    
    

    入参如上所示 ssc,kafkaParams,topics,可以多个topic,storageLevel
    DirectKafkaInputDStream KafkaRDD的stream,并且Kafka的每个topic的每个Partition 与RDD的partition一一对应。
    spark.streaming.kafka.maxRatePerPartition 这个参数决定了每个partition每秒钟接收的最大的消息数量。并且这个Dstream并不负责提交offsets。因此你可以实现exactly-once 语义。
    首先我们要看一下compute方法,也就是负责产生指定时间RDD的方法。这个方法我会在DStream里面提到。

    override def compute(validTime: Time): Option[KafkaRDD[K, V, U, T, R]] = {
        val untilOffsets = clamp(latestLeaderOffsets(maxRetries))//获取各个partition应该获取的offset 也就是当前的offset+maxRatePerPartition 和partiton最新的offset中取最小值。
        val rdd = KafkaRDD[K, V, U, T, R](//根据当前的offset和最新的offset创建一个KafkaRdd
          context.sparkContext, kafkaParams, currentOffsets, untilOffsets, messageHandler)
    
        // Report the record number and metadata of this batch interval to InputInfoTracker.
        val offsetRanges = currentOffsets.map { case (tp, fo) =>
          val uo = untilOffsets(tp)
          OffsetRange(tp.topic, tp.partition, fo, uo.offset)
        }
        val description = offsetRanges.filter { offsetRange =>
          // Don't display empty ranges.
          offsetRange.fromOffset != offsetRange.untilOffset
        }.map { offsetRange =>
          s"topic: ${offsetRange.topic}\tpartition: ${offsetRange.partition}\t" +
            s"offsets: ${offsetRange.fromOffset} to ${offsetRange.untilOffset}"
        }.mkString("\n")
        // Copy offsetRanges to immutable.List to prevent from being modified by the user
        val metadata = Map(
          "offsets" -> offsetRanges.toList,
          StreamInputInfo.METADATA_KEY_DESCRIPTION -> description)
        val inputInfo = StreamInputInfo(id, rdd.count, metadata)
        ssc.scheduler.inputInfoTracker.reportInfo(validTime, inputInfo)
        //更新当前的offsets
        currentOffsets = untilOffsets.map(kv => kv._1 -> kv._2.offset)
        Some(rdd)
      }
    

    然后看下clamp方法 :获取各个partition应该获取的offset: 也就是当前的offset+maxRatePerPartition 和partiton最新的offset中取最小值。

     protected def clamp(
        leaderOffsets: Map[TopicAndPartition, LeaderOffset]): Map[TopicAndPartition, LeaderOffset] = {
        maxMessagesPerPartition.map { mmp =>
          leaderOffsets.map { case (tp, lo) =>
            tp -> lo.copy(offset = Math.min(currentOffsets(tp) + mmp, lo.offset))
          }
        }.getOrElse(leaderOffsets)
      }
    

    再看获取最新offset的方法,入参是获取leaderoffset的重试参数。由于机器宕机等原因,某个partition的leader可能丢失,所以这个时候会有一个isr中的broker,成为该partition的leader。这样consumer就能够连接新的leader了。

     @tailrec
      protected final def latestLeaderOffsets(retries: Int): Map[TopicAndPartition, LeaderOffset] = {
        val o = kc.getLatestLeaderOffsets(currentOffsets.keySet)//获取partition leader的offset
        // Either.fold would confuse @tailrec, do it manually
        if (o.isLeft) {//如果异常
          val err = o.left.get.toString
          if (retries <= 0) {//且重试次数为不大于0 的时候这抛出异常
            throw new SparkException(err)
          } else {//否则重试,并且线程等待 一定时间后,默认是200ms,这个可以通过修改refresh.leader.backoff.ms 参数修改
            log.error(err)
            Thread.sleep(kc.config.refreshLeaderBackoffMs)
            latestLeaderOffsets(retries - 1)
          }
        } else {
          o.right.get
        }
      }
    

    这建议大家可以将重试次数设置成3,并且超时时间设置成3000。并且做好Job的运行状态检查,如果发现job异常退出的时候,可以自动重启Job。
    KafkaRDD的创建参考KafkaRDD
    https://www.jianshu.com/p/0b0767393d63

    相关文章

      网友评论

          本文标题:spark源码阅读-KafkaUtils代码-Direct方式

          本文链接:https://www.haomeiwen.com/subject/zftrhftx.html