Spark Streaming + Kafka集成指南(Kafka代理版本0.10.0或更高版本)

一、概述

此文内容主要来自于官方文档,并且使用spark streaming 消费kafka的数据进行实时计算,经过自己测试实验进行一个总结。
spark 版本:2.4.0
kafka 版本:0.10
scala版本:2.11

Kafka 0.10的Spark Streaming集成在设计上类似于0.8 Direct Stream方法。它提供简单的并行性,Kafka分区和Spark分区之间的1:1对应关系以及对偏移量和元数据的访问。但是,由于较新的集成使用了新的Kafka consumer API而不是简单的API,因此用法上存在显著差异。集成的此版本标记为实验性的,因此API可能会发生更改。

二、Spark Streaming 整合kafka步骤

1、引入依赖

对于使用SBT / Maven项目定义的Scala / Java应用程序,将流式应用程序与以下artifact链接

groupId = org.apache.spark
artifactId = spark-streaming-kafka-0-10_2.11
version = 2.4.0

不要手动添加对org.apache.kafka 的依赖(例如kafka-clients)。该spark-streaming-kafka-0-10已经具有适当的传递依赖关系,并且不同的版本可能不兼容。

这里使用maven来管理spark streaming的依赖,我们可以去maven仓库中搜索spark-streaming-kafka-0-10,找到相应的依赖,我这里使用的是cdh集群安装的spark,所以选择Cloudera相关的依赖,如下图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
查看spark-streaming-kafka-0-10jar包的位置在cloudera-repos仓库中

cloudera-repos仓库地址:https://repository.cloudera.com/artifactory/cloudera-repos/

所以在pom.xml文件中需要添加<repository>的地址才能下载对应的依赖,此次spark streaming 与kafka的整合还需要Spark-streaming 和 Spark-core的依赖,完整的pom.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>spark-streaming-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <repositories>
        <repository>
            <id>scala-tools.org</id>
            <name>Scala-Tools Maven2 Repository</name>
            <url>http://scala-tools.org/repo-releases</url>
        </repository>
        <repository>
            <id>cloudera</id>
            <name>cloudera repository</name>
            <url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>scala-tools.org</id>
            <name>Scala-Tools Maven2 Repository</name>
            <url>http://scala-tools.org/repo-releases</url>
        </pluginRepository>
    </pluginRepositories>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.apache.spark/spark-streaming-kafka-0-10 -->
        <!-- spark streaming 与kafka整合的依赖包 -->
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-streaming-kafka-0-10_2.11</artifactId>
            <version>2.4.0-cdh6.3.1</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.spark/spark-core -->
        <!-- spark core, 创建SparkConf需要的依赖-->
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-core_2.11</artifactId>
            <version>2.4.0-cdh6.3.1</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.spark/spark-streaming -->
        <!-- spark streaming ,创建StreamingContext需要的依赖-->
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-streaming_2.11</artifactId>
            <version>2.4.0-cdh6.3.1</version>
        </dependency>
    </dependencies>

    <build>
        <sourceDirectory>src/main/scala</sourceDirectory>
        <plugins>
            <!--编译scala文件的插件-->
            <plugin>
                <groupId>org.scala-tools</groupId>
                <artifactId>maven-scala-plugin</artifactId>
                <version>2.15.2</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <scalaVersion>2.11</scalaVersion>
                </configuration>
            </plugin>
            <!--打jar包的插件-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.0.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>com.southcn.nfplus.recall.VideoRecallStreaming</mainClass>
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

        </plugins>
    </build>
    <reporting>
        <plugins>
            <plugin>
                <groupId>org.scala-tools</groupId>
                <artifactId>maven-scala-plugin</artifactId>
                <configuration>
                    <scalaVersion>2.11</scalaVersion>
                </configuration>
            </plugin>
        </plugins>
    </reporting>
</project>


2、创建 Direct Stream

请注意,导入的名称空间包括版本org.apache.spark.streaming.kafka010,scala代码如下:


package spark.streaming.demo

import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.InputDStream
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.kafka010.{CanCommitOffsets, ConsumerStrategies, HasOffsetRanges, KafkaUtils, LocationStrategies}
import org.slf4j.{Logger, LoggerFactory}


object KafkaUtil {
    // kafka的broker地址
    val brokers: String = "cdh1:9092,cdh2:9092,cdh3:9092"
    // 每一个stream使用一个独立的group.id
    val groupId: String = "kafka_demo"
    // 要消费的topic,接受一个数组,可以传入多个topic
    val topics: Array[String] = Array("topicA", "topicB")
    // 用于记录日志
    val log: Logger = LoggerFactory.getLogger(this.getClass)

    val kafkaParams: Map[String, Object] = Map[String, Object](
        "bootstrap.servers" -> brokers,
        "key.deserializer" -> classOf[StringDeserializer],
        "value.deserializer" -> classOf[StringDeserializer],
        "group.id" -> groupId,
        "auto.offset.reset" -> "latest",
        "enable.auto.commit" -> (false: java.lang.Boolean)
    )

    def getKafkaStream(ssc: StreamingContext, topics: Array[String]): InputDStream[ConsumerRecord[String, String]] ={
        // 使用KafkaUtils.createDirectStream创建数据流
        val stream: InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream[String, String](
            ssc,
            LocationStrategies.PreferConsistent,
            ConsumerStrategies.Subscribe[String, String](topics, kafkaParams)
        )
        stream
    }

    def main(args: Array[String]): Unit = {

        val sparkConf: SparkConf = new SparkConf().setMaster("local[*]").setAppName(this.getClass.getName)
        val ssm: StreamingContext = new StreamingContext(sparkConf, Seconds(10))

        val inputDStream: InputDStream[ConsumerRecord[String, String]] = getKafkaStream(ssm, topics)

        inputDStream.foreachRDD { rdd =>
            // 获取偏移量
            val offsetRanges = rdd.asInstanceOf[HasOffsetRanges].offsetRanges
            rdd.foreachPartition {iter =>
                iter.foreach { consumerRecord =>
                    val key: String = consumerRecord.key()
                    val value: String = consumerRecord.value()
                    println(s"key: ${key}, value: ${value}")
                }
            }
            // 一段时间,计算完成之后, 异步提交偏移量
            inputDStream.asInstanceOf[CanCommitOffsets].commitAsync(offsetRanges)
        }

        ssm.start()
        ssm.awaitTermination()
    }


}

流中的每个项目都是一个ConsumerRecord

有关可能的kafkaParams的信息,请参阅Kafka Consumer config docs。如果您的Spark批处理持续时间大于默认的Kafka心跳会话超时(30秒),请适当增加heartbeat.interval.mssession.timeout.ms。对于大于5分钟的批处理,这将需要更改代理上的group.max.session.timeout.ms。请注意,示例将enable.auto.commit设置为false,有关讨论,请参见下面的存储偏移。

1、LocationStrategies 位置策略说明

新的Kafka consumer API将消息预取到缓冲区中。因此,出于性能原因,Spark集成时将缓存的consumer保留在执行程序上(而不是为每个批次重新创建它们),并且更喜欢调度partitions到在具有适当的consumer的主机上,这一点很重要。

在大多数情况下,您应该使用LocationStrategies.PreferConsistent如上所示。这将在可用执行程序之间平均分配分区。如果您的executorsKafka broker位于同一主机上,请使用PreferBrokers,它将首选在Kafka leader上为该分区调度分区。最后,如果分区之间的负载有明显的偏差,请使用PreferFixed。这使您可以指定分区到主机的显式映射(任何未指定的分区将使用一致的位置)。

consumers的缓存的默认最大大小为64。如果您希望处理超过(64 *执行程序数)个Kafka分区,则可以通过更改此设置spark.streaming.kafka.consumer.cache.maxCapacity

如果您想为Kafka consumers禁用缓存,可以设置spark.streaming.kafka.consumer.cache.enabled为false。要解决SPARK-19185中描述的问题,可能需要禁用缓存。解决SPARK-19185后,可以在更高版本的Spark中删除此属性。

缓存由topicpartition和group.id设置密钥,因此对的每次调用都应单独 使用一个。group.idcreateDirectStream

2、ConsumerStrategies 消费者策略说明

新的Kafka consumers API具有多种不同的方式来指定主题,其中一些方式需要大量的post-object-instantiation设置。 ConsumerStrategies提供了一个抽象,即使从checkpoint重新启动后,Spark仍可以获取正确配置的consumers 。

ConsumerStrategies.Subscribe,如上所示,允许您订阅固定的主题集合。SubscribePattern允许您使用正则表达式指定感兴趣的主题。请注意,与0.8集成不同,在运行流期间使用Subscribe或SubscribePattern应该响应添加分区。最后,Assign允许您指定固定的分区集合。这三种策略都具有重载的构造函数,这些构造函数使您可以为特定分区指定起始偏移量。

如果您有上述选项无法满足的特定消费者设置需求,则ConsumerStrategy可以扩展公共类。

3、存储偏移量

发生故障时的Kafka交付语义取决于存储偏移量的方式和时间。Spark输出操作至少一次。因此,如果您希望等效于一次语义,则必须在等幂输出之后存储偏移量,或者在输出中将偏移量存储在原子事务中。通过这种集成,您可以按照增加可靠性(和代码复杂度)的顺序,使用3个选项来存储偏移量。

  • checkpoint
    如果启用Spark checkpointing,则偏移量将存储在检查点中。这很容易实现,但是有缺点。您的输出操作必须是幂等的,因为您将获得重复的输出。此外,如果您的应用程序代码已更改,则无法从检查点恢复。对于计划的升级,您可以通过与旧代码同时运行新代码来减轻这种情况(因为输出无论如何都需要等幂,因此它们不应冲突)。但是对于需要代码更改的计划外故障,除非有另一种方法来识别已知的起始偏移量,否则您将丢失数据。
  • kafka本身
    Kafka具有偏移提交API,该API在特殊的Kafka主题中存储偏移量。默认情况下,新使用者将定期自动提交偏移量。这几乎肯定不是您想要的,因为由使用者成功轮询的消息可能尚未导致Spark输出操作,从而导致语义未定义。这就是为什么上面的流示例将“ enable.auto.commit”设置为false的原因。但是,您可以使用commitAsyncAPI在知道存储了输出之后将偏移量提交给Kafka 。与检查点相比,它的好处是,无论您对应用程序代码进行的更改如何,Kafka都是持久存储。但是,Kafka不是事务性的,因此您的输出必须仍然是幂等的。
  • 自己的数据存储,比如mysql
    对于支持事务的数据存储,即使在失败情况下,将偏移与结果保存在同一事务中也可以使两者保持同步。如果您在检测重复或跳过的偏移量范围时很谨慎,则回滚事务可防止重复或丢失的消息影响结果。这相当于一次语义。即使是由于聚合而产生的输出(通常很难使等幂),也可以使用此策略。

参考文档
官方文档:http://spark.apache.org/docs/2.4.0/streaming-kafka-0-10-integration.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值