SparkStreamingReceiver&Direct方式的消息处理方式对比和代码示意

SparkStreaming2种不同的版本集成

SparkStreaming与kafka的集成有2套

一、Spark-streaming-kafka-0-8

二、Spark-streaming-kafka-0-10

Spark-streaming-kafka-0-8Spark-streaming-kafka-0-10
支持的kafka-Broker版本0.8.2.1 或 更高版本0.10.0 或 更高版本
目前是否被遗弃deprecated after spark2.3.0Stable
支持语言Scala, java,pythonScala,java
是否支持Receiver StreamYesNo
是否支持Direct StreamYesYes
SSL/TLS支持NOYes
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])

相关的Receiver配置如下

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关系,这样就容易去理解和调优
  • 提升了效率

    • 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=??
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值