Kafka直連存儲ZK

這次的博客向大家介紹一下將偏移量存儲在Zookeeper中。
我在註明書寫邏輯的地方,可以在那裏對RDD進行算子操作。

package kafka1
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}


object KafkaDirectZookeeper {
  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 ="test002"
    //指定kafka的Broker地址(SparkStreaming的Task直連到Kafka分區上,用的是底層API消費)
    val brokerList="192.168.85.200:9092"
    //我們要自己維護Offset偏移量,將Offset保存到zk中
    val zkQuorum = "192.168.85.200:2181"
    //創建Stream時,使用topic名字集合,SparkStreaming可以同時消費多個Topic
    val topics = Set(topic)
    //創建一個ZKGroupTopicDrirs對象,其實是指定往ZK中寫入數據的目錄
    //用於保存偏移量
    val topicDir=new ZKGroupTopicDirs(groupId,topic)
    //獲取zookeeper中的路徑"/consumers/gp01/offsets/test002/"
    val zkTopicPath =s"${topicDir.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)
    val clientOffset = zkClient.countChildren(zkTopicPath)
    //創建kafkaStream
    var kafkaStream:InputDStream[(String,String)]=null
    //如果zookeeper中有保存offset 我們會利用這個offset作爲kafkaStream的其實
    //TopicAndPartition
    var fromOffsets:Map[TopicAndPartition,Long]=Map()
    //如果保存過offset
    if(clientOffset>0){
      //clientOffset的數量其實就是 /gp01/offset/test的分區數目
      for(i<-0 until clientOffset){
        val partitionOffset=zkClient.readData[String](s"$zkTopicPath/$i")

        val tp = TopicAndPartition(topic,i)
        //將不同partition對應的offset增加到fromOffsets中
        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
       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]()

    kafkaStream.foreachRDD{
        //對RDD進行操作,觸發Action
      kafkardd=>
        offsetRanges =kafkardd.asInstanceOf[HasOffsetRanges].offsetRanges
        //下面可以對maps做rdd操作
        val maps = kafkardd.map(_._2)
        if(!maps.isEmpty){
        //用於書寫邏輯
        }
        for(o <- offsetRanges){
          val zkpath=s"${topicDir.consumerOffsetDir}/${o.partition}"
          //將該Partition的offset保存到zookeeper中
          // /gp01/offset/test
          ZkUtils.updatePersistentPath(zkClient,zkpath,o.untilOffset.toString)
    }
    }
    ssc.start()
    ssc.awaitTermination()
  }
}

在這裏,我畫了一下圖給大家介紹下存儲流程:
在這裏插入圖片描述
我們通過從producer端輸入數據,然後它會分區存儲在Kafka集羣中,他會根據你設置的分區數進行存儲,但是它存儲的並不是平均的,在這裏我設置的partition數量爲3。當Driver端需要讀取數據時,它會去Zookeeper中根據自身訪問的topic和groupID查詢是否在zookeeper中保存有數據(如果以前使用同一消費者組讀取同一Topic會保存數據),如果保存有就分別讀取每個分區的偏移量,如果沒有就從最新讀或者最舊讀,當讀取完數據後,會通過讀取的範圍對偏移量進行更新,這樣就完成了Zookeeper保存偏移量。

									 summed up by JiaMingcan
									 轉載請署名:JiaMingcan
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章