這次的博客向大家介紹一下將偏移量存儲在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