Spark_SparkStreaming Kafka Direct/Receiver 两种连接方式的区别

 

参考文章:

DirectStream、Stream的区别-SparkStreaming源码分析02

https://blog.csdn.net/wisgood/article/details/51815853

 

spark-kafka direct方式读取和receiver方式读取的区别

https://blog.csdn.net/wzqllwy/article/details/78869889

 

 

  最近在面试中被问到了两种连接方式的区别,对Receiver 获取数据方式没什么印象,只回答了 Direct 方式。现在整理一下,以备不时之需。不过Receiver 已经是非常古老的方式了,在 Spark 2.3 中已经不提倡使用了,未来会被废弃。

   我认为其实这并不是一个很好的面试题,因为Receiver 已经属于上古时期的方式了,我们这里只做了解,实际使用的概率极低。

 

下面基于目前Spark 的最新版本 Spark 2.4.0 进行讲解。

首先看下这两种方式 在目前的 spark-streaming-kafka 的支持 :

Spark_SparkStreaming Kafka Direct/Receiver 两种连接方式的区别_第1张图片

  

   可以看到 Receiver 方式只有在  streaming-kafka-0-8 才有支持,在较新的 streaming-kafka-0-10 已经不再支持,这两个版本的区别在于支持的Kafka 版本不同。 streaming-kafka-0-8 支持 kafka 0.8.0 + , 而 streaming-kafka-0-10 支持 kafka 0.10.0 + 。

  笔者在写这篇文章的时候,kafka 已经更新到了  2.0.0, 所以 streaming-kafka-0-10 已经算一个非常成熟稳定的版本了,推荐使用  streaming-kafka-0-10 。

 

这个是这两种方式的官网介绍:

streaming-kafka-0-8

http://spark.apache.org/docs/latest/streaming-kafka-0-8-integration.html#approach-2-direct-approach-no-receivers

 

streaming-kafka-0-10

http://spark.apache.org/docs/latest/streaming-kafka-0-10-integration.html

 

 

针对于 0.8 我们截取了额外的信息:

Spark_SparkStreaming Kafka Direct/Receiver 两种连接方式的区别_第2张图片

streaming-kafka-0-8  在 spark 2.3.0 + 已经废弃了,所以 receiver 也不再支持了

 

下面讲解下2种方式的区别,我们其实可以参考下官网 streaming-kafka-0-8  这个文章,里面介绍的比较详细,下面我们更进一步介绍:我们从概念上跟原理上去分析一下。

 

一、基于Receiver的方式

 

理论层次

这种方式使用Receiver来获取数据。Receiver是使用Kafka的高层次Consumer API来实现的。receiver从Kafka中获取的数据都是存储在Spark Executor的内存中的,然后Spark Streaming启动的job会去处理那些数据。

然而,在默认的配置下,这种方式可能会因为底层的失败而丢失数据。如果要启用高可靠机制,让数据零丢失,就必须启用Spark Streaming的预写日志机制(Write Ahead Log,WAL)Spark 1.3.0 + 支持。该机制会同步地将接收到的Kafka数据写入分布式文件系统(比如HDFS)上的预写日志中。所以,即使底层节点出现了失败,也可以使用预写日志中的数据进行恢复。
需要注意的要点

1、Kafka中的topic的partition,与Spark中的RDD的partition是没有关系的。所以,在KafkaUtils.createStream()中,提高partition的数量,只会增加一个Receiver中,读取partition的线程的数量。不会增加Spark处理数据的并行度。

2、可以创建多个Kafka输入DStream,使用不同的consumer group和topic,来通过多个receiver并行接收数据。

3、如果基于容错的文件系统,比如HDFS,启用了预写日志机制,接收到的数据都会被复制一份到预写日志中。因此,在KafkaUtils.createStream()中,设置的持久化级别是StorageLevel.MEMORY_AND_DISK_SER。
 

源码层次

1、 KafkaUtils.createStream

首先从源码层面来看,其主要调用栈顺序:

KafkaUtils.createStream--->createStream--->new KafkaInputDStream--->new KafkaReceiver

KafkaReceiver类继承了Receiver,当Reciver被调用起来时,执行onStart()方法,MessageHandler负责将收到的数据进行存储。执行流程如下:

  1. 创建createStreamReceiver被调起执行
  2. 连接ZooKeeper,读取相应的ConsumerTopic配置信息等
  3. 通过consumerConnector连接到Kafka集群,收取指定topic的数据
  4. 创建KafkaMessageHandler线程池来对数据进行处理,通过ReceiverInputDStream中的方法,将数据转换成BlockRDD,供后续计算

 

二、基于Direct的方式

    这种新的不基于Receiver的直接方式,是在Spark 1.3中引入的,从而能够确保更加健壮的机制。替代掉使用Receiver来接收数据后,这种方式会周期性地查询Kafka,来获得每个topic+partition的最新的offset,从而定义每个batch的offset的范围。当处理数据的job启动时,就会使用Kafka的简单consumer api来获取Kafka指定offset范围的数据。

这种方式有如下优点:

1、简化并行读取:如果要读取多个partition,不需要创建多个输入DStream然后对它们进行union操作。Spark会创建跟Kafka partition一样多的RDD partition,并且会并行从Kafka中读取数据。所以在Kafka partition和RDD partition之间,有一个一对一的映射关系。

2、高性能如果要保证零数据丢失,在基于receiver的方式中,需要开启WAL机制。这种方式其实效率低下,因为数据实际上被复制了两份,Kafka自己本身就有高可靠的机制,会对数据复制一份,而这里又会复制一份到WAL中。而基于direct的方式,不依赖Receiver,不需要开启WAL机制,只要Kafka中作了数据的复制,那么就可以通过Kafka的副本进行恢复。

3、一次且仅一次的事务机制:

基于receiver的方式,是使用Kafka的高阶API来在ZooKeeper中保存消费过的offset的。这是消费Kafka数据的传统方式。这种方式配合着WAL机制可以保证数据零丢失的高可靠性,但是却无法保证数据被处理一次且仅一次,可能会处理两次。因为Spark和ZooKeeper之间可能是不同步的。

4、降低资源。
Direct不需要Receivers,其申请的Executors全部参与到计算任务中;而Receiver-based则需要专门的Receivers来读取Kafka数据且不参与计算。因此相同的资源申请,Direct 能够支持更大的业务。

5、降低内存。
Receiver-based的Receiver与其他Exectuor是异步的,并持续不断接收数据,对于小业务量的场景还好,如果遇到大业务量时,需要提高Receiver的内存,但是参与计算的Executor并无需那么多的内存。而Direct 因为没有Receiver,而是在计算时读取数据,然后直接计算,所以对内存的要求很低。实际应用中我们可以把原先的10G降至现在的2-4G左右。

6、鲁棒性更好。
Receiver-based方法需要Receivers来异步持续不断的读取数据,因此遇到网络、存储负载等因素,导致实时任务出现堆积,但Receivers却还在持续读取数据,此种情况很容易导致计算崩溃。Direct 则没有这种顾虑,其Driver在触发batch 计算任务时,才会读取数据并计算。队列出现堆积并不会引起程序的失败。

基于direct的方式,使用kafka的简单api,Spark Streaming自己就负责追踪消费的offset,并保存在checkpoint中。Spark自己一定是同步的,因此可以保证数据是消费一次且仅消费一次。
 

 

源码层次

 

2、 KafkaUtils.createDirectStream

主要调用栈顺序:

KafkaUtils.createDirectStream—> new DirectKafkaInputDStream

执行流程如下:

  1. 实例化KafkaCluster,根据用户配置的Kafka参数,连接Kafka集群
  2. 通过Kafka API读取Topic中每个Partition最后一次读的Offset 可以参考我的 HW, LEO 等概念的参考文章:https://blog.csdn.net/u010003835/article/details/88683871
  3. 接收成功的数据,直接转换成KafkaRDD,供后续计算

 

你可能感兴趣的:(Spark,Kafka)