SparkStreaming2种不同的版本集成
SparkStreaming与kafka的集成有2套
Spark-streaming-kafka-0-8 | Spark-streaming-kafka-0-10 | |
---|---|---|
支持的kafka-Broker版本 | 0.8.2.1 或 更高版本 | 0.10.0 或 更高版本 |
目前是否被遗弃 | deprecated after spark2.3.0 | Stable |
支持语言 | Scala, java,python | Scala,java |
是否支持Receiver Stream | Yes | No |
是否支持Direct Stream | Yes | Yes |
SSL/TLS支持 | NO | Yes |
offset提交API | 没有 | 有 |
动态主题订阅支持 | 不支持 | 支持 |
Spark从kafka拉取数据的发家史:
- 首先在
Spark1.3以前版本,支持以Receiver的方式从
Kafka-0.8+`拉取数据,此时不支持Direct的的方式 - 然后在在
Spark1.3
的时候,引入了Direct方式的拉取数据API,此时Spark与kafka-0.8+
之间支持2种拉取数据方式 - 然后在
Spark2.3
的时候Spark已经不能与kafka-0.8
进行很好的兼容,此时最稳定的兼容版本是kafka-0.10.0+
,但是此时只支持一种Direct的连接方式作为通用方式,简化了对消息的并行度处理
其中Receiver是基于kafka的高级API,kafka的offset是以固定时间间隔存储在zookeeper的topic子节点下,但是Direct是基于kafka的低阶API,直接从kafka不同的分区拉取数据,不经过Receiver,offset可以存在不同的地方(checkpoint、kafka的特定topic、外部的存储系统如:MySQL、Redis等)
那么下面我就以Spark-streaming-kafka-0-8这个集成方式来分别讲解Receiver和Direct方式。
一、Receiver-based 处理
这种方法使用Receiver接收器来接收来自kafka的数据,将数据存储在Spark的Executor的
Storage内存中
,然后由SparkStreaming的output触发的job将这些数据进行处理。
弊端:
这种方式在默认情况下有可能 丢失数据,因为这种方式Spark这边的Reveiver作为kafka的消费者,
在接收或并且存储数据之后不会向kakfa发送确认
,所以kafka的消息消费信息与Spark之间是不同步的,假如kafka的消息传出去了,但是Spark挂掉了,那么此时假如Spark的Receiver接受的数据持久化在内存中并且还没来得及处理,此时数据就会丢失,Spark下次也只能从最新的数据进行读取。此时的消费语义是at-mostly-once
解决弊端:
在Spark1.2之后,未确保数据0丢失,Spark引入了启用
Write-Ahead-Logs
,将kafka的数据写入分布式文件系统(HDFS)的WAL上,以便在发生故障的时候回复所有数据如何启用WAL请参考:Spark启用WAL,往下翻一点
如果使用Receiver高级API,还有重要的几点需要注意:
- kafka中的topic-partition与在SparkStreaming中生成的RDD的分区是没有关系的,所以在KafkaUtils.createStream(…,partitions_nums)中加partitions_nums的值只能增加拉取数据的并行度,不能增加Spark消费的并行度
- 多个kafka的DStream可以以不同的消费者组和topics来实现多个Receiver并行拉取
- 如果开启Write-Ahead-Logs基于HDFS类似的文件系统,数据会被实现存储在日志中,因此,再通过KafkaUtils.createStream(,StorageLevels.level)必须指定指定StorageLevels.level为:
StorageLevels.MEMORY_AND_DISK_SER
以序列化方式存储
使用代码如下:
import org.apache.spark.streaming.kafka._
val kafkaStream = KafkaUtils.createStream(streamingContext,
[ZK quorum], [consumer group id], [per-topic number of Kafka partitions to consume])
spark.streaming.receiver.*=???
二、Direct-based处理
Direct消息接收处理方式在Spark1.3的时候被引入,这个摒弃了用Receiver的方式去接受kafka数据,而是直接从kafka的topic-partition去查询lastest-offsets的数据,然后根据定义的offset-ranges处理相应的批次数据,该方法在存储数据之后会向kafka集群发送确认
整个过程经过是:)
output算子出发并启动处理数据的job =>
Direct简单Conusmer-API就从kafka的topic+partitions中读取offsets-ranges的数据 =>
开始处理。。。。。。
这种处理方式相比Receiver方式有以下提升:
-
简单了并行度处理
- 增加处理数据的并行度,我们不需要将多个Receiver的Dstream进行union操作,然后repartition,SparkStreaming会自动创建分区数与kafka的partitions数量相同的RDD去并行处理数据,提升吞吐量,这将会并行从kafka读取数据,
RDD分区数与Kafka的分区数是(1 to 1)的mapping关系
,这样就容易去理解和调优
- 增加处理数据的并行度,我们不需要将多个Receiver的Dstream进行union操作,然后repartition,SparkStreaming会自动创建分区数与kafka的partitions数量相同的RDD去并行处理数据,提升吞吐量,这将会并行从kafka读取数据,
-
提升了效率
- WAL-Receiver的方式,需要将数据copy2次,一次是kafka的follower从leaer进行复制,然后穿行得执行将数据以序列化的方式拷贝到WAL
- 而Direct的方式没有接收器,不需要WAL,消息可以直接从kafka中进行恢复
-
Exactly-once的语义
-
Receiver高级API将offset保存在Zookeeper中,
如果不用WAL,那么数据消费语义是at-most once
,假如使用WAL,那么虽然能保证at-least once
,但是message可能被消费2次在一些failure的情况下; -
Receiver方式为什么会消费2次呢?因为kafka的消息由Spark消费,但是offsets保存在ZK中,很难保证消费的一致性【举个栗子,假如SparkSreaming接受到了offset为10的数据,ZK此时还没更新offsets信息,还停留在5,此时kafka挂了,下次启动后那么又从5开始拉取数据到Spark,此时10之前的数据刚消费完,5-10的数据又来了,得重新消费一遍】
-
Direct方式offset的存储已经不需要依赖zookeeper,offset的追踪由SparkStreaming的Checkpoint操作来完成,而且还支持其他方式,比如可以调用
consumer.asyncCommit
方法存在kafka的特定topic中,还可以将以用户自定义的方法将sparkStreaming的output操作与offsets提交绑定到一个事务操作中,手动将offsets提交到第三方的存储系统如:MySQL\Redis
-
简单的使用伪代码如下:
import org.apache.spark.streaming.kafka._
// createDirectStream方法有多种重载形式
val directKafkaStream = KafkaUtils.createDirectStream[
[key class], [value class], [key decoder class], [value decoder class] ](
streamingContext, [map of Kafka parameters], [set of topics to consume])
每个RDD对应一个分区的offset_ranges的数据:
// 为当前的offset—ranges败柳一个引用, 保证它能在下游DStream中能被使用
var offsetRanges = Array.empty[OffsetRange]
//请注意,只有在directKafkaStream上调用的第一个方法中完成了对HasOffsetRanges的类型转换,此转换才会成功
directKafkaStream.transform { rdd =>
offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
rdd
}.map {
...
}.foreachRDD { rdd =>
for (o <- offsetRanges) {
println(s"${o.topic} ${o.partition} ${o.fromOffset} ${o.untilOffset}")
}
...
}
Direct相关的配置如下:
spark.streaming.kafka.*=???
#Direct API读取每个kafka分区的最大速率(messages/s)为单位,这个参数比较重要
spark.streaming.kafka.maxRatePerPartition=??