在前几期文章里讲了带Receiver的Spark Streaming 应用的相关源码解读,但是现在开发Spark Streaming的应用越来越多的采用No Receivers(Direct Approach)的方式,No Receiver的方式的优势: 
1. 更强的控制自由度 
2. 语义一致性 
其实No Receivers的方式更符合我们读取数据,操作数据的思路的。因为Spark 本身是一个计算框架,他底层会有数据来源,如果没有Receivers,我们直接操作数据来源,这其实是一种更自然的方式。 如果要操作数据来源,肯定要有一个封装器,这个封装器一定是RDD类型。 以直接访问Kafka中的数据为例,看一下源码中直接读写Kafka中数据的例子代码:
  1. object DirectKafkaWordCount {
  2. def main(args: Array[String]) {
  3. if (args.length < 2) {
  4. System.err.println(s"""
  5. |Usage: DirectKafkaWordCount <brokers> <topics>
  6. | <brokers> is a list of one or more Kafka brokers
  7. | <topics> is a list of one or more kafka topics to consume from
  8. |
  9. """.stripMargin)
  10. System.exit(1)
  11. }
  12. StreamingExamples.setStreamingLogLevels()
  13. val Array(brokers, topics) = args
  14. // Create context with 2 second batch interval
  15. val sparkConf = new SparkConf().setAppName("DirectKafkaWordCount")
  16. val ssc = new StreamingContext(sparkConf, Seconds(2))
  17. // Create direct kafka stream with brokers and topics
  18. val topicsSet = topics.split(",").toSet
  19. val kafkaParams = Map[String, String]("metadata.broker.list" -> brokers)
  20. val messages = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder](
  21. ssc, kafkaParams, topicsSet)
  22. // Get the lines, split them into words, count the words and print
  23. val lines = messages.map(_._2)
  24. val words = lines.flatMap(_.split(" "))
  25. val wordCounts = words.map(x => (x, 1L)).reduceByKey(_ + _)
  26. wordCounts.print()
  27. // Start the computation
  28. ssc.start()
  29. ssc.awaitTermination()
  30. }
  31. }

Spark streaming 会将数据源封装成一个RDD,也就是KafkaRDD:

  1. /**
  2. * A batch-oriented interface for consuming from Kafka.
  3. * Starting and ending offsets are specified in advance,
  4. * so that you can control exactly-once semantics.
  5. * @param kafkaParams Kafka <a href="http://kafka.apache.org/documentation.html#configuration">
  6. * configuration parameters</a>. Requires "metadata.broker.list" or "bootstrap.servers" to be set
  7. * with Kafka broker(s) specified in host1:port1,host2:port2 form.
  8. * @param offsetRanges offset ranges that define the Kafka data belonging to this RDD
  9. * @param messageHandler function for translating each message into the desired type
  10. */
  11. private[kafka]
  12. class KafkaRDD[
  13. K: ClassTag,
  14. V: ClassTag,
  15. U <: Decoder[_]: ClassTag,
  16. T <: Decoder[_]: ClassTag,
  17. R: ClassTag] private[spark] (
  18. sc: SparkContext,
  19. kafkaParams: Map[String, String],
  20. val offsetRanges: Array[OffsetRange], //该RDD的数据偏移量
  21. leaders: Map[TopicAndPartition, (String, Int)],
  22. messageHandler: MessageAndMetadata[K, V] => R
  23. ) extends RDD[R](sc, Nil) with Logging with HasOffsetRanges

可以看到KafkaRDD 混入了HasOffsetRanges,它是一个trait:

  1. trait HasOffsetRanges {
  2. def offsetRanges: Array[OffsetRange]
  3. }

其中OffsetRange,标识了RDD的数据的主题、分区、开始偏移量和结束偏移量:

  1. inal class OffsetRange private(
  2. val topic: String,
  3. val partition: Int,
  4. val fromOffset: Long,
  5. val untilOffset: Long) extends Serializable

回到KafkaRDD,看一下KafkaRDD的getPartitions方法:

  1. override def getPartitions: Array[Partition] = {
  2. offsetRanges.zipWithIndex.map { case (o, i) =>
  3. val (host, port) = leaders(TopicAndPartition(o.topic, o.partition))
  4. new KafkaRDDPartition(i, o.topic, o.partition, o.fromOffset, o.untilOffset, host, port)
  5. }.toArray
  6. }

返回KafkaRDDPartition:

  1. private[kafka]
  2. class KafkaRDDPartition(
  3. val index: Int,
  4. val topic: String,
  5. val partition: Int,
  6. val fromOffset: Long,
  7. val untilOffset: Long,
  8. val host: String,
  9. val port: Int
  10. ) extends Partition {
  11. /** Number of messages this partition refers to */
  12. def count(): Long = untilOffset - fromOffset
  13. }

KafkaRDDPartition清晰的描述了数据的具体位置,每个KafkaRDDPartition分区的数据交给KafkaRDD的compute方法计算:


  1. override def compute(thePart: Partition, context: TaskContext): Iterator[R] = {
  2. val part = thePart.asInstanceOf[KafkaRDDPartition]
  3. assert(part.fromOffset <= part.untilOffset, errBeginAfterEnd(part))
  4. if (part.fromOffset == part.untilOffset) {
  5. log.info(s"Beginning offset ${part.fromOffset} is the same as ending offset " +
  6. s"skipping ${part.topic} ${part.partition}")
  7. Iterator.empty
  8. } else {
  9. new KafkaRDDIterator(part, context)
  10. }
  11. }

KafkaRDD的compute方法返回了KafkaIterator对象:

  1. private class KafkaRDDIterator(
  2. part: KafkaRDDPartition,
  3. context: TaskContext) extends NextIterator[R] {
  4. context.addTaskCompletionListener{ context => closeIfNeeded() }
  5. log.info(s"Computing topic ${part.topic}, partition ${part.partition} " +
  6. s"offsets ${part.fromOffset} -> ${part.untilOffset}")
  7. val kc = new KafkaCluster(kafkaParams)
  8. val keyDecoder = classTag[U].runtimeClass.getConstructor(classOf[VerifiableProperties])
  9. .newInstance(kc.config.props)
  10. .asInstanceOf[Decoder[K]]
  11. val valueDecoder = classTag[T].runtimeClass.getConstructor(classOf[VerifiableProperties])
  12. .newInstance(kc.config.props)
  13. .asInstanceOf[Decoder[V]]
  14. val consumer = connectLeader
  15. var requestOffset = part.fromOffset
  16. var iter: Iterator[MessageAndOffset] = null
  17. //..................
  18. }

KafkaIterator中创建了一个KakfkaCluster对象用于与Kafka集群进行交互,获取数据。

回到开头的例子,我们使用 KafkaUtils.createDirectStream 创建了InputDStream:

  1. val messages = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder](
  2. ssc, kafkaParams, topicsSet)

看一下createDirectStream源码:

  1. def createDirectStream[
  2. K: ClassTag,
  3. V: ClassTag,
  4. KD <: Decoder[K]: ClassTag,
  5. VD <: Decoder[V]: ClassTag] (
  6. ssc: StreamingContext,
  7. kafkaParams: Map[String, String],
  8. topics: Set[String]
  9. ): InputDStream[(K, V)] = {
  10. val messageHandler = (mmd: MessageAndMetadata[K, V]) => (mmd.key, mmd.message)
  11. //创建KakfaCluster对象
  12. val kc = new KafkaCluster(kafkaParams)
  13. //更具kc的信息获取数据偏移量
  14. val fromOffsets = getFromOffsets(kc, kafkaParams, topics)
  15. new DirectKafkaInputDStream[K, V, KD, VD, (K, V)](
  16. ssc, kafkaParams, fromOffsets, messageHandler)
  17. }

首先通过KafkaCluster从Kafka集群获取信息,创建DirectKafkaInputDStream对象返回

DirectKafkaInputDStream的compute方法源码:
  1. override def compute(validTime: Time): Option[KafkaRDD[K, V, U, T, R]] = {
  2.     //计算最近的数据终止偏移量
  3. val untilOffsets = clamp(latestLeaderOffsets(maxRetries))
  4. //利用数据的偏移量创建KafkaRDD
  5. val rdd = KafkaRDD[K, V, U, T, R](
  6. context.sparkContext, kafkaParams, currentOffsets, untilOffsets, messageHandler)
  7. // Report the record number and metadata of this batch interval to InputInfoTracker.
  8. val offsetRanges = currentOffsets.map { case (tp, fo) =>
  9. val uo = untilOffsets(tp)
  10. OffsetRange(tp.topic, tp.partition, fo, uo.offset)
  11. }
  12. val description = offsetRanges.filter { offsetRange =>
  13. // Don't display empty ranges.
  14. offsetRange.fromOffset != offsetRange.untilOffset
  15. }.map { offsetRange =>
  16. s"topic: ${offsetRange.topic}\tpartition: ${offsetRange.partition}\t" +
  17. s"offsets: ${offsetRange.fromOffset} to ${offsetRange.untilOffset}"
  18. }.mkString("\n")
  19. // Copy offsetRanges to immutable.List to prevent from being modified by the user
  20. val metadata = Map(
  21. "offsets" -> offsetRanges.toList,
  22. StreamInputInfo.METADATA_KEY_DESCRIPTION -> description)
  23. val inputInfo = StreamInputInfo(id, rdd.count, metadata)
  24. ssc.scheduler.inputInfoTracker.reportInfo(validTime, inputInfo)
  25. currentOffsets = untilOffsets.map(kv => kv._1 -> kv._2.offset)
  26. Some(rdd)
  27. }

可以看到DirectKafkaInputDStream的compute方法中,首先从Kafka集群获取数据的偏移量,然后利用获取偏移量创建RDD,这个Receiver的RDD创建方式不同




15、Spark Streaming源码解读之No Receivers彻底思考的更多相关文章

  1. Spark Streaming源码解读之No Receivers彻底思考

    本期内容 : Direct Acess Kafka Spark Streaming接收数据现在支持的两种方式: 01. Receiver的方式来接收数据,及输入数据的控制 02. No Receive ...

  2. Spark Streaming源码解读之JobScheduler内幕实现和深度思考

    本期内容 : JobScheduler内幕实现 JobScheduler深度思考 JobScheduler 是整个Spark Streaming调度的核心,需要设置多线程,一条用于接收数据不断的循环, ...

  3. Spark Streaming源码解读之流数据不断接收和全生命周期彻底研究和思考

    本节的主要内容: 一.数据接受架构和设计模式 二.接受数据的源码解读 Spark Streaming不断持续的接收数据,具有Receiver的Spark 应用程序的考虑. Receiver和Drive ...

  4. Spark Streaming源码解读之流数据不断接收全生命周期彻底研究和思考

    本期内容 : 数据接收架构设计模式 数据接收源码彻底研究 一.Spark Streaming数据接收设计模式   Spark Streaming接收数据也相似MVC架构: 1. Mode相当于Rece ...

  5. Spark Streaming源码解读之Receiver生成全生命周期彻底研究和思考

    本期内容 : Receiver启动的方式设想 Receiver启动源码彻底分析 多个输入源输入启动,Receiver启动失败,只要我们的集群存在就希望Receiver启动成功,运行过程中基于每个Tea ...

  6. Spark Streaming源码解读之生成全生命周期彻底研究与思考

    本期内容 : DStream与RDD关系彻底研究 Streaming中RDD的生成彻底研究 问题的提出 : 1. RDD是怎么生成的,依靠什么生成 2.执行时是否与Spark Core上的RDD执行有 ...

  7. Spark Streaming源码解读之Job动态生成和深度思考

    本期内容 : Spark Streaming Job生成深度思考 Spark Streaming Job生成源码解析 Spark Core中的Job就是一个运行的作业,就是具体做的某一件事,这里的JO ...

  8. 16.Spark Streaming源码解读之数据清理机制解析

    原创文章,转载请注明:转载自 听风居士博客(http://www.cnblogs.com/zhouyf/) 本期内容: 一.Spark Streaming 数据清理总览 二.Spark Streami ...

  9. 11.Spark Streaming源码解读之Driver中的ReceiverTracker架构设计以及具体实现彻底研究

    上篇文章详细解析了Receiver不断接收数据的过程,在Receiver接收数据的过程中会将数据的元信息发送给ReceiverTracker:   本文将详细解析ReceiverTracker的的架构 ...

随机推荐

  1. 【题解】【LibreOJ Round #6】花团 LOJ 534 时间线段树分治 背包

    Prelude 题目链接:萌萌哒传送门(/≧▽≦)/ Solution 如果完全离线的话,可以直接用时间线段树分治来做,复杂度\(O(qv \log q)\). 现在在线了怎么办呢? 这其实是个假在线 ...

  2. 题解【bzoj2038 [2009国家集训队]小Z的袜子(hose)】

    Description \(m\) 个询问,每次给出一个区间,求从这个区间中取出两个数使得它们同色的概率. \(n,m,a_i \leq 50000\) Solution 莫队模板题 最后的概率是 选 ...

  3. update condition 字段报错

    mysql> update tf_user_present set condition="0" where id=1;ERROR 1064 (42000): You have ...

  4. SpringBoot(五) :spring data jpa 的使用

    原文出处: 纯洁的微笑 在上篇文章springboot(二):web综合开发中简单介绍了一下spring data jpa的基础性使用,这篇文章将更加全面的介绍spring data jpa 常见用法 ...

  5. Lucene 查询分页技术

    常用的Lucene查询代码如下所示,该代码的作用是将path路径下的所有索引信息返回 public String matchAll(String path) { try { Directory dir ...

  6. ACM-ICPC2018 沈阳赛区网络预赛-E-The cake is a lie

    You promised your girlfriend a rounded cake with at least SS strawberries. But something goes wrong, ...

  7. NOIP 2000 方格取数

    https://www.luogu.org/problem/show?pid=1004 题目描述 设有N*N的方格图(N<=9),我们将其中的某些方格中填入正整数,而其他的方格中则放 人数字0. ...

  8. 区间->点,点->区间,线段树优化建图+dijstra Codeforces Round #406 (Div. 2) D

    http://codeforces.com/contest/787/problem/D 题目大意:有n个点,三种有向边,这三种有向边一共加在一起有m个,然后起点是s,问,从s到所有点的最短路是多少? ...

  9. 重构改善既有代码设计--重构手法08:Replace Method with Method Object (以函数对象取代函数)

    你有一个大型函数,其中对局部变量的使用,使你无法釆用 Extract Method. 将这个函数放进一个单独对象中,如此一来局部变量就成了对象内的值域(field) 然后你可以在同一个对象中将这个大型 ...

  10. HDU 3790 最短生成树 (最短路)

    题目链接 Problem Description 给你n个点,m条无向边,每条边都有长度d和花费p,给你起点s终点t,要求输出起点到终点的最短距离及其花费,如果最短距离有多条路线,则输出花费最少的. ...