DStream-03 Kafka offset 原理和源码

DEMO

object KafkaDirectDstream {
  def main(args: Array[String]): Unit = {
    val sparkConf = new SparkConf().setAppName("KafkaDirectDstream")
    sparkConf.setMaster("local[*]")
    sparkConf.set("spark.streaming.kafka.maxRatePerPartition", "1")
    sparkConf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
    val streamingContext = new StreamingContext(sparkConf, Seconds(2))
    streamingContext.sparkContext.setLogLevel("ERROR")
    val kafkaParams = Map[String, Object](
      "bootstrap.servers" -> "s1:9092",
      "key.deserializer" -> classOf[StringDeserializer],
      "value.deserializer" -> classOf[StringDeserializer],
      "group.id" -> "p1",
      "auto.offset.reset" -> "earliest",
      "enable.auto.commit" -> (false: java.lang.Boolean)
    )
    val topics = Array("test_topic")
    // 这边就是入口
    val dstream = KafkaUtils.createDirectStream[String, String](
      streamingContext,
      PreferConsistent,
      Subscribe[String, String](topics, kafkaParams)
    )

    dstream.map(record => (record.key, record.value, record.partition(), record.offset()))
      .foreachRDD(rdd => {
        println(s" Time: ${System.currentTimeMillis() / 1000}")
        
        rdd.mapPartitionsWithIndex((p, it) => {
          println(s" partition:${p} count:${it.count(x=>true)}")
          it
        }).foreachPartition(v=>null)

      })
    streamingContext.start()
    streamingContext.awaitTermination()
  }
}

KafkaUtils

如果创建DirectKafkaInputDStream 时如果没有传 perPartitionConfig 则就会使用 PerPartitionConfig

def createDirectStream[K, V](
    ssc: StreamingContext,
    locationStrategy: LocationStrategy,
    consumerStrategy: ConsumerStrategy[K, V]
  ): InputDStream[ConsumerRecord[K, V]] = {
  val ppc = new DefaultPerPartitionConfig(ssc.sparkContext.getConf)
  createDirectStream[K, V](ssc, locationStrategy, consumerStrategy, ppc)
}
def createDirectStream[K, V](
    ssc: StreamingContext,
    locationStrategy: LocationStrategy,
    consumerStrategy: ConsumerStrategy[K, V],
    perPartitionConfig: PerPartitionConfig
  ): InputDStream[ConsumerRecord[K, V]] = {
  new DirectKafkaInputDStream[K, V](ssc, locationStrategy, consumerStrategy, perPartitionConfig)
}

PerPartitionConfig

最关键的就是包含了 spark.streaming.kafka.maxRatePerPartition 和 spark.streaming.kafka.minRatePerPartition

private class DefaultPerPartitionConfig(conf: SparkConf)
    extends PerPartitionConfig {
  val maxRate = conf.getLong("spark.streaming.kafka.maxRatePerPartition", 0)
  val minRate = conf.getLong(" ", 1)

  def maxRatePerPartition(topicPartition: TopicPartition): Long = maxRate
  override def minRatePerPartition(topicPartition: TopicPartition): Long = minRate
}

DirectKafkaInputDStream

在每次发送GenerateJobs的消息时,就会触发Dstream的getOrCompute 或 compute

override def compute(validTime: Time): Option[KafkaRDD[K, V]] = {
  val untilOffsets = clamp(latestOffsets())
  val offsetRanges = untilOffsets.map { case (tp, uo) =>
    val fo = currentOffsets(tp)
    OffsetRange(tp.topic, tp.partition, fo, uo)
  }
  val useConsumerCache = context.conf.getBoolean("spark.streaming.kafka.consumer.cache.enabled",
    true)
  val rdd = new KafkaRDD[K, V](context.sparkContext, executorKafkaParams, offsetRanges.toArray,
    getPreferredHosts, useConsumerCache)

  // Report the record number and metadata of this batch interval to InputInfoTracker.
  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)

  currentOffsets = untilOffsets
  commitAll()
  Some(rdd)
}
// limits the maximum number of messages per partition 
// 限制每个分区的数据量
// offsets 是最新的每个partition的offset map

protected def clamp(
  offsets: Map[TopicPartition, Long]): Map[TopicPartition, Long] = {

  maxMessagesPerPartition(offsets).map { mmp =>
    mmp.map { case (tp, messages) =>
    	// 从map 通过分区获取 最新的offset
        val uo = offsets(tp)
        // tp 指向 当前offset + messages 和最新的offset 的最小值。
        // message 就是一个批次的条数 offset 到 offset+message 就是下一个批次的offset 范围。
        // 取最小值就是为了避便这个范围超过了 最新的offset
        tp -> Math.min(currentOffsets(tp) + messages, uo)
    }
  }.getOrElse(offsets)
}
// 计算每个partition的offset 这边设计到反压机制,默认反压 是关闭的
protected[streaming] def maxMessagesPerPartition(
  offsets: Map[TopicPartition, Long]): Option[Map[TopicPartition, Long]] = {
  
  //默认没有开启反压,这边就是None
  val estimatedRateLimit = rateController.map { x => {
    val lr = x.getLatestRate()
    if (lr > 0) lr else initialRate
  }}

  // calculate a per-partition rate limit based on current lag
  val effectiveRateLimitPerPartition = estimatedRateLimit.filter(_ > 0) match {
    case Some(rate) =>
      val lagPerPartition = offsets.map { case (tp, offset) =>
        tp -> Math.max(offset - currentOffsets(tp), 0)
      }
      val totalLag = lagPerPartition.values.sum

      lagPerPartition.map { case (tp, lag) =>
        val maxRateLimitPerPartition = ppc.maxRatePerPartition(tp)
        val backpressureRate = lag / totalLag.toDouble * rate
        tp -> (if (maxRateLimitPerPartition > 0) {
          Math.min(backpressureRate, maxRateLimitPerPartition)} else backpressureRate)
      }
    case None => offsets.map { case (tp, offset) => 
    	// 取出spark.streaming.kafka.maxRatePerPartition 每个分区的条数就是 这个配置的值
    	tp -> ppc.maxRatePerPartition(tp).toDouble }
  }

  if (effectiveRateLimitPerPartition.values.sum > 0) {
    val secsPerBatch = context.graph.batchDuration.milliseconds.toDouble / 1000
    Some(effectiveRateLimitPerPartition.map {
      // 然后取 每个批次的秒数 * maxRatePerPartition 和 minRatePerPartition 最大值
      // 也就是 tp-> limit (上个方法的 message)
      case (tp, limit) => tp -> Math.max((secsPerBatch * limit).toLong,
        ppc.minRatePerPartition(tp))
    })
  } else {
    None
  }
}
// 判断是否反压,没有有时None
override protected[streaming] val rateController: Option[RateController] = {
  if (RateController.isBackPressureEnabled(ssc.conf)) {
    Some(new DirectKafkaRateController(id,
      RateEstimator.create(ssc.conf, context.graph.batchDuration)))
  } else {
    None
  }
}

正常情况就是

maxRatePerPartition * second.interval 就是一个批次中一个分区的数据量

maxRatePerPartition * second.interval*partition 就是一个整个批次的处理数据量

posted on 2020-02-21 17:54  chouc  阅读(263)  评论(0编辑  收藏  举报

导航