第15课:Spark Streaming源码解读之No Receivers彻底思考

原创 2016年05月31日 16:52:08

receive和no receiver的方式(derict的方式)

封装器一定是RDD类型的KafkaRDD,是为不同的数据来源推出不同的RDD

foreachRDD中就可以获得当前batch duration中产生的RDD的分区的数据,RDD所访问的所有分驱的数据。

 

建议企业级采用no receivers方式开发Spark Streaming应用程序,好处:

1、更优秀的自由度控制

2、语义一致性

no receivers更符合数据读取和数据操作,Spark 计算框架底层有数据来源,如果只有direct直接操作数据来源则更天然。操作数据来源封装其一定是rdd级别的。

所以Spark 推出了自定义的rdd即Kafkardd,只是数据来源不同。

KafkaRDD.scala

private[kafka]
class KafkaRDD[
  K:ClassTag,
  V:ClassTag,
  U <:Decoder[_]: ClassTag,
  T <:Decoder[_]: ClassTag,
  R:ClassTag] private[spark] (
    sc: SparkContext,
    kafkaParams: Map[String, String],
    val offsetRanges:Array[OffsetRange],
    leaders: Map[TopicAndPartition,(String, Int)],
    messageHandler: MessageAndMetadata[K, V] => R
 
) extendsRDD[R](sc, Nil) with Logging with HasOffsetRanges{
  override def getPartitions: Array[Partition] = {
    offsetRanges.zipWithIndex.map { case (o, i) =>
        val (host,port) = leaders(TopicAndPartition(o.topic, o.partition))
        new KafkaRDDPartition(i,o.topic, o.partition, o.fromOffset, o.untilOffset, host, port)
    }.toArray
  }

 

final class OffsetRange private(
    val topic:String,
    val partition:Int,
    val fromOffset:Long,
    val untilOffset:Long) extends Serializable {
  import OffsetRange.OffsetRangeTuple

 
/** Kafka TopicAndPartition object, for convenience */
 
def topicAndPartition():TopicAndPartition = TopicAndPartition(topic, partition)

  /** Number of messages this OffsetRange refers to */
 
def count():Long = untilOffset - fromOffset

  override def equals(obj: Any): Boolean = obj match {
    case that:OffsetRange =>
      this.topic== that.topic &&
        this.partition== that.partition &&
        this.fromOffset== that.fromOffset &&
        this.untilOffset== that.untilOffset
    case _=> false
 
}

 

 

override def getPreferredLocations(thePart: Partition): Seq[String] = {
  val part =thePart.asInstanceOf[KafkaRDDPartition]
  // TODO is additional hostname resolution necessary here
 
Seq(part.host)
}

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) {
    log.info(s"Beginning offset ${part.fromOffset} is the same as ending offset " +
      s"skipping ${part.topic}${part.partition}")
   Iterator.empty
 
} else {
    new KafkaRDDIterator(part, context)
  }
}

private class KafkaRDDIterator(

//kafka真正获取数据本身
    part: KafkaRDDPartition,
    context: TaskContext) extends NextIterator[R] {

  context.addTaskCompletionListener{context => closeIfNeeded() }

  log.info(s"Computing topic ${part.topic}, partition ${part.partition}" +
    s"offsets ${part.fromOffset} -> ${part.untilOffset}")

  val kc = new KafkaCluster(kafkaParams)

 

 

def connect(host: String,port: Int): SimpleConsumer =
  new SimpleConsumer(host,port, config.socketTimeoutMs,
    config.socketReceiveBufferBytes, config.clientId)

 

 

override def getNext(): R = {
  if (iter == null || !iter.hasNext){
    iter =fetchBatch
  }
  if (!iter.hasNext) {
    assert(requestOffset == part.untilOffset, errRanOutBeforeEnd(part))
    finished = true
    null
.asInstanceOf[R]
  } else {
    val item =iter.next()
    if (item.offset>= part.untilOffset) {
      assert(item.offset ==part.untilOffset, errOvershotEnd(item.offset, part))
      finished = true
      null
.asInstanceOf[R]
    } else {
      requestOffset = item.nextOffset
      messageHandler(new MessageAndMetadata(
        part.topic, part.partition,item.message, item.offset, keyDecoder, valueDecoder))
    }
  }
}

KafkaUtils.scala

创建kafka API的时候一般都是通过KafkaUtils创建的

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)
}

private[streaming]
class DirectKafkaInputDStream[
  K:ClassTag,
  V:ClassTag,
  U <:Decoder[K]: ClassTag,
  T <:Decoder[V]: ClassTag,
  R:ClassTag](
    ssc_ : StreamingContext,
    val kafkaParams:Map[String, String],
    val fromOffsets:Map[TopicAndPartition, Long],
    messageHandler: MessageAndMetadata[K, V] => R
 
) extendsInputDStream[R](ssc_)with Logging {
  val maxRetries= context.sparkContext.getConf.getInt(
    "spark.streaming.kafka.maxRetries", 1)  //重试一次

//读取速度
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
  }
}

override def compute(validTime: Time): Option[KafkaRDD[K, V, U, T, R]] = {
  val untilOffsets= clamp(latestLeaderOffsets(maxRetries))
  val rdd = KafkaRDD[K, V, U, T, R](
    context.sparkContext, kafkaParams, currentOffsets, untilOffsets, messageHandler)

  // Report the record number and metadata of this batchinterval 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 frombeing 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.map(kv => kv._1 -> kv._2.offset)
  Some(rdd)
}

 

private[kafka] def getFromOffsets(
    kc: KafkaCluster,
    kafkaParams: Map[String, String],
    topics: Set[String]
  ): Map[TopicAndPartition,Long] = {
  val reset= kafkaParams.get("auto.offset.reset").map(_.toLowerCase)
  val result= for {
    topicPartitions <-kc.getPartitions(topics).right
    leaderOffsets <- (if (reset == Some("smallest")) {
     kc.getEarliestLeaderOffsets(topicPartitions)
    } else {
     kc.getLatestLeaderOffsets(topicPartitions)
    }).right
  } yield {
    leaderOffsets.map { case (tp, lo) =>
        (tp, lo.offset)
    }
  }
  KafkaCluster.checkErrors(result)
}

 

KafkaRDDPartition

//相当于kafka数据来源的指针

private[kafka]
class KafkaRDDPartition(
  val index:Int,
  val topic:String,
  val partition:Int,
  val fromOffset:Long,
  val untilOffset:Long,
  val host: String,
  val port:Int
) extends Partition {
  /** Number of messages this partition refers to */
 
def count():Long = untilOffset - fromOffset
}

思考直接抓取kafka数据和receiver读取数据:

好处1. derict的方式没有缓存,不存在内存溢出的方式

好处2. receiver是和具体的excecuter,worker绑定。Receiver的方式不方便做分布式。默认kafkaRDD数据都是分布在多个excecuter上的

好处3.数据消费的问题,在实际操作的时候采用receiver的方式有个弊端,消费数据来不及处理即操作数据有deLay多才时,Spark Streaming程序有可能奔溃。但如果是direct方式访问kafka数据不会存在此类情况。因为diect方式直接读取kafka数据,如果delay就不进行下一个batchDuration读取。

好处4.完全的语义一致性,不会重复消费数据,而且保证数据一定被消费,跟kafka进行交互,只有数据真正执行成功之后才会记录下来。

生产环境下强烈建议采用direct方式读取kafka数据。

 

backpressure参数可以试探流进的速度和处理能力是否一致。

SQL查询语句中的 limit offset

经常用到在数据库中查询中间几条数据的需求 比如下面的sql语句: ① selete * from testtable limit 2,1; ② selete * from testtable l...
  • u012927188
  • u012927188
  • 2014年12月16日 11:15
  • 13537

Spark Streaming +Kafka 使用底层API直接读取Kafka的Partition数据,手动更新Offset到Zookeeper集群

Spark Streaming  +Kafka 使用底层API直接读取Kafka的Partition数据,正常Offset存储在CheckPoint中。但是这样无法实现Kafka监控工具对Kafka的...
  • Dax1n
  • Dax1n
  • 2016年11月30日 20:22
  • 4621

sql 中 limit 与 limit,offset连用的区别

① select * from table limit 2,1;                  //含义是跳过2条取出1条数据,limit后面是从第2条开始读,读取1条信息,即读取第3条数据 ...
  • AinUser
  • AinUser
  • 2017年05月29日 17:52
  • 711

mysql查询时,offset过大影响性能的原因与优化方法

mysql查询使用select命令,配合limit,offset参数可以读取指定范围的记录。本文将介绍mysql查询时,offset过大影响性能的原因及优化方法。...
  • fdipzone
  • fdipzone
  • 2017年05月28日 17:13
  • 19192

kafka offset查询、提交

wiki地址https://cwiki.apache.org/confluence/display/KAFKA/Committing+and+fetching+consumer+offsets+in+...
  • u012660667
  • u012660667
  • 2016年11月22日 15:14
  • 2800

让你彻底弄清offset

转自:http://www.cnblogs.com/jscode/archive/2012/09/03/2669299.html
  • chelen_jak
  • chelen_jak
  • 2014年05月16日 09:48
  • 3266

修改kafka topic的offset几种方法

查询topic的offset的范围 用下面命令可以查询到topic:test broker:suna:9092的offset的最小值: bin/kafka-run-class.sh kafka...
  • yxgxy270187133
  • yxgxy270187133
  • 2016年12月15日 10:44
  • 12020

offset、scroll、client三大家族

1、offset    偏移 1、offsetWidth 和 offsetHeight 是用来得到对象的大小,由自身宽高 内边距 边框构成但是不包括 外边距 offsetHeight和sty...
  • k491022087
  • k491022087
  • 2016年09月23日 00:26
  • 2485

Kafka offset存储方式与获取消费实现

1.概述 Kafka版本[0.10.1.1],已默认将消费的 offset 迁入到了 Kafka 一个名为 __consumer_offsets 的Topic中。其实,早在 0.8.2.2 版本...
  • anningzhu
  • anningzhu
  • 2017年03月05日 18:07
  • 464

关于Kafka 的消息日志Offset 的了解

之前在做Kafka 整合Storm的时候,因为对Kafka 不是很熟,考虑过这样的一个场景问题,针对一个Topic,Kafka消息日志中有个offset信息来标注消息的位置,Storm每次从kafka...
  • looklook5
  • looklook5
  • 2014年12月18日 17:31
  • 9324
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:第15课:Spark Streaming源码解读之No Receivers彻底思考
举报原因:
原因补充:

(最多只允许输入30个字)