栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

SparkStreaming学习记录

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

SparkStreaming学习记录

目录

一、流式计算简介

1.1. SparkStreaming简介

​编辑1.2. SparkStreaming基本工作原理

二、SparkStreaming实时处理入门 

2.1. 工程创建

2.2. 入口类StreamingContext

2.3. 实例练习

三、SparkStreaming与Kafka整合

3.1. 整合简述

3.2. 两种整合方式

3.3. 实操(Direct 方式)

四、SparkStreaming常见transformation算子

五、window

六、SparkSQL和SparkStreaming的整合案例


一、流式计算简介

大数据中的计算的数据通常可分为以下三大类:
1、一种是离线数据(T+1,周、月、季度、年等指标)

2、一种是实时数据(一条数据触发一次计算、较短时间触发一次计算、最近几秒、最近几分钟、最近几十分钟)

3、一种是准实时(交互式)(较短时间触发一次计算、最近几分钟、最近几十分钟)

1.1. SparkStreaming简介

        SparkStreaming,和SparkSQL一样,也是Spark生态栈中非常重要的一个模块,主要是用来进行流式计算的框架。流式计算框架,从计算的延迟上面,又可以分为纯实时流式计算和准实时流式计算,SparkStreaming是属于的准实时计算框架。

        纯实时的计算,指的是来一条记录(event事件),启动一次计算的作业;离线计算,指的是每次计算一个非常大的一批(比如几百G,好几个T)数据;准实时计算介于纯实时和离线计算之间的一种计算方式,不是每一条记录就计算一次,比离线计算数据量小的多,计算数据量可表示为Micro-batch(微小的批次)。

        SparkStreaming是SparkCore的api的一种扩展,使用DStream(discretized stream or DStream)作为数据模型,基于内存处理连续的数据流,本质上还是RDD的基于内存的计算。

        DStream,本质上是RDD的序列。SparkStreaming的处理流程可以归纳为下图:

1.2. SparkStreaming基本工作原理

        接收实时输入数据流,然后将数据拆分成多个batch,比如每收集1秒的数据封装为一个batch,然后将每个batch交给Spark的计算引擎进行处理,最后会生产出一个结果数据流,其中的数据,也是由一个一个的batch所组成的。

        Spark Streaming提供了一种高级的抽象,叫做DStream,英文全称为Discretized Stream,中文翻译为“离散流”,它代表了一个持续不断的数据流。DStream可以通过输入数据源来创建,比如Kafka、Flume、ZMQ和Kinesis;也可以通过对其他DStream应用高阶函数来创建,比如map、reduce、join、window。
        DStream的内部,其实一系列持续不断产生的RDD。RDD是Spark Core的核心抽象,即,分布式式弹性数据集。DStream中的每个RDD都包含了一个时间段内的数据。

         对DStream应用的算子,比如map,其实在底层会被翻译为对DStream中每个RDD的操作。比如对一个DStream执行一个map操作,会产生一个新的DStream。但是,在底层,其实其原理为,对输入DStream中每个时间段的RDD,都应用一遍map操作,然后生成的新的RDD,即作为新的DStream中的那个时间段的一个RDD。底层的RDD的transformation操作,还是由Spark Core的计算引擎来实现的。Spark Streaming对Spark Core进行了一层封装,隐藏了细节,然后对开发人员提供了方便易用的高层次的API。

二、SparkStreaming实时处理入门 

2.1. 工程创建

在pom.xml文件中导入SparkStreaming的maven依赖

  
        
            org.apache.spark
            spark-streaming_2.12
            3.1.2
        

在com package下创建一个新的package named sparkStreaming_example。

2.2. 入口类StreamingContext

SparkStreaming中的入口类,称之为StreamingContext,但是底层还是得需要依赖SparkContext。

object SparkStreamingTest {
    def main(args: Array[String]): Unit = {
        
        val conf = new SparkConf()
                    .setAppName("SparkStreamingTest")
                    .setMaster("local[*]")
        val duration = Seconds(2)
        val ssc = new StreamingContext(conf, duration)

        //业务
        
        
        //为了执行的流式计算,必须要调用start来启动
        ssc.start()
        
        ssc.awaitTermination()
    }
}

2.3. 实例练习

SparkStreaming是一个流式计算的计算引擎,那么就模拟一个对流式数据进行单词统计。

这里我们需要用到netcat,Netcat(简称nc)是一款强大的命令行网络工具,用来在两台机器之间建立TCP/UDP连接,并通过标准的输入输出进行数据的读写。

可以使用nc命令查看系统中是否有安装Netcat,如果没有安装,输入以下命令安装

yum install -y nc

使用场景:

1)与服务器建立连接

使用命令: nc 要建立连接的服务器地址 连接端口

功能:如 nc www.baidu.com 80 可与百度服务器建立TCP连接

连接后可向百度服务器发送GET请求,模拟浏览器访问百度首页,输入GET / HTTP/1.0并按两次回车

​

2)使用命令:nc -lk port号
说明: -l为listen,监听port端口 

-k 强制 nc 待命链接。当客户端从服务端断开连接后,过一段时间服务端也会停止监听。

但通过选项 -k 我们可以强制服务器保持连接并继续监听端口。
功能:启动了一个可以发送socket文本流的服务器的端口,该端口等待客户端来连接,端口号为port

退出重新进入时如出现

Ncat: bind to :::6666: Address already in use. QUITTING.
下载net-tools

yum install net-tools

通过命令netstat -nap | grep 6666找到占用进程

再通过命令kill -9 进程号杀死进程,重新监听就可以了

代码实现

package com.sparkStreaming_example

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

object StreamingWordCount {
  def main(args: Array[String]): Unit = {
    //1、获取SparkStreaming上下文环境
    val conf = new SparkConf().setAppName("streaming-wc").setMaster("local[*]")
    //Durations.seconds(5)  5秒处理一次数据
    val ssc: StreamingContext = new StreamingContext(conf, Durations.seconds(5))
    //2、获取数据源
    val ds: ReceiverInputDStream[String] = ssc.socketTextStream("user1", 6666)
    //3、对获取的数据进行加工处理
    val sumed: DStream[(String, Int)] = ds.filter(_.length > 0)
      .flatMap(_.split(" "))
      .map((_, 1))
      .reduceByKey(_ + _)
    //4、将结果数据进行持久化
    sumed.print()
    //5、开启流程序并等待结束信号
    ssc.start()
    ssc.awaitTermination() //等待结束信号
  }
}

三、SparkStreaming与Kafka整合

3.1. 整合简述

        kafka是做消息的缓存,数据和业务隔离操作的消息队列,而sparkstreaming是一款准实时流式计算框架,所以二者的整合,是大势所趋。

二者的整合,有主要的两大版本。

         kafka作为一个实时的分布式消息队列,实时的生产和消费消息,在实际开发中Spark Streaming经常会结合Kafka来处理实时数据。Spark Streaming 与 kafka整合需要引入spark-streaming-kafka.jar,该jar根据kafka版本有2个分支,分别是spark-streaming-kafka-0-8 和 spark-streaming-kafka-0-10。jar包分支选择原则:

  • 0.10.0>kafka版本>=0.8.2.1,选择 08 接口
  • kafka版本>=0.10.0,选择 010 接口

sparkStreaming和Kafka整合一般两种方式:Receiver方式和Direct方式

3.2. 两种整合方式

Receiver方式

        Receiver方式基于kafka的高级消费者API实现(高级优点:高级API写起来简单;不需要去自行去管理offset,系统通过zookeeper自行管理;不需要管理分区,副本等情况,系统自动管理;消费者断线会自动根据上一次记录在 zookeeper中的offset去接着获取数据;高级缺点:不能自行控制 offset;不能细化控制如分区、副本、zk 等)。Receiver从kafka接收数据,存储在Executor中,Spark Streaming 定时生成任务来处理数据。

       默认配置的情况,Receiver失败时有可能丢失数据。如果要保证数据的可靠性,需要开启预写式日志,简称WAL(Write Ahead Logs,Spark1.2引入),只有接收到的数据被持久化之后才会去更新Kafka中的消费位移。接收到的数据和WAL存储位置信息被可靠地存储,如果期间出现故障,这些信息被用来从错误中恢复,并继续处理数据。

Direct的方式

        Direct方式从Spark1.3开始引入的,通过 KafkaUtils.createDirectStream 方法创建一个DStream对象,Direct方式的结构如下图所示:

Direct 方式特点如下:

  • Offset 可自定义
  • 使用kafka低阶API
  • 底层实现为KafkaRDD

        该方式中Kafka的一个分区与Spark RDD对应,通过定期扫描所订阅Kafka每个主题的每个分区的最新偏移量以确定当前批处理数据偏移范围。与Receiver方式相比,Direct方式不需要维护一份WAL数据,由Spark Streaming程序自己控制位移的处理,通常通过检查点机制处理消费位移,这样可以保证Kafka中的数据只会被Spark拉取一次。

3.3. 实操(Direct 方式)
  • 引入依赖

    org.apache.spark
    spark-streaming-kafka-0-10_2.12
    3.1.2
  • 模拟kafka生产数据

运行程序前先启动kafka的消费者客户端

/usr/local/kafka-2.4.1/bin/kafka-console-consumer.sh 
--bootstrap-server user1:9092 
--topic test 
--from-beginning   

 kafka生产数据代码

package com.sparkStreaming_example

import org.apache.kafka.clients.producer.{KafkaProducer, ProducerRecord}
import java.util.{Properties, Random}


object DataToKafka {
  def main(args: Array[String]): Unit = {
    val prop = new Properties()
    //提供Kafka服务器信息
    prop.put("bootstrap.servers", "user1:9092")
    //指定响应的方式
    prop.put("acks", "all")
    //请求失败重试的次数
    prop.put("retries", "3")
    //指定key的序列化方式,key是用于存放数据对应的offset
    prop.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer")
    //指定value的序列化方式
    prop.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer")
    //创建producer对象
    val producer = new KafkaProducer[String, String](prop)
    //提供一个数组,数组中数据
    val arr = Array(
      "hello zhangsan",
      "hello lisi",
      "hello wangwu",
      "hello zhaoliu",
      "hello sunqi",
      "hello zhouba",
    )
    //提供一个随机数,随机获取数组中数据向kafka中进行发送存储
    val r = new Random()
    while (true) {
      val message = arr(r.nextInt(arr.length))
      producer.send(new ProducerRecord[String, String]("test", message))
      //      Thread.sleep(r.nextInt(1000))
      Thread.sleep(1000)
    }
  }
}

程序运行后,消费者客户端消费数据 

 

  • 实时消费kafka数据
package com.sparkStreaming_example

import org.apache.kafka.clients.consumer.ConsumerConfig
import org.apache.spark.SparkConf
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.kafka010.{ConsumerStrategies, KafkaUtils, LocationStrategies}

object StreamingWithKafka {
  def main(args: Array[String]): Unit = {
    //1.创建SparkConf对象
    val conf = new SparkConf()
      .setAppName("SparkStreamingWithKafka")
      .setMaster("local[*]")
    //2.提供批次时间
    val time = Seconds(3)
    //3.提供StreamingContext对象
    val sc = new StreamingContext(conf,time)
    //4.提供Kafka配置参数
    val kafka = Map[String,Object](
      ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG->"user1:9092",
      ConsumerConfig.GROUP_ID_CONFIG->"user",
      "key.deserializer"->"org.apache.kafka.common.serialization.StringDeserializer",
      "value.deserializer"->"org.apache.kafka.common.serialization.StringDeserializer",
    )
    //5.读取Kafka中数据信息生成DStream
    val value = KafkaUtils.createDirectStream(sc,
      //本地化策略:将Kafka的分区数据均匀的分配到各个执行Executor中
      LocationStrategies.PreferConsistent,
      //表示要从使用kafka进行消费【offset谁来管理,从那个位置开始消费数据】
      ConsumerStrategies.Subscribe[String, String](Set("test"), kafka)
    )
    //6.将每条消息kv获取出来
    val line: DStream[String] = value.map(record => record.value())
    //7.开始计算操作
    line.flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).print()
    //8.开始任务
    sc.start()
    sc.awaitTermination()
  }
}

运行程序后不断从kafka读取数据 

  

 说明

  • 简化的并行性:不需要创建多个输入Kafka流并将其合并。 使用directStream,Spark Streaming将创建与使用Kafka分区一样多的RDD分区,这些分区将全部从Kafka并行读取数据。 所以在Kafka和RDD分区之间有一对一的映射关系。
  • 效率:在第一种方法中实现零数据丢失需要将数据存储在预写日志中,这会进一步复制数据。这实际上是效率低下的,因为数据被有效地复制了两次:一次是Kafka,另一次是由预先写入日志(WriteAhead Log)复制。这个第二种方法消除了这个问题,因为没有接收器,因此不需要预先写入日志。只要Kafka数据保留时间足够长。
  • 正好一次(Exactly-once)的语义:第一种方法使用Kafka的高级API来在Zookeeper中存储消耗的偏移量。传统上这是从Kafka消费数据的方式。虽然这种方法(结合提前写入日志)可以确保零数据丢失(即至少一次语义),但是在某些失败情况下,有一些记录可能会消费两次。发生这种情况是因为Spark Streaming可靠接收到的数据与Zookeeper跟踪的偏移之间的不一致。因此,在第二种方法中,我们使用不使用Zookeeper的简单Kafka API。在其检查点内,Spark Streaming跟踪偏移量。这消除了Spark Streaming和Zookeeper/Kafka之间的不一致,因此Spark Streaming每次记录都会在发生故障的情况下有效地收到一次。为了实现输出结果的一次语义,将数据保存到外部数据存储区的输出操作必须是幂等的,或者是保存结果和偏移量的原子事务。

四、SparkStreaming常见transformation算子

cogroup

cogroup就是groupByKey的另外一种变体,groupByKey是操作一个K-V键值对,而cogroup一次操作两个,有点像join,不同之处在于返回值结果:

#cogroup()
val ds1:DStream[(K, V)]
val ds2:DStream[(K, w)]
val cg:DStream[(K, (Iterable[V], Iterable[W]))] = ds1.cogroup(ds2)
#groupByKey()
val gp:DStream[(K, Iterable[V])] = ds1.groupByKey()

updateStateByKey

  • updateStateByKey(func) 根据于key的前置状态和key的新值,对key进行更新,返回一个新状态的Dstream。
  • 功能:统计截止到目前为止key的状态。
  • 在这个操作中需要两个数据,一个是key的前置状态,一个是key的新增(当前批次的数据);还有历史数据(前置状态)得需要存储在磁盘,不应该保存在内存中。
  • 同时key的前置状态可能有可能没有。

案例实现——wordcount

package com.sparkStreaming_example

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


object UpdateStateByKeyTest {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
      .setAppName("WordCountUpdateStateByKey")
      .setMaster("local[*]")
    val duration = Seconds(5)
    val ssc = new StreamingContext(conf, duration)
    ssc.checkpoint("C:\Users\80621\Desktop\data\streamData\chk")
    val lines:DStream[String] = ssc.socketTextStream("user1", 6666)

    val pairs:DStream[(String, Int)] = lines.flatMap(_.split("\s+")).map((_, 1))

    val usb:DStream[(String, Int)] = pairs.updateStateByKey(updateFunc)
    usb.print()
    ssc.start()
    ssc.awaitTermination()
  }

  
  def updateFunc(seq: Seq[Int], option: Option[Int]): Option[Int] = {
    println("option:" + option +  "> seq: " + seq.mkString("[", ",", "]"))
    
    Option(seq.sum + option.getOrElse(0))
  }
}

在端口6666中不断输入,程序累计输出

五、window

滚动窗口(左闭右开)+滑动窗口

        window操作就是窗口函数。Spark Streaming提供了滑动窗口操作的支持,从而让我们可以对一个滑动窗口内的数据执行计算操作。每次掉落在窗口内的RDD的数据,会被聚合起来执行计算操作,然后生成的RDD,会作为window DStream的一个RDD。比如下图中,就是对每三秒钟的数据执行一次滑动窗口计算,这3秒内的3个RDD会被聚合起来进行处理,然后过了两秒钟,又会对最近三秒内的数据执行滑动窗口计算。所以每个滑动窗口操作,都必须指定两个参数,窗口长度以及滑动间隔,而且这两个参数值都必须是batch间隔的整数倍。

说明:

  • 红色的矩形就是一个窗口,窗口hold的是一段时间内的数据流。
  • 这里面每一个time都是时间单元,在官方的例子中,每隔window size是3 time unit, 而且每隔2个单位时间,窗口会slide一次。
    所以基于窗口的操作,需要指定2个参数:
    window length - The duration of the window (3 in the figure)
    slide interval - The interval at which the window-based operation is performed (2 in the figure).
  • 窗口大小,个人感觉是一段时间内数据的容器。
  • 滑动间隔,就是我们可以理解的cron表达式

案例实现

package com.sparkStreaming_example

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


object SlideWindow {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
      .setAppName("WordCountUpdateStateByKey")
      .setMaster("local[*]")
    val batchInterval = 2
    val duration = Seconds(batchInterval)
    val ssc = new StreamingContext(conf, duration)
    val lines:DStream[String] = ssc.socketTextStream("user1", 6666)
    val pairs:DStream[(String, Int)] = lines.flatMap(_.split("\s+")).map((_, 1))

    val ret:DStream[(String, Int)] = pairs.reduceByKeyAndWindow(_+_,
      windowDuration = Seconds(batchInterval * 3),  //窗口长度3s
      slideDuration = Seconds(batchInterval * 2))  //滑动间隔2s

    ret.print()

    ssc.start()
    ssc.awaitTermination()
  }
}

六、SparkSQL和SparkStreaming的整合案例

        Spark最强大的地方在于,可以与Spark Core、Spark SQL整合使用,之前已经通过transform、foreachRDD等算子看到,如何将DStream中的RDD使用Spark Core执行批处理操作。现在就来看看,如何将DStream中的RDD与Spark SQL结合起来使用。
案例:top3的商品排序: 最新的top3

这里就是基于updatestateByKey,统计截止到目前为止的不同品类下的商品销量top3。

DS-->RDD-->DataFrame-->注册成表-->对表进行sql-->展示结果

代码

package com.sparkStreaming_example

import org.apache.spark.SparkConf
import org.apache.spark.sql.SparkSession
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.{Seconds, StreamingContext}

object sparkSQLWithStreaming {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
      .setAppName("StreamingWithSQL")
      .setMaster("local[*]")
    val batchInterval = 3
    val duration = Seconds(batchInterval)
    val spark = SparkSession.builder()
      .config(conf)
      .getOrCreate()
    //获取上下文
    val ssc = new StreamingContext(spark.sparkContext, duration)
    //设置checkpoint
    ssc.checkpoint("C:\Users\80621\Desktop\data\streamData\chk-1")
    //获取数据源
    val lines:DStream[String] = ssc.socketTextStream("user1", 6666)
    //001 mi moblie
    val pairs:DStream[(String, Int)] = lines.map(line => {
      val fields = line.split("\s+")
      if(fields == null || fields.length != 3) {
        ("", -1)
      } else {
        val brand = fields(1)
        val category = fields(2)
        (s"${category}_${brand}", 1)
      }
    }).filter(t => t._2 != -1)
    //实时累加计算
    val usb:DStream[(String, Int)] = pairs.updateStateByKey(updateFunc)
    //将ds数据进行循环
    usb.foreachRDD((rdd, bTime) => {
      if(!rdd.isEmpty()) {//category_brand count
        import spark.implicits._
        //将rdd转换成df
        val df = rdd.map{
          case (cb, count) => {
            val category = cb.substring(0, cb.indexOf("_"))
            val brand = cb.substring(cb.indexOf("_") + 1)
            (category, brand, count)
          }}.toDF("category", "brand", "sales")
        //将df注册成临时表
        df.createOrReplaceTempView("tmp_category_brand_sales")
        //根据category进行分组排名,然后取top3的SQL语句
        val sql =
          """
            |select
            |  t.category,
            |  t.brand,
            |  t.sales,
            |  t.rank
            |from (
            |  select
            |    category,
            |    brand,
            |    sales,
            |    row_number() over(partition by category order by sales desc) rank
            |  from tmp_category_brand_sales
            |) t
            |where t.rank < 4
            |;
                    """.stripMargin
        //执行SQL语句并打印结果
        spark.sql(sql).show()
      }
    })
    //开启streaming
    ssc.start()
    ssc.awaitTermination()
  }

  def updateFunc(seq: Seq[Int], option: Option[Int]): Option[Int] = {
    Option(seq.sum + option.getOrElse(0))
  }
}

显示每个类别的前三名

 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1024623.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号