1.Receiver模式
1.KafkaUtils.createDStream--API创建。
2.会有一个Receiver作为常驻Task运行在Executor进行中,一直等待数据的到来。
3. 一个Receiver效率会比较低,那么可以使用多个Receiver,但是多个Receiver中的数据又需要手动进行合并,很麻烦,且其中某个Receiver挂了之后,会导致数据丢失,需要开启WAL预写日志来保证数据的安全,但是效率又低了。
4.Receiver模式使用Zookeeper来连接Kafka(Kafka的新版本中已经不推荐使用该方式了)
5.Receiver模式使用的是Kafka的高阶API(高度封装),offset由Receiver提交到ZK中(Kafka新版本的offset默认存储在__consumer_offset),容易与spark维护在Checkpoint中的offset不一样
2.Direct模式
1.KafkaUtils.CreateDirectStream--API创建
2. Direct模式是直接连接到Kafka的各个分区,并拉取数据,提高了数据读取的并发能力
3.Direct模式使用的是Kafka低阶API(底层API),可以自己维护偏移量到任何地方
4.Direct模式+手动操作可以保证数据的Exact-Once精确一次(数据仅会被处理一次)
注意
这两者模式都是依赖于此依赖
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming-kafka-0-10_2.12</artifactId>
<version>3.0.0</version>
</dependency>
SparkStreaming整合Kafka的两个版本的API
Spark-streaming-kafka-0-8
支持Receiver、Direct模式,但不支持offset维护API,不支持动态分区。
Spark-streaming-kafka-0-10
支持Direct,不支持Receiver模式,支持维护offset维护API,支持动态订阅,推荐使用。
使用案例
1.创建环境
// 配置环境
val conf = new SparkConf().setAppName("Covid10WzApplication").setMaster("local[*]")
val sc = new SparkContext(conf)
sc.setLogLevel("WARN") // 设置日志打印等级
val ssc = new StreamingContext(sc, Seconds(5))
ssc.checkpoint("./sscckp") // 设置检查点
2.设置kafka连接参数
// 准备Kafka的连接参数
val kafkaParams = Map[String, Object](
"bootstrap.servers" -> "hadoop102:9092,hadoop103:9092,hadoop104:9092", // 集群地址
"group.id" -> "SparkKafka", // 消费者名称
// latest表示如果记录了偏移量则从记录的位置开始读取数据,如果没有记录则从最新/最后的位置开始消费
// earliest表示如果记录了偏移量则从记录的位置开始读取,如果没有记录则从最开始的位置开始消费
// none 表示如果记录了偏移量则从记录的位置开始读取,如果没有则报错
"auto.offset.reset" -> "latest",
"enable.auto.commit" -> (false: java.lang.Boolean),// 是否自动提交偏移量
"key.deserializer" -> classOf[IntegerDeserializer], // 反序列化类型
"value.deserializer" -> classOf[StringDeserializer] // 反序列化类型
)
3.订阅主题
// 设置需订阅的Kafka主题
val topics = Array[String]("covid_wz")
// 连接Kafka,获取流式数据
val KafkaDs = KafkaUtils.createDirectStream[Int, String](
ssc,
LocationStrategies.PreferConsistent,
ConsumerStrategies.Subscribe[Int, String](topics, kafkaParams))
4.消费数据,手动提交偏移量
// 手动提交偏移量
// 我们要手动提交偏移量,那么就意味着消费了一批数据(rdd)就应该提交一次偏移量
KafkaDs.foreachRDD(rdd => {
if (rdd.count() > 0) { // 判断rdd中是否有数据
rdd.foreach(record => {
println("从Kafka中消费的数据:" + record)
// 从Kafka中消费的数据:ConsumerRecord(topic = covid_wz, partition = 0, leaderEpoch = 0, offset = 0, CreateTime = 1655871649181, serialized key size = -1, serialized value size = 3, headers = RecordHeaders(headers = [], isReadOnly = false), key = null, value = hhh)
})
// 获取偏移量
// 使用Spark-streaming-kafka-0-10中封装好的API来存放偏移量并提交
val offsets = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
for (o <- offsets) {
println(o)
// OffsetRange(topic: 'covid_wz', partition: 1, range: [2 -> 2])
println(s"topic=${o.topic},partition=${o.partition},fromOffset=${o.fromOffset},until=${o.untilOffset}")
//topic=covid_wz,partition=1,fromOffset=2,until=2
//topic=covid_wz,partition=0,fromOffset=1,until=2
//topic=covid_wz,partition=2,fromOffset=2,until=4
}
// 手动提交偏移量到Kafka的默认主题:__consumer_offsets中,如果还开启了Checkpoint还会提交给Checkponit
KafkaDs.asInstanceOf[CanCommitOffsets].commitAsync(offsets)
}
})