【Spark】SparkStreaming实时处理入门案例SparkStreaming编程整合HDFS整合Kafka(九)

SparkStreaming实时处理入门案例

编码案例

package chapter6

import org.apache.spark.streaming.dstream.{DStream, ReceiverInputDStream}
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.streaming.{Seconds, StreamingContext}

object WordCount {
  def main(args: Array[String]): Unit = {
    //创建SparkStreaming程序入口
    val conf: SparkConf = new SparkConf().setAppName("wc").setMaster("local[2]")
    val sc = new SparkContext(conf)
    val ssc = new StreamingContext(sc,Seconds(5))
    //设置日志级别
    sc.setLogLevel("WARN")
    //加载数据
    val file: ReceiverInputDStream[String] = ssc.socketTextStream("hadoop101",9999)
    //针对分隔符进行切分
    val spliFile: DStream[String] = file.flatMap(_.split(" "))
    //每个单词记为1次
    val wordAndOne: DStream[(String, Int)] = spliFile.map((_,1))
    //聚合
    val wordAndCount: DStream[(String, Int)] = wordAndOne.reduceByKey(_+_)
    //打印输出
    wordAndCount.print()
    //开启sparkStreaming
    ssc.start()
    //让其一直开启,等待关闭
    ssc.awaitTermination()
  }
}

在终端通过netcat来模拟网络数据,运行streaming程序,然后观察控制台输出,如图-9所示。下载Netcat发送数据:yum install -y nc。
在这里插入图片描述

图-9 netcat测试streaming程序
按照Spark Core中的方式进行打包,并将程序上传到Spark机器上运行:

bin/spark-submit --master spark://hadoop101:7077 --class WorldCount /root/SparkDay01-1.0-SNAPSHOT.jar

StreamingContext和Receiver
1)StreamingContext:StreamingContext是程序的入口类,用于创建DStream,维护SparkStreaming程序的生命周期。
关于local说明:当我们将上述程序中的master由local[*],修改为local的时候,程序业务不变,发生只能接收数据,无法处理数据,如图-10所示。
在这里插入图片描述

图-10 local无法消费数据
local[*]和local的区别,后者只为当前程序提供一个线程来处理,前者提供可用的所有的cpu的core来处理,当前情况下为2或者4。
所以我们推测,当前程序无法处理数据的原因,只能是拥有cpu-core或者线程个数造成的。
同时还可以推导出来的是,SparkStreaming在当下案例中,优先使用线程资源来接收数据,其次才是对数据的处理,接收数据的对象就是Receiver。
所以,如果读取数据的时候有receiver,程序的线程个数至少为2。
Start:start方法是用来启动当前sparkStreaming应用的,所以,是不能在ssc.start()之后再添加任何业务逻辑,否则,凉凉,如图-11所示!
在这里插入图片描述

图-11 start之后不可有其他算子操作
awaitTermination:要想持续不断的执行streaming计算,就必须要调用awaitTermination方法,以便driver能够在后台常驻,其核心原理如图-12所示。
在这里插入图片描述

图-12 awaitTermination核心原理
2)Receiver:Receiver,顾名思义,就是数据的接收者,这里把资源分成了两部分,一部分用来接收数据,一部分用来处理数据。Receiver接收到的数据,说白了就是一个个的batch数据,是RDD,存储在Executor内存。Receiver就是Executor内存中的一部分。
不是所有的streaming作业都需要有Receiver。
通过图-13,来阐述基于Receiver的程序执行的流程。
在这里插入图片描述

图-13 receiver接收并启动计算核心原理

SparkStreaming编程

SparkStreaming整合HDFS
说明:SparkStreaming监听hdfs的某一个目录,目录下的新增文件,做实时处理。这种方式在特定情况下还是挺多的。需要使用的api为:ssc.fileStream()。
1)监听文件:必须要从另一个相匹配的目录移动到其它目录。
2)监听本地:无法读取手动拷贝,或者剪切到指定目录下的文件,只能读取通过流写入的文件。
3)监听hdfs:有的操作系统和监听本地是一样。正常情况下,我们可以读取到通过put上传的文件,还可以读取通过cp拷贝的文件,但是读取不了mv移动的文件,读取文件的这种方式,没有额外的Receiver消耗线程资源,所以可以指定master为local。
基本编程:

object SparkStreamingHDFS {
    def main(args: Array[String]): Unit = {
        Logger.getLogger("org.apache.hadoop").setLevel(Level.WARN)
        Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
        Logger.getLogger("org.spark_project").setLevel(Level.WARN)
        val conf = new SparkConf()
            .setAppName("SparkStreamingHDFS")
            .setMaster("local")
        val duration = Seconds(2)
        val ssc = new StreamingContext(conf, duration)
        //读取local中数据 --->需要通过流的方式写入
//        val lines = ssc.textFileStream("file:///E:/data/monitored")
        //hdfs
        val lines = ssc.textFileStream("hdfs://hadoop101:9000/data/spark")
        lines.print()
        ssc.start()
        ssc.awaitTermination()
    }
}

SparkStreaming整合Kafka
kafka是做消息的缓存,数据和业务隔离操作的消息队列,而sparkstreaming是一款准实时流式计算框架,所以二者的整合,是大势所趋。
二者的整合,有主要的两大版本,如图-14所示:
在这里插入图片描述

图-14 streaming和kafka整合版本
在spark-stremaing-kafka-0-8的版本中又分为了两种方式:receiver的方式和direct的方式来读取kafka中的数据,主要区别就是是否依赖zookeeper来管理offset信息,以及是否拥有receiver。
但需要说明的0.8的版本自spark2.3之后便进入不维护阶段,此时建议大家使用0.10的版本,在此讲义中我们就是0.10的版本来进行讲述二者的整合。
Spark对于Kafka的连接主要有两种方式,一种是Direct方式直连模式,另外一种是Receiver方式接收器模式。Direct方式只在driver端接收数据,所以继承了InputDStream,是没有receivers的。
主要通过KafkaUtils#createDirectStream以及KafkaUtils#createStream这两个API来创建,除了要传入的参数不同外,接收kafka数据的节点、拉取数据的时机也完全不同。
Receiver方式
Receiver:接收器模式是使用Kafka高级Consumer API实现的。与所有接收器一样,从Kafka通过Receiver接收的数据存储在Spark Executor的内存中,然后由Spark Streaming启动的job来处理数据。然而默认配置下,这种方式可能会因为底层的失败而丢失数据(请参阅接收器可靠性)。如果要启用高可靠机制,确保零数据丢失,要启用Spark Streaming的预写日志机制(Write Ahead Log,(已引入)在Spark 1.2)。该机制会同步地将接收到的Kafka数据保存到分布式文件系统(比如HDFS)上的预写日志中,以便底层节点在发生故障时也可以使用预写日志中的数据进行恢复。
单点读数据,读到的数据会缓存到executor的cache里,增大了内存使用的压力。
在Receiver的方式中,使用的是Kafka的高阶API接口从Zookeeper中获取offset值,这也是传统的从Kafka中读取数据的方式,但由于Spark Streaming消费的数据和Zookeeper中记录的offset不同步,这种方式偶尔会造成数据重复消费。
特点
1)在spark的executor中,启动一个接收器,专门用于读取kafka的数据,然后存入到内存中,供sparkStreaming消费。
2)为了保证数据0丢失,WAL,数据会保存2份,有冗余。
3)Receiver是单点读数据,如果挂掉,程序不能运行。
4)数据读到executor内存中,增大了内存使用的压力,如果消费不及时,会造成数据积压。如图-15:
在这里插入图片描述

图-15 Receiver方式
对于使用Maven项目定义的Scala/Java应用程序时,我们需要添加相应的依赖包:

<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-streaming-kafka-0-10_2.12</artifactId>
    <version>3.2.1</version>
</dependency>

在流应用程序代码中,导入KafkaUtils并创建输入DStream,如下所示。

import org.apache.spark.streaming.kafka._
val kafkaStream = KafkaUtils.createStream(streamingContext,
[ZK quorum], [consumer group id], [per-topic number of Kafka partitions to consume])

还有几个需要注意的点:
1)Kafka中topic的partition与Spark Streaming中生成的RDD的partition无关,因此,在KafkaUtils.createStream()中,增加某个topic的partition的数量,只会增加单个Receiver消费topic的线程数,也就是读取Kafka中topic partition的线程数量,它不会增加Spark在处理数据时的并行性。
2)可以使用不同的consumer group和topic创建多个Kafka输入DStream,以使用多个receiver并行接收数据。
Direct方式
Direct:直连模式,在spark1.3之后,引入了Direct方式。不同于Receiver的方式,Direct方式没有receiver这一层,其会周期性的获取Kafka中每个topic的每个partition中的最新offsets,并且相应的定义要在每个batch中处理偏移范围,当启动处理数据的作业时,kafka的简单的消费者api用于从kafka读取定义的偏移范围。其形式如图-16:
在这里插入图片描述

图-16 Direct方式
这种方法相较于Receiver方式的优势在于:
1)简化的并行:在Receiver的方式中我们提到创建多个Receiver之后利用union来合并成一个Dstream的方式提高数据传输并行度。而在Direct方式中,Kafka中的partition与RDD中的partition是一一对应的并行读取Kafka数据,这种映射关系也更利于理解和优化。
2)高效:在Receiver的方式中,为了达到0数据丢失需要将数据存入Write Ahead Log中,这样在Kafka和日志中就保存了两份数据,浪费!而第二种方式不存在这个问题,只要我们Kafka的数据保留时间足够长,我们都能够从Kafka进行数据恢复。
3)精确一次:在Receiver的方式中,使用的是Kafka的高阶API接口从Zookeeper中获取offset值,这也是传统的从Kafka中读取数据的方式,但由于Spark Streaming消费的数据和Zookeeper中记录的offset不同步,这种方式偶尔会造成数据重复消费。而第二种方式,直接使用了简单的低阶Kafka API,Offsets则利用Spark Streaming的checkpoints进行记录,消除了这种不一致性。
请注意,此方法的一个缺点是它不会更新Zookeeper中的偏移量,因此基于Zookeeper的Kafka监视工具将不会显示进度。但是,您可以在每个批处理中访问此方法处理的偏移量,并自行更新Zookeeper。
直连模式特点:
1)batch time 每隔一段时间,去kafka读取一批数据,然后消费。
2)简化并行度,rdd的分区数量=topic的分区数量。
3)数据存储于kafka中,没有数据冗余。
4)不存在单点问题。
5)效率高。
6)可以实现仅消费一次的语义exactly-once语义。
整合编码,从kafka读取数据:

import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.kafka.common.serialization.StringDeserializer
import org.apache.spark.SparkConf
import org.apache.spark.serializer.KryoSerializer
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.dstream.{DStream, InputDStream}
import org.apache.spark.streaming.kafka010.{ConsumerStrategies, KafkaUtils, LocationStrategies}
/*
    SparkStremaing从kafka中读取数据
  PerPartitionConfig
    spark.streaming.kafka.maxRatePerPartition
    spark.streaming.kafka.minRatePerPartition
            都是代表了streaming程序消费kafka速率,
                max: 每秒钟从每个分区读取的最大的纪录条数
                    max=10,分区个数为3,间隔时间为2s
                        所以这一个批次能够读到的最大的纪录条数就是:10*3*2=60
                        如果配置的为0,或者不设置,起速率没有上限
                min: 每秒钟从每个分区读取的最小的纪录条数
              那么也就意味着,streaming从kafka中读取数据的速率就在[min, max]之间
    执行过程中出现序列化问题:
serializable (class: org.apache.kafka.clients.consumer.ConsumerRecord, value: ConsumerRecord
     spark中有两种序列化的方式:
     默认的就是java序列化的方式,也就是写一个类 implement Serializable接口,这种方式的有点事非常稳定,但是一个非常的确定是性能不佳
     还有一种高性能的序列化方式——kryo的序列化,性能非常高,官方给出的数据是超过java的序列化性能10倍,同时在使用的时候只需要做一个声明式的注册即可
sparkConf.set("spark.serializer", classOf[KryoSerializer].getName)//指定序列化的方式
  .registerKryoClasses(Array(classOf[ConsumerRecord[String, String]]))
//注册要序列化的类
 */
object StreamingFromKafkaOps {
    def main(args: Array[String]): Unit = {
        val conf = new SparkConf()
            .setMaster("local")
            .setAppName("StreamingFromKafkaOps")
//            .set("spark.serializer", classOf[KryoSerializer].getName)
//            .registerKryoClasses(Array(classOf[ConsumerRecord[String, String]]))
        //两次流式计算之间的时间间隔,batchInterval
        val batchDuration = Seconds(2) // 每隔2s提交一次sparkstreaming的作业
        val ssc = new StreamingContext(conf, batchDuration)
        val topics = Set("hadoop")
        val kafkaParams = Map[String, Object](
            "bootstrap.servers" -> "hadoop101:9092,hadoop102:9092,hadoop103:9092",
            "key.deserializer" -> classOf[StringDeserializer],
            "value.deserializer" -> classOf[StringDeserializer],
            "group.id" -> "spark-kafka-grou-0817",
            "auto.offset.reset" -> "earliest",
            "enable.auto.commit" -> "false")
        /*
            从kafka中读取数据
            locationStrategy:位置策略
            制定如何去给特定的topic和partition来分配消费者进行调度,可以通过LocationStrategies来得到实例。
             在kafka0.10之后消费者先拉取数据,所以在适当的executor来缓存executor操作对于提高性能是非常重要的。
             PreferBrokers:
             如果你的executor在kafka的broker实例在相同的节点之上可以使用这种方式。
             PreferConsistent:
             大多数情况下使用这个策略,会把partition分散给所有的executor
             PreferFixed:
             当网络或者设备性能等个方便不均衡的时候,可以蚕蛹这种方式给特定executor来配置特定partition。
            不在这个map映射中的partition使用PreferConsistent策略
            consumerStrategy:消费策略
            配置在driver或者在executor上面创建的kafka的消费者。该接口封装了消费者进程信息和相关的checkpoint数据
            消费者订阅的时候的策略:
            Subscribe: 订阅多个topic进行消费,这多个topic用集合封装
            SubscribePattern: 可以通过正则匹配的方式,来订阅多个消费者,比如订阅的topic有 aaa,aab,aac,adc,可以通过a[abc](2)来表示
            Assign:指定消费特定topic的partition来进行消费,是更加细粒度的策略
         */
        val message:InputDStream[ConsumerRecord[String, String]] = KafkaUtils.createDirectStream(ssc, LocationStrategies.PreferConsistent,
                                                                    ConsumerStrategies.Subscribe(topics, kafkaParams))
//        message.print()//直接打印有序列化问题
        message.foreachRDD((rdd, bTime) => {
            if(!rdd.isEmpty()) {
                println("-------------------------------------------")
                println(s"Time: $bTime")
                println("-------------------------------------------")
                rdd.foreach(record => {
                    println(record)
                })
            }
        })
        ssc.start()
        ssc.awaitTermination()
    }
}
  • 18
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZikH~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值