SparkSteaming中直连与receiver两种方式的区别

SparkStreaming的Receiver方式和直连方式有什么区别?

Receiver接收固定时间间隔的数据(放在内存中的),使用高级API,自动维护偏移量,达到固定的时间才去进行处理,效率低并且容易丢失数据,灵活性特别差,不好,而且它处理数据的时候,如果某一刻的数据量过大,那么就会造成磁盘溢写的情况,他通过WALS进行磁盘写入。

Receiver实现方式:

代码如下:

object KafkaWC02 {


  def main(args: Array[String]): Unit = {


    val conf = new SparkConf().setAppName("kafkaWC").setMaster("local[2]") //设置线程数
    val ssc = new StreamingContext(conf, Seconds(5))

    //设置检查点
    ssc.checkpoint("D:\data\checpoint\checpoint1")
    //接下来编写kafka的配置信息
    val zks = "spark01:2181"
    //然后是kafka的消费组
    val groupId = "gp1"
    //Topic的名字  Map的key是Topic名字,第二个参数是线程数
    val topics = Map[String, Int]("test02" -> 1)
    //创建kafka的输入数据流,来获取kafka中的数据
    val data = KafkaUtils.createStream(ssc, zks, groupId, topics)
    //获取到的数据是键值对的格式(key,value)
    //获取到的数据是 key是偏移量  value是数据
    //接下来开始处理数据


    val lines = data.flatMap(_._2.split(" "))
    val words = lines.map((_, 1))
    val res = words.updateStateByKey(updateFunc,new HashPartitioner(ssc.sparkContext.defaultParallelism),true)
    res.print()
    //val result = words.reduceByKey(_ + _)
    //val res = result.updateStateByKey[Int](updateFunc)
    //res.print()
    //打印输出
    //result.print()
    //启动程序
    ssc.start()
    //等待停止
    ssc.awaitTermination()


  }
  //(iterator:Iteratot[(K,Seq[V]),Option[S]]))
  //传过来的值是Key   Value类型
  //第一个参数,是我们从kafka获取到的元素,key  ,String类型
  //第二个参数,是我们进行单词统计的value值,Int类型
  //第三个参数,是我们每次批次提交的中间结果集
  val updateFunc=(iter:Iterator[(String,Seq[Int],Option[Int])])=>{
    iter.map(t=>{
      (t._1,t._2.sum+t._3.getOrElse(0))
    })
  }
}

  

Direct直连方式,

它使用的是底层API实现Offest我们开发人员管理,这样的话,它的灵活性特别好。并且可以保证数据的安全性,而且不用担心数据量过大,因为它有预处理机制,进行提前处理,然后再批次提交任务。

Direct实现方式:

代码如下:

import kafka.common.TopicAndPartition
import kafka.message.MessageAndMetadata
import kafka.serializer.StringDecoder
import kafka.utils.{ZKGroupTopicDirs, ZkUtils}
import org.I0Itec.zkclient.ZkClient
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.InputDStream
import org.apache.spark.streaming.kafka.{HasOffsetRanges, KafkaUtils, OffsetRange}
import org.apache.spark.streaming.{Duration, StreamingContext}

/**
  * 重要!!!  Direct直连方式
  */
object KafkaDirectWC {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("Direct").setMaster("local[2]")
    val ssc = new StreamingContext(conf,Duration(5000))
    //指定组名
    val groupId = "gp01"
    //指定消费的topic名字
    val topic = "tt"
    //指定kafka的Broker地址(SparkStreaming的Task直接连接到Kafka分区上,用的是底层API消费)
    val brokerList ="spark:9092"
    //接下来我们要自己维护offset了,将offset保存到ZK中
    val zkQuorum = "spark:2181"
    //创建stream时使用的topic名字集合,SparkStreaming可以同时消费多个topic
    val topics:Set[String] = Set(topic)
    //创建一个ZkGroupTopicDirs对象,其实是指定往Zk中写入数据的目录
    // 用于保存偏移量
    val TopicDirs = new ZKGroupTopicDirs(groupId,topic)
    //获取zookeeper中的路径“/gp01/offset/tt/”
    val zkTopicPath = s"${TopicDirs.consumerOffsetDir}"
    //准备kafka参数
    val kafkas = Map(
      "metadata.broker.list"->brokerList,
      "group.id"->groupId,
      //从头开始读取数据
      "auto.offset.reset"->kafka.api.OffsetRequest.SmallestTimeString
    )
    // zookeeper 的host和ip,创建一个client,用于更新偏移量
    // 是zookeeper客户端,可以从zk中读取偏移量数据,并更新偏移量
    val zkClient = new ZkClient(zkQuorum)
    //"/gp01/offset/tt/0/10001"
    //"/gp01/offset/tt/1/20001"
    //"/gp01/offset/tt/2/30001"
    val clientOffset = zkClient.countChildren(zkTopicPath)
    // 创建KafkaStream
    var kafkaStream :InputDStream[(String,String)]= null
    //如果zookeeper中有保存offset 我们会利用这个offset作为KafkaStream的起始位置
    //TopicAndPartition  [/gp01/offset/tt/0/ , 8888]
    var fromOffsets:Map[TopicAndPartition,Long] = Map()
    //如果保存过offset
    if(clientOffset > 0){
      //clientOffset 的数量其实就是 /gp01/offset/tt的分区数目
      for(i<-0 until clientOffset){
        // /gp01/offset/tt/  0/10001
        val partitionOffset = zkClient.readData[String](s"$zkTopicPath/${i}")
        // tt/0
        val tp = TopicAndPartition(topic,i)
        //将不同partition 对应得offset增加到fromoffset中
        // tt/0 -> 10001
        fromOffsets += (tp->partitionOffset.toLong)
      }
      // key 是kafka的key value 就是kafka数据
      // 这个会将kafka的消息进行transform 最终kafka的数据都会变成(kafka的key,message)这样的Tuple
      val messageHandler = (mmd:MessageAndMetadata[String,String])=>
        (mmd.key(),mmd.message())
      // 通过kafkaUtils创建直连的DStream
      //[String,String,StringDecoder, StringDecoder,(String,String)]
      // key    value  key解码方式     value的解码方式   接收数据的格式
      kafkaStream = KafkaUtils.createDirectStream
        [String,String,StringDecoder,
          StringDecoder,(String,String)](ssc,kafkas,fromOffsets,messageHandler)
    }else{
      //如果未保存,根据kafkas的配置使用最新的或者最旧的offset
      kafkaStream = KafkaUtils.createDirectStream
        [String,String,StringDecoder,StringDecoder](ssc,kafkas,topics)
    }
    //偏移量范围
    var offsetRanges = Array[OffsetRange]()
    //从kafka读取的数据,是批次提交的,那么这块注意下,
    // 我们每次进行读取数据后,需要更新维护一下偏移量
    //那么我们开始进行取值
    //    val transform = kafkaStream.transform{
    //      rdd=>
    //        //得到该RDD对应得kafka消息的offset
    //        // 然后获取偏移量
    //        offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
    //        rdd
    //    }
    //    val mes = transform.map(_._2)
    // 依次迭代DStream中的RDD
    kafkaStream.foreachRDD{
      //对RDD进行操作 触发Action
      kafkardd=>

        offsetRanges = kafkardd.asInstanceOf[HasOffsetRanges].offsetRanges

        //下面 你就可以怎么写都行了,为所欲为
        val maps = kafkardd.map(_._2)

        maps.foreach(println)

        for(o<-offsetRanges){
          // /gp01/offset/tt/  0
          val zkpath = s"${TopicDirs.consumerOffsetDir}/${o.partition}"
          //将该partition的offset保存到zookeeper中
          // /gp01/offset/tt/  0/88889
          ZkUtils.updatePersistentPath(zkClient,zkpath,o.untilOffset.toString)
        }
    }
    // 启动
    ssc.start()
    ssc.awaitTermination()
  }
}

  

原文地址:https://www.cnblogs.com/zmoumou/p/9975406.html