kafka 基础知识整理(三)kafka + spark streaming

Kafka为一个分布式的消息队列,spark流操作kafka有两种方式:一种是利用接收器(receiver)和kafaka的高层API实现。一种是不利用接收器,直接用kafka底层的API来实现(spark1.3以后引入)。

一、Reveiver方式

基于Receiver方式实现会利用Kakfa的高层消费API,和所有的其他Receivers一样,接受到的数据会保存到excutors中,然后由spark Streaming 来启动Job进行处理这些数据。在默认的配置下,这种方式在失败的情况下,会丢失数据,如果要保证零数据丢失,需要启用WAL(Write Ahead Logs)。它同步将接受到数据保存到分布式文件系统上比如HDFS。 所以数据在出错的情况下可以恢复出来。使用两个步骤:

1、添加依赖:spark-streaming-kafka_2.10-1.3.0

2、编程:

import org.apache.spark.streaming.kafka._
  val kafkaStream = KafkaUtils.createStream(
  streamingContext,[ZK quorum], [consumer group id],
  [per-topic number of Kafka partitions to consume]
)

注意:
1.kafka的分区数和Spark的RDD的分区不是一个概念。所以在上述函数中增加特定主题的分区数,仅仅增加了一个receiver中消费topic的线程数,并不难增加spark并行处理数据的数量。(那是不是多少个paratition最好对应多少个receiver的消费线程啊?)
2.对于不同的group和topic,可以使用多个recivers创建多个DStreams来并行处理数据(如果是同一个topic如何保证数据不被重复消费?)。
3.如果启用了WAL,接收到的数据会被持久化一份到日志中,因此需要将storage_lever设置成StorgeLevel.MEMORY_AND_DISK_SER开启:

val conf = new SparkConf()
conf.set("spark.streaming.receiver.writeAheadLog.enable","true")
val sc= new SparkContext(conf)
val ssc = new StreamingContext(sc,Seconds(5))
ssc.checkpoint("checkpoint")
val lines = KafkaUtils.createStream[String, String, StringDecoder, StringDecoder](ssc, kafkaParams, topicMap, StorageLevel.MEMORY_AND_DISK_SER).map(_._2)

//开启在强行终止的情况下,数据仍然会丢失,解决办法:
sys.addShutdownHook({ ssc.stop(true,true))})

3、运行运行提交代码的时候,需要添加以下基本Jar包依赖:

--jars lib/spark-streaming-kafka_2.10-1.3.0.jar,
lib/spark-streaming_2.10-1.3.0.jar,
lib/kafka_2.10-0.8.1.1.jar,lib/zkclient-0.3.jar

4、例子

object KafkaWordCount {
  def main(args: Array[String]) {
  if (args.length < 4) { 
   System.err.println("Usage: KafkaWordCount <zkQuorum> <group> <topics> <numThreads>") System.exit(1)
 } 
StreamingExamples.setStreamingLogLevels() 
val Array(zkQuorum, group, topics, numThreads) = args 
val sparkConf = new SparkConf().setAppName("KafkaWordCount") 
val ssc = new StreamingContext(sparkConf, Seconds(2)) 
//保证元数据恢复,就是Driver端挂了之后数据仍然可以恢复 ssc.checkpoint("checkpoint")
 val topicMap = topics.split(",").map((_,numThreads.toInt)).toMap
 val lines = KafkaUtils.createStream(ssc, zkQuorum, group, topicMap).map(_._2) 
val words = lines.flatMap(_.split(" ")) 
val wordCounts = words.map(x => (x, 1L)) .reduceByKeyAndWindow(_ + _, _ - _, Minutes(10), Seconds(2), 2) 
wordCounts.print()
ssc.start() 
ssc.awaitTermination()
}}

5、图示:

Receiver
数据恢复

二、直接操作方式

不同于Receiver接收数据方式,这种方式定期从kafka的topic下对应的partition中查询最新偏移量,并在每个批次中根据相应的定义的偏移范围进行处理。Spark通过调用kafka简单的消费者API读取一定范围的数据。相比基于Receiver方式有几个优点:

1、简单的并发:

不需要创建多个kafka输入流,然后Union他们,而使用DirectStream,spark Streaming将会创建和kafka分区一样的RDD的分区数,而且会从kafka并行读取数据,Spark的分区数和Kafka的分区数是一一对应的关系。

2、高效

第一种实现数据的零丢失是将数据预先保存在WAL中,会复制一遍数据,会导致数据被拷贝两次:一次是被Kafka复制;另一次是写入到WAL中,没有Receiver消除了这个问题。

3、仅一次语义:

Receiver方式读取kafka,使用的是高层API将偏移量写入ZK中,虽然这种方法可以通过数据保存在WAL中保证数据的不对,但是可能会因为sparkStreaming和ZK中保存的偏移量不一致而导致数据被消费了多次,第二种方式不采用ZK保存偏移量,消除了两者的不一致,保证每个记录只被Spark Streaming操作一次,即使是在处理失败的情况下。如果想更新ZK中的偏移量数据,需要自己写代码来实现。


直接操作

步骤:
1、引入依赖
同第一种方式。
2、编程

import org.apache.spark.streaming.kafka._
val directKafkaStream = KafkaUtils.createDirectStream[[key class], [value class]
, [key decoder class], [value decoder class] ]
(streamingContext, [map of Kafka parameters], [set of topics to consume])

3、如果想获得每个topic中每个分区的在spark streaming中的偏移量,可以通过以下代码:

directKafkaStream.foreachRDD { rdd =>
  val offsetRanges = rdd.asInstanceOf[HasOffsetRanges] 
 //offsetRanges.length = # of Kafka partitions being consumed
  ...
}
//例子:val ssc = new StreamingContext(sc, Seconds(2))
val kafkaParams = Map("zookeeper.connect" -> zkConnect, 
 "group.id" -> kafkaGroupId, "metadata.broker.list" -> 
 "10.15.42.23:8092,10.15.42.22:8092", "auto.offset.reset" -> 
 "smallest" 
 )
val topics = Set(topic)
val directKafkaStream = KafkaUtils.createDirectStream[String, String, StringDecoder, StringDecoder]( ssc, kafkaParams, topics)
//KafkaCluster 需要从源码拷贝,此类是私有类。directKafkaStream.foreachRDD(
 rdd => { 
val offsetLists = rdd.asInstanceOf[HasOffsetRanges].offsetRanges 
val kc = new KafkaCluster(kafkaParams) for (offsets <- offsetLists) { 
val topicAndPartition = TopicAndPartition(offsets.topic, offsets.partition)
 val o = kc.setConsumerOffsets(kafkaGroupId, Map((topicAndPartition, offsets.untilOffset)))
 if (o.isLeft) {
 println(s"Error updating the offset to Kafka cluster: ${o.left.get}") 
} } })

3、部署:同第一种方式。
4、图示:


直接操作

说明
图片均来自互联网,根据Spark官网的文章总结翻译而来。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容