8.分布式计算平台Spark:StructStreaming

分布式计算平台Spark:StructStreaming

一、重点

  1. SparkStreaming基本原理

    • 本质还是SparkCore:基于RDD的离线批处理
    • 原理:划分微小时间单位的批处理
      • ReceiverTask:将源源不断的数据流划分Block:默认200ms
        • 将每个Block的数据缓存在Executor的内存中
        • 将位置反馈给Driver
      • Driver等到Batch时间:1s
        • 区别:Core按照触发函数来触发job的,按照时间来触发job的
  2. DStream:离散的数据流

    • DStream = Seq[RDD]
    • 基于时间有序的RDD的集合,符合RDD的所以特性
    • 函数
      • 转换函数:tranformRDD
        • 对每个RDD进行转换操作,有返回值
      • 输出函数:foreachRDD
        • 对每个RDD进行操作,没有返回值
      • 能使用RDD的处理就尽量使用RDD的处理
  3. 流式计算的业务模式

    • 无状态模式:每个批次的结果就是这个批次中最终的结果,与其他的批次无关
      • 一般用于实现ETL
    • 有状态模式:每个批次的结果不是这个批次的最终结果,这个批次的最终结果与其他的批次有关,一般需要聚合
      • 一般用于累计统计分析
      • updateStateByKey(更新逻辑):根据Key对Value进行函数更新Value的值
        • 如果Key的value没有发生,也会被更新,每次都将所有结果写入checkpoint
      • mapWithState(对象:key,当前的value,之前的value):根据Key对Value进行函数更新Value的值
        • 只对Key的Value发生变化的数据进行更新,基于checkpoint+缓存来实现的、
    • 窗口计算:按照一定的时间周期计算一定时间范围的数据
      • 一般用于热搜实时统计
      • 区别:不再是基于每个批次的计算,基于窗口的计算
      • Batch:批次时间
      • Slide:滑动的频率【计算时间频率】
      • Window:计算的范围
  4. 与Kafka集成

    • 0.8x

      • receiver:启动receiver不断接受kafka发送过来的数据
        • RDD的分区数 = Block Interval的个数
        • 导致数据丢失的问题,通过WAL来解决数据丢失问题,导致性能较慢
        • 这种方式我们是不用的
      • direct:由SparkStreaming主动到Kafka中拉取数据
        • RDD的分区数 = Kafka的Topic的分区数
        • 拉取数据的流程
          • step1:SparkStreaming会到Kafka请求获取 每个分区最新的偏移量
          • step2:根据上个批次最后的偏移量和这个批次的最新偏移量构建拉取范围
          • step3:根据范围向Kafka请求消费每个分区的数据
    • 0.10.x

      • 类似于Direct方式来实现的
    • 问题:假设每3s统计一次到目前为止的订单总金额

      • 程序如果故障,重新启动程序

        • 问题1:程序第一次运行的状态会丢失

          12:00:00 ~ 12:00:03		1000万
          12:00:03 ~ 12:00:06		1000万	=》  2000万
          程序发生故障
          |
          重启程序
          12:00:06 ~ 12:00:09		500万	=》	500万
          
        • 问题2:消费Kafka的数据,重新启动以后,就不知道上一次消费的位置,Kafka会根据自己记录位置来继续提供消费

          • Kafka记录的位置与我们实际的位置是由差异的,会导致数据丢失或者数据重复的问题
    • 解决

      • 问题1

        • 方案一:checkpoint来实现
          • 在checkpoint中存储整个程序的所有信息
            • meta:配置信息、DStream的转换、上次处理的偏移量位置
            • data:数据之前的状态
          • 问题:如果对代码做了DStream级别的修改,程序是无法恢复
          • 要求:不能对代码进行DStream级别的修改
        • 方案二:自己存储上一次的状态
      • 问题2:手动管理偏移量

  5. 问题

    • SparkCore 中的累加器会发生跟java中的线程安全问题一样的情况吗
    • SparkStreaming可以看作是一个分布式队列,队列中的过期数据应该需要清理,那么垃圾回收机制是怎么样的 在有状态模式下,当前状态下产生异常,如何进行异常处理,程序继续运行还是异常退出 SparkStreaming中有流水线机制吗 例: 数据处理分为读取,ETL,分析,存储四个阶段,每个阶段需要10ms 非流水线:读取->ETL->分析->存储->读取->ETL->分析->存储->读取->ETL->分析->存储 每40ms吞吐一批数据,延迟为40ms 流水线: 时间增加方向----------> 读取->ETL->分析->存储 读取->ETL->分析->存储 读取->ETL->分析->存储 读取->ETL->分析->存储 读取->ETL->分析->存储 读取->ETL->分析->存储 完全进入流水线的时候,理论上每10ms就能吞吐一批数据,有四倍的吞吐量,同时延迟只有10ms 批处理是顺序执行,会有很大的延迟,使得程序运行时很多时候都是等待数据的状态 猜测:flink=微小的批处理+流水线?
    • 不太清楚BatchInterval与窗口计算中的Slide Interval的区别在哪

二、概要

  1. StructStreaming功能与应用场景
    • 介绍特点
    • 功能与应用场景
    • 基本原理
  2. 使用
    • 代码开发规则
    • 数据源
    • 特性:流式数据去重、持续处理【真实时计算】、基于事件时间的处理

三、StructStreaming的介绍

1、结构化流的设计

  • 诞生:源自于DataFlow
    • 新的流式计算特性:基于事件时间的处理等等
  • 理解:使用SparkSQL来开发流式计算
    • SparkCore:基于代码开发离线批处理
    • SparkSQL:基于结构化SQL开发的离线批处理
    • SparkStreaming:基于SparkCore的流式计算
      • 问题:本身基于计算时间的处理、微小时间的批处理,延迟性比较高、开发接口还是传统的编程
    • StructStreaming:基于SparkSQL的流式计算
    • 用一套API实现所有模块的编程:SQL
  • 基本原理
    • SparkSQL:数据结构:DataFrame/DataFrame:分布式的表
    • SparkStreaming:数据结构:DStream:无边界的数据流
    • |
    • StructStreaming:将无边界的数据流读取到一个DS/DF:无边界的分布式表
      • 所有数据源源不断的追加到这个表中
      • 最后处理的结果也是放入DS或者DF中:无边界的结果表
      • 每次处理的结果都不断追加到这个表中
      • 通过查询从结果表中查询我们自己想要的数据

2、官网介绍

Structured Streaming is a scalable and fault-tolerant stream processing engine built on the Spark SQL engine. You can express your streaming computation the same way you would express a batch computation on static data. The Spark SQL engine will take care of running it incrementally and continuously and updating the final result as streaming data continues to arrive. You can use the Dataset/DataFrame API in Scala, Java, Python or R to express streaming aggregations, event-time windows, stream-to-batch joins, etc. The computation is executed on the same optimized Spark SQL engine. Finally, the system ensures end-to-end exactly-once fault-tolerance guarantees through checkpointing and Write-Ahead Logs. In short, Structured Streaming provides fast, scalable, fault-tolerant, end-to-end exactly-once stream processing without the user having to reason about streaming.
  • 结构化流基于SparkSQL引擎之上

  • 对于流式计算或者离线批处理可以使用同一套API来实现

  • 正常代码

    val inputData:DataFrame = spark.readStream
    
    • 将流式数据放入一个无边界的表中:不断追加数据

      image-20201226104532753
    val rsData:DataFrame = inputData.filter.groupBy.count……
    
    • 不断对新到达的数据实现处理和转换

      image-20201226104654924

    • 最终将结果追加写入一个无边界的表

      image-20201226104725392

3、官网示例

  • 启动HDFS

    start-dfs.sh
    
  • 第一台机器运行nc

    nc -lk 9999
    
  • 第二台机器运行

    /export/server/spark/bin/run-example \
    --master local[2] \
    --conf spark.sql.shuffle.partitions=2 \
    org.apache.spark.examples.sql.streaming.StructuredNetworkWordCount \
    node1.it.cn 9999
    
  • 发现特性:

    • StructStreaming中默认的间隔批次时间是ms级别,可以调整间隔时间的
    • 如果没有数据是不输出的
    • 有状态的计算

4、自定义开发

package bigdata.it.cn.spark.struct.wordcount

import org.apache.spark.sql.streaming.OutputMode
import org.apache.spark.sql.{
   DataFrame, Dataset, Row, SparkSession}

/**
  * @ClassName StructStreamingMode
  * @Description TODO 结构化流实现词频统计
  * @Date 2020/12/26 10:55
  * @Create By     Frank
  */
object StructStreamingWordCount {
   

  def main(args: Array[String]): Unit = {
   
    /**
      * step1:先创建SparkSession
      */
    val spark = SparkSession.builder()
      .appName(this.getClass.getSimpleName.stripSuffix("$"))
      .master("local[3]")
      .config("spark.sql.shuffle.partitions","2")
      .getOrCreate()
    //导入包
    import spark.implicits._
    import org.apache.spark.sql.functions._

    /**
      * step2:处理逻辑
      */
    //todo:1-读取数据
    val inputData: DataFrame = spark.readStream
      .format("socket")
      .option("host","node1")
      .option("port","9999")
      .load()

    //todo:2-处理
    val rsData = inputData
      //转换DataSet
      .as[String]
      .filter(line => null != line && line.trim.length > 0)
      .flatMap(line => line.trim.split("\\s+"))
      .groupBy($"value")
      .count()

    //todo:3-保存
    val query = rsData
      .writeStream
      .outputMode(OutputMode.Complete())
      .format("console")
      .option("numRows","10")//指定显示结果的条数
      .option("truncate","false")//如果某列的值过长,是否省略显示
      //启动
      .start()

    /**
      * step3:长久运行
      */
    query.awaitTermination()
    query.stop()


  }
}

四、StructStreaming输入

1、数据源

  • http://spark.apache.org/docs/2.4.5/structured-streaming-programming-guide.html#input-sources
image-20201224161339358
  • 工作中主要使用的还是Kafka Source

2、文件数据源

  • 所有的文件类型必须指定Schema

  • 监控目录:如果目录产生新的文件,就立即处理

  • 举例:读取文件

    package bigdata.it.cn.spark.struct.datasource
    
    import org.apache.spark.sql.streaming.{
         OutputMode, StreamingQuery}
    import org.apache.spark.sql.types.{
         IntegerType, StringType, StructType}
    import org.apache.spark.sql.{
         DataFrame, SparkSession}
    
    /**
      * @ClassName StructStreamingMode
      * @Description TODO 读取文件:统计年龄在25岁以下的人,每种兴趣爱好对应的人数
      * @Date 2020/12/26 10:55
      * @Create By     Frank
      */
    object StructStreamingReadFile {
         
    
      def main(args: Array[String]): Unit = {
         
        /**
          * step1:先创建SparkSession
          */
        val spark = SparkSession.builder()
          .appName(this.getClass.getSimpleName.stripSuffix("$"))
          .master("local[3]")
          .config("spark.sql.shuffle.partitions","2")
          .getOrCreate()
        //导入包
        import spark.implicits._
    
        /**
          * step2:处理逻辑
          */
        //todo:1-读取数据:不断追加新的数据到DF中
        //构建Schema
        val schema = new StructType()
          .add("name",StringType,true)
          .add("age",IntegerType,true)
          .add("hobby",StringType,true)
    
    
        val inputData: DataFrame = spark.readStream
          //更改分隔符
          .option("sep",";")
          //指定目录中文件的Schema
          .schema(schema)
          //指定监控的目录
          .csv("datastruct/filesource")
    
        //todo:2-处理:不断的对每个批次的数据做处理聚合合并
        val rsData: DataFrame = inputData
          //过滤
          .filter($"age" < 25)
          .groupBy($"hobby")
          .count()
    
    
        //todo:3-保存
        val query: StreamingQuery = rsData
          .writeStream
          .outputMode(OutputMode.Complete())
          .format("console")
          .option("numRows","10")//指定显示结果的条数
          .option("truncate","false")//如果某列的值过长,是否省略显示
          //启动
          .start()
    
        /**
          * step3:长久运行
          */
        query.awaitTermination()
        query.stop()
    
    
      }
    }
    
    

3、Rate数据源【了解】

  • StructStream用于测试代码的数据

  • 会帮你模拟产生数据:时间和数值【从0开始编号】

    bigdata.it.cn.spark.struct.datasource.StructuredRateSource
    

五、Query选项

1、输出模式

  • http://spark.apache.org/docs/2.4.5/structured-streaming-programming-guide.html#output-modes

  • 三种

    • Append:用于追加输出
      • 一般用于没有聚合的场景
      • 如果要在聚合中使用,只能在基于事件的窗口中基于水印的聚合
    • Complete:完整的输出,用于将所有数据结果进行输出
      • 聚合的场景下全部结果的输出
    • Update:更新输出,只输出发生更新的数据
      • 一般用于各种场景下
  • 不同的操作需要使用不同的模式

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G1t36CzF-1616249333541)(Day45_分布式计算平台Spark:StructStreaming.assets/image-20201226114429826.png)]

2、查询器名称

  • 一般用于在监控中辨别不同的查询器

    val query: StreamingQuery = rsData
          .writeStream
          //保存模式
          .outputMode(OutputMode.Complete())
          .format("console")
          //指定查询器的名称
          .queryName("firstQuery")
          .option("numRows","10")//指定显示结果的条数
          .option("truncate","false")//如果某列的值过长,是否省略显示
          //启动
          .start()
    
    

3、触发间隔

  • 支持三种流式计算模式

    • 第一种:微小时间批处理,默认的模式
    • 第二种:只处理一次
    • 第三种:实时不断的数据流处理,数据产生一条,就处理一条
  • 实现

    • 每个批次间隔处理一次

      //设置处理的模式
            .trigger(Trigger.ProcessingTime("3 seconds"))
      

      image-20201226115344838

    • 只处理一次:一般不用

      //设置处理的模式
            .trigger(Trigger.Once())
      

      image-20201226115704405

4、检查点

  • http://spark.apache.org/docs/2.4.5/structured-streaming-programming-guide.html#recovering-from-failures-with-checkpointing

    In case of a failure or intentional shutdown, you can recover the previous progress and state of a previous query, and continue where it left off. This is done using checkpointing and write-ahead logs. You can configure a query with a checkpoint location, and the query will save all the progress information (i.e. range of offsets processed in each trigger) and the running aggregates (e.g. word counts in the quick example) to the checkpoint location. This checkpoint location has to be a path in an HDFS compatible file system, and can be set as an option in the DataStreamWriter when starting a query.
    
  • 配置方式一

        val query: StreamingQuery = rsData
          .writeStream
          //保存模式
          .outputMode(OutputMode.Complete())
          .format("console")
          //指定查询器的名称
          .queryName("firstQuery")
          //设置处理的模式
    //      .trigger(Trigger.Once())
          //配置检查点,存储程序的元数据信息,用于程序的恢复
          .option("checkpointLocation", "datastruct/output/chk1")
          .option("numRows","10")//指定显示结果的条数
          .option("truncate","false")//如果某列的值过长,是否省略显示
          //启动
          .start()
    
  • 配置方式二

        /**
          * step1:先创建SparkSession
          */
        val spark = SparkSession.builder()
          .appName(this.getClass.getSimpleName.stripSuffix("$"))
          .master("local[3]")
          .config("spark.sql.shuffle.partitions","2")
          //设置检查点方式二
          .config("spark.sql.streaming.checkpointLocation","datastruct/output/chk2")
          .getOrCreate()
    

5、输出终端

image-20201226120443600

http://spark.apache.org/docs/2.4.5/structured-streaming-programming-guide.html#output-sinks

  • Foreach Sink

    • 用于自定义保存的实现,一般用于保存至MySQL、Redis等数据库中

    • MySQL中建表

      drop table if exists `db_spark`.`tb_word_count`;
      CREATE TABLE `db_spark`.`tb_word_count` (
        `id` int NOT NULL AUTO_INCREMENT,
        `word` varchar(255) NOT NULL,
        `count` int NOT NULL,
        PRIMARY KEY (`id`),
        UNIQUE KEY `word` (`word`)
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
      
      REPLACE INTO `tb_word_count` (`id`, `word`, `count`) VALUES (NULL, ?, ?)
      
    • 自定义输出器代码实现

      package bigdata.it.cn.spark.struct.datasink
      
      import java.sql.{
             Connection, DriverManager, PreparedStatement}
      
      import org.apache.spark.sql.{
             ForeachWriter, Row}
      
      /**
        * @ClassName MySQLForeachSink
        * @Description TODO 用于自定义将数据写入MySQL
        * @Date 2020/12/26 12:07
        * @Create By     Frank
        */
      class MySQLForeachSink extends ForeachWriter[Row]{
             
      
        var conn:Connection = null
        var pstm:PreparedStatement = null
        val sql = "REPLACE INTO `tb_word_count` (`id`, `word`, `count`) VALUES (NULL, ?, ?)"
      
        //用于初始化资源
        override def open(partitionId: Long, epochId: Long): Boolean = {
             
            Class.forName("com.mysql.cj.jdbc.Driver")
          conn = DriverManager.getConnection(
            "jdbc:mysql://node1.it.cn:3306/db_spark?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true",
            "root",
          "123456"
          )
          pstm = conn.prepareStatement(sql)
          //构建完成
          true
        }
      
        //实现处理
        override def process(value: Row): Unit = {
             
          pstm.setString(1,value.getAs[String]("value"))
          pstm.setInt(2,value.getAs[Long]("count").toInt)
          pstm.execute()
        }
      
        //释放资源
        override def close(errorOrNull: Throwable): Unit = {
             
          pstm.close()
          conn.close()
        }
      }
      
      
    • 代码中调用

          //todo:3-保存
          val query: StreamingQuery = rsData
            .writeStream
            .outputMode(OutputMode.Complete())
            //自定义输出接口
            .foreach(new MySQLForeachSink)
            //启动
            .start()
      

      image-20201226143434056

  • Foreach Batch Sink

    • 基于SparkSQL的批处理来实现输出

      df.write.jdbc()
      
    • 实现

          val query: StreamingQuery = rsData
            .writeStream
            .outputMode(OutputMode.Complete()<
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值