Spark Streaming(二) Structed Streaming

Spark Streaming(二) Structed Streaming

本文详细介绍下Spark Streaming的第二代引擎Structed Streaming,包括Structed Streaming的概述,核心概念,
Structed Streaming相关的应用等。

Structed Streaming 概述

Structed Streaming 有两个关键思想:以处理批量计算的方式对待流计算和与存储系统的事务集成,以提供端到端、只需一次的保证。

  • 以处理批量计算的方式对待流计算

以处理批量计算的方式对待流计算,也就是说将流数据看作是连续追加表的形式。这意味着将传入的数据流视为输入表,并在新的一组数据到达时将其视为附加到输入表的一组新行。
这种思路有很多好处。其中之一是能够利用Scala、Java或Python中现有的DataFrame和DataSet的结构化API来执行流计算,
并让结构化流引擎负责在新的流数据到达时增量地、连续地运行它们。
另一个好处是可以使用上一章中讨论的同一Catalyst引擎来优化通过结构化API表示的流计算。
除此之外从使用结构化API中获得的知识可以直接用于构建在Spark结构化流引擎上运行的流应用程序。唯一需要学习的剩余部分是特定于流处理域的部分,例如事件时间处理和维护状态,节约了学习成本。

  • 与存储系统的事务集成,以提供端到端、只需一次的保证
    这里的目标是确保从存储系统读取数据的服务应用程序看到流应用程序已处理的数据的一致快照。
    传统上,开发人员有责任确保从流应用程序向外部存储系统发送数据时不会出现重复数据或数据丢失。这是流应用程序开发人员提出的痛点之一。
    从内部来看,Structure Streaming引擎已经提供了恰好只有一次的保证,现在,如果外部存储系统支持事务,同样的保证也会扩展到这些系统。

从Spark 2.3 开始,Structed Streaming 已经扩展为支持一种叫“ontinuous processing”的新模型,在此之前Spark只支持Micro Batch 模型,Micro Batch是Spark的默认模型。
由于Micro Batch模型等待并收集一小批数据然后处理的性质,它适用于可以容忍100毫秒范围内的端到端延迟的应用。对于其他需要低至1毫秒的端到端延迟的应用它们应该使用连续处理模型。

Structed Streaming的核心概念

通常情况下流应用程序的主要部分包括指定一个或多个流数据源、提供用于以DataFrame转换的形式操作传入数据流的逻辑、定义输出模式和触发器,以及最后指定要将结果写入的数据接收器。

这些构成了Structed Streaming的核心概念。

数据源(Data Source)

对于批处理来说,Data Source是驻留在某些存储系统(如本地文件系统、HDFS或S3)上的静态数据集。Structed Straming 的数据源有很大的不同。他们产生的数据是连续的,可能永远不会结束,而且产生的速度可能会随着时间的推移而变化。

Structed Streaming 支持以下Data Source:

  • Kafka Source
    这里要求Kafka版本在0.10及以上,是生产环境中最流行的数据源。

  • File Source
    既支持本地文件系统,HDFS也支持S3。支持的文件格式包含了常见的Text, CSV, JSON, ORC和Parquet。

  • Socket Source
    这仅用于测试目的。它从侦听特定主机和端口的套接字读取UTF-8数据。

  • Rate Source
    这仅用于测试和基准测试目的。此源可配置为每秒生成多个事件,其中每个事件由时间戳和单调递增的值组成。
    这是学习Structed Streaming时最容易使用的资源。

数据源需要为结构化流式传输提供的一个重要特性是跟踪流中的读取位置的方式,以提供端到端、精确一次的保证。
此外Apache Spark 2.3引入了DataSourceV2API,这是一组官方支持的接口,供Spark开发人员开发可以轻松与结构化流集成的自定义数据源。

输出模式(Output Mode)

输出模式是告知Structed Streaming应该如何将输出数据写入接收器的一种方式。这个概念是Spark中的流处理所独有的。
有三种选择模式:

  • 追加模式:如果未指定输出模式,则这是默认模式。在此模式下,只有追加到结果表的新行才会发送到指定的输出接收器。
  • 完成模式:整个结果表将写入输出接收器。
  • 更新模式:只有在结果表中更新的行才会写入输出接收器。对于未更改的行,不会将其写出。
触发器类型(Trigger Types)

Structed Streaming引擎使用触发信息来确定何时在您的流传输应用程序中运行所提供的流传输计算逻辑。
目前有4种触发器类型:

  • 未指定(Not Specified)
    这是Spark Streaming的默认模式。对于此默认类型,Spark将使用微批处理模式,并在前一批数据完成处理后立即处理下一批数据。

  • 固定间隔(Fixed Internal)
    对于这种类型,Spark将使用微批模式,并根据用户提供的间隔处理批量数据。如果由于任何原因,前一批数据的处理时间超过该间隔,则在前一批数据完成后立即处理下一批数据。

  • 触发一次(One-time)
    此触发器类型用于一次性处理可用批量数据,一旦处理完成,Spark将立即停止流应用程序。当数据量极低时,此触发器类型非常有用,因此启动群集并一天只处理几次数据更具成本效益。

  • 连续触发(Continuous)
    此触发器类型调用新的连续处理模式,该模式是为需要非常低延迟的特定流应用程序设计的。这是Spark 2.3中新的实验性处理模式。

数据接收器(Data Sink)

数据接收器与数据源相对应,它们用于存储流应用程序的输出。
重要的是要识别哪些接收器可以支持哪种输出模式,以及它们是否具有容错能力。
目前Spark Streaming 支持以下Data Sink:

  • Kafka Sink
    与Data Source 类似要求Kafka版本在0.10及以上

  • File Sink
    与Data Source类似,支持写出到本地文件系统,HDFS或S3。同时也支持Text, CSV, JSON, ORC和Parquet等文件类型。

  • Foreach Sink
    这意味着对输出中的行运行任意计算。

  • Console Sink
    这仅用于测试和调试目的,并且在处理低量数据时使用。每次触发时,输出都会打印到控制台。

  • Memory Sink
    这仅在处理小容量数据时用于测试和调试目的。它使用Driver的内存来存储输出。

其中具有分区容错性的有Kafka Sink, File Sink。
Kafka Sink, Console Sink和Foreach Sink 完全支持3种输出模式(追加,完成,更新)。
File Sink只支持追加模式,Memory Sink 支持追加和完成模式,不支持更新模式。

Structed Streaming相关的应用

下面来看一个Sturcted Streaming相关的应用程序,以便理解之前提到的各种概念。
这个示例是关于如何处理来自文件数据源的一小部分移动操作事件的。

每一个事件包含3个字段:

  • Id:表示电话的唯一id
  • Action: 用户的操作,Open或者Close
  • ts: 事件发生的时间戳,即Event time。

让我们来看下具体的数据的Schema:

val mobileDataDF= spark.read.json("/data/mobile")

mobileDataDF.printSchema

输出结果为:
|-- action: string (nullable = true)
|-- id: string (nullable = true)
|-- ts: string (nullable = true)

默认情况下Structed Streaming从一个文件数据源读取数据时是需要一个Schema的,这是因为读取数据源目录可能是空的,所以就无法推测出Schema。

如果需要强制指定自动推测Schema,需要将配置partk.sql.Streaming.schemaInference设置为true。

这里手动创建一个Schema:

 val mobileDataSchema = new StructType().add("id", StringType, false)
                                        .add("action", StringType, false)
                                        .add("ts", TimestampType, false)

这里我们尝试根据一个10秒长度的固定窗口,根据动作类型就行统计计数。代码如下:

val mobileSSDF = spark.readStream.schema(mobileDataSchema).json("/data/input")
mobileSSDF.isStreaming
val actionCountDF = mobileSSDF.groupBy(window($"ts", "10 minutes"), $"action").count
val mobileConsoleSQ = actionCountDF.writeStream
                                   .format("console").option("truncate", "false")
                                   .outputMode("complete")
                                   .start()

第一行定义了一个Structed Streaming类型的DataFrame。 指定数据源
第二行进行了验证,结果为True
第三行定义转换逻辑
第四行定义数据输出收集器为console,并指定输出模式为complete。

当往input folder下面拷入一个file1.json数据时,Structed Streaming就会执行,
file1.json
{“id”:“phone1”,“action”:“open”,“ts”:“2018-03-02T10:02:33”}
{“id”:“phone2”,“action”:“open”,“ts”:“2018-03-02T10:03:35”}
{“id”:“phone3”,“action”:“open”,“ts”:“2018-03-02T10:03:50”}
{“id”:“phone1”,“action”:“close”,“ts”:“2018-03-02T10:04:35”}

结果如下:

Batch: 0
-------------------------------------------
+------------------------------------------+------+-----+
|window                                    |action|count|
+------------------------------------------+------+-----+
|[2018-03-02 10:00:00, 2018-03-02 10:10:00]|close |1    |
|[2018-03-02 10:00:00, 2018-03-02 10:10:00]|open  |3    |
+------------------------------------------+------+-----+

再拷入一个
file2.json
{“id”:“phone3”,“action”:“close”,“ts”:“2018-03-02T10:07:35”}
{“id”:“phone4”,“action”:“open”,“ts”:“2018-03-02T10:07:50”}

输出结果为:

Batch: 1
-------------------------------------------
+------------------------------------------+------+-----+
|window                                    |action|count|
+------------------------------------------+------+-----+
|[2018-03-02 10:00:00, 2018-03-02 10:10:00]|close |2    |
|[2018-03-02 10:00:00, 2018-03-02 10:10:00]|open  |4    |
+------------------------------------------+------+-----+

可以看到由于输出模式为complete模式Structed Streaming根据event time把file1和file2中的数据进行处理后输出结果。

再增加一批数据
file3.json
{“id”:“phone2”,“action”:“close”,“ts”:“2018-03-02T10:04:50”}
{“id”:“phone5”,“action”:“open”,“ts”:“2018-03-02T10:10:50”}

输出结果为:

Batch: 2
-------------------------------------------
+------------------------------------------+------+-----+
|window                                    |action|count|
+------------------------------------------+------+-----+
|[2018-03-02 10:00:00, 2018-03-02 10:10:00]|close |3    |
|[2018-03-02 10:00:00, 2018-03-02 10:10:00]|open  |4    |
|[2018-03-02 10:10:00, 2018-03-02 10:20:00]|open  |1    |
+------------------------------------------+------+-----+

输出结果为3条,这是因为最后一条数据phone5的ts 已经超过了10分钟的窗口,属于下一个窗口。

mobileConsoleSQ是StreamingQuery的实例,通过mobileConsoleSQ我们可以管理Structed Streaming包括查看状态,停止处理等。
支持以下方法:

scala> mobileConsoleSQ.
awaitTermination   explain   isActive       name                  recentProgress   sparkSession   stop
exception          id        lastProgress   processAllAvailable   runId            status

停止Streaming application

mobileConsoleSQ.stop()

Streming DataFrame相关的操作

Structed Streaming的卖点之一是Spark中一套统一的批处理和流处理API。对于流DataFrame,可以对其应用任何选择和筛选器转换,以及对单个列操作的任何Spark SQL函数。即大多数在普通DataFrame可以应用的API 都可以在Streaming DataFrame中使用。

但是也要注意的是Streaming DataFrame中不支持一些DataFrame变换,因为它们太复杂而无法维护状态,或者因为流式数据的无界性质。
Streaming DataFrame不支持以下转换:

  • 多重聚合或者链式聚合
  • limt 或者 take N行的操作
  • Distinct 转换
  • 在没有任何聚合的情况下对流数据帧进行排序。但是,在某种形式的聚合之后支持排序。

更多关于Structed Streaming如何在不同输入数据源和不同输出数据收集器下工作的例子将在下一篇文章中介绍

你可能感兴趣的:(spark,大数据)