第一件事儿先贴官方文档,由此可见,英文的重要性啊,哈哈...
structured streaming : http://spark.apache.org/docs/latest/structured-streaming-programming-guide.html
spark Streaming : http://spark.apache.org/docs/latest/streaming-programming-guide.html
structured Streaming是Spark新提出的一种实时流的框架,以前是Spark Streaming。那么这两者有什么区别呢,为什么有了Spark Streaming,还要提出Structured Streaming呢?且听我细细道来...
spark streaming:
Spark Streaming是核心Spark API的扩展,可实现实时数据流的可扩展,高吞吐量,容错流处理。数据可以从许多来源(如Kafka,Flume,Kinesis或TCP套接字)中提取,并且可以使用以高级函数表示的复杂算法进行处理map
,例如reduce
,join
和window
。最后,处理后的数据可以推送到文件系统,数据库和实时仪表板。实际上,您可以在数据流上应用Spark的 机器学习和 图形处理算法。
在内部,它的工作原理如下。Spark Streaming接收实时输入数据流并将数据分成批处理,然后由Spark引擎处理以批量生成最终结果流。
Spark Streaming提供称为离散流或DStream的高级抽象,表示连续的数据流。DStream可以从来自Kafka,Flume和Kinesis等源的输入数据流创建,也可以通过在其他DStream上应用高级操作来创建。在内部,DStream表示为一系列 RDD。
Spark Streaming接收流数据,并根据一定的时间间隔拆分成一批批batch数据,用抽象接口DStream表示(DStream可以看成是一组RDD序列,每个batch对应一个RDD),然后通过Spark Engine处理这些batch数据,最终得到处理后的一批批结果数据。
structured-streaming(结构化流):
结构化流是一种基于Spark SQL引擎的可扩展且容错的流处理引擎。您可以像表达静态数据的批处理计算一样表达流式计算。Spark SQL引擎将负责逐步和连续地运行它,并在流数据继续到达时更新最终结果。您可以使用Scala,Java,Python或R中的数据集/数据框架API来表示流聚合,事件时间窗口,流到批处理连接等。计算在同一优化的Spark SQL引擎上执行。最后,系统通过检查点和预写日志确保端到端的一次性容错保证。简而言之,结构化流传输提供快速,可扩展,容错,端到端的精确一次流处理,而无需用户推理流式传输。
在内部,默认情况下,结构化流式查询使用微批处理引擎进行处理,该引擎将数据流作为一系列小批量作业处理,从而实现低至100毫秒的端到端延迟和完全一次的容错保证。但是,自Spark 2.3以来,我们引入了一种称为连续处理的新型低延迟处理模式,它可以实现低至1毫秒的端到端延迟,并且具有至少一次保证。无需更改查询中的数据集/数据框操作,您就可以根据应用程序要求选择模式。
结构化流中的关键思想是将实时数据流视为连续追加的表。这导致新的流处理模型非常类似于批处理模型。您将流式计算表示为静态表上的标准批处理查询,Spark将其作为无界输入表上的增量查询运行。让我们更详细地了解这个模型。
将输入数据流视为“输入表”。到达流的每个数据项都像一个新行被附加到输入表。
对输入的查询将生成“结果表”。每个触发间隔(例如,每1秒),新行将附加到输入表,最终更新结果表。每当结果表更新时,我们都希望将更改的结果行写入外部接收器。
“输出”定义为写入外部存储器的内容。输出可以以不同的模式定义:
完整模式 - 整个更新的结果表将写入外部存储器。由存储连接器决定如何处理整个表的写入。
追加模式 - 自上次触发后,只有结果表中附加的新行才会写入外部存储器。这仅适用于预计结果表中的现有行不会更改的查询。
更新模式 - 只有自上次触发后在结果表中更新的行才会写入外部存储(自Spark 2.1.1起可用)。请注意,这与完整模式的不同之处在于此模式仅输出自上次触发后已更改的行。如果查询不包含聚合,则它将等同于追加模式。
请注意,每种模式适用于某些类型的查询。稍后将对此进行详细讨论。
第一个lines
DataFrame是输入表,最终的wordCounts
DataFrame是结果表。需要注意的是在流媒体的查询lines
数据帧生成wordCounts
是完全一样的,因为它是一个静态的数据帧。但是,当启动此查询时,Spark将不断检查套接字连接中的新数据。如果有新数据,Spark将运行“增量”查询,该查询将先前运行的计数与新数据相结合,以计算更新的计数,如下所示。
请注意,Structured Streaming不会实现整个表。它从流数据源读取最新的可用数据,逐步处理以更新结果,然后丢弃源数据。它仅保留更新结果所需的最小中间状态数据(例如,前面示例中的中间计数)。
该模型与许多其他流处理引擎明显不同。许多流系统要求用户自己维护运行聚合,因此必须推断容错和数据一致性(至少一次,或至多一次,或完全一次)。在此模型中,Spark负责在有新数据时更新结果表,从而减轻用户对其的推理。作为一个例子,让我们看看这个模型如何处理基于事件时间的处理和迟到的数据。
事件时间是嵌入数据本身的时间。对于许多应用程序,您可能希望在此事件时间运行。例如,如果您想每分钟获取IoT设备生成的事件数,那么您可能希望使用生成数据的时间(即数据中的事件时间),而不是Spark接收的时间他们。此事件时间在此模型中非常自然地表达 - 来自设备的每个事件都是表中的一行,事件时间是行中的列值。这允许基于窗口的聚合(例如,每分钟的事件数)在事件时间列上只是一种特殊类型的分组和聚合 - 每个时间窗口是一个组,每行可以属于多个窗口/组。因此,
此外,该模型自然地处理基于其事件时间到达的时间晚于预期的数据。由于Spark正在更新结果表,因此它可以在存在延迟数据时完全控制更新旧聚合,以及清理旧聚合以限制中间状态数据的大小。从Spark 2.1开始,我们支持水印,允许用户指定后期数据的阈值,并允许引擎相应地清理旧状态。稍后将在“ 窗口操作”部分中详细介绍这些内容。
提供端到端的一次性语义是结构化流的设计背后的关键目标之一。为实现这一目标,我们设计了结构化流媒体源,接收器和执行引擎,以可靠地跟踪处理的确切进度,以便通过重新启动和/或重新处理来处理任何类型的故障。假设每个流源都具有偏移(类似于Kafka偏移或Kinesis序列号)以跟踪流中的读取位置。引擎使用检查点和预写日志来记录每个触发器中正在处理的数据的偏移范围。流式接收器设计为处理重新处理的幂等功能。结合使用可重放的源和幂等接收器,结构化流可以确保端到端的精确一次语义 在任何失败。
从Spark 2.0开始,DataFrames和Datasets可以表示静态的,有界的数据,以及流式,无界数据。与静态数据集/数据框类似,您可以使用公共入口点SparkSession
(Scala / Java / Python / R文档)从流源创建流式数据框/数据集,并对它们应用与静态数据框/数据集相同的操作。如果您不熟悉数据集/数据框架,强烈建议您使用“ 数据框架/数据集编程指南”熟悉它们 。
可以通过返回的DataStreamReader
接口(Scala / Java / Python文档)创建Streaming DataFrame SparkSession.readStream()
。在R中,用这个read.stream()
方法。与用于创建静态DataFrame的读取接口类似,您可以指定源的详细信息 - 数据格式,架构,选项等。
输入源
有一些内置源。
文件来源 - 将目录中写入的文件作为数据流读取。支持的文件格式为text,csv,json,orc,parquet。有关更新的列表,请参阅DataStreamReader接口的文档,以及每种文件格式支持的选项。请注意,文件必须原子地放置在给定目录中,在大多数文件系统中,可以通过文件移动操作来实现。
Kafka来源 - 从Kafka读取数据。它与Kafka经纪人版本0.10.0或更高版本兼容。有关更多详细信息,请参阅Kafka集成指南。
套接字源(用于测试) - 从套接字连接读取UTF8文本数据。侦听服务器套接字位于驱动程序中。请注意,这应仅用于测试,因为这不提供端到端的容错保证。
速率源(用于测试) - 以每秒指定的行数生成数据,每个输出行包含一个timestamp
和value
。其中timestamp
是一个Timestamp
含有信息分配的时间类型,并且value
是Long
包含消息的计数从0开始作为第一行类型。此源用于测试和基准测试。
某些源不具有容错能力,因为它们无法保证在发生故障后可以使用检查点偏移重放数据。请参阅前面 的容错语义部分。以下是Spark中所有源代码的详细信息。
资源 | 选项 | 容错 | 笔记 |
---|---|---|---|
文件来源 | path :输入目录的路径,并且对所有文件格式都是通用的。 maxFilesPerTrigger :每个触发器中要考虑的最大新文件数(默认值:无最大值) latestFirst :是否先处理最新的新文件,当存在大量积压的文件时有用(默认值:false) fileNameOnly :是否基于以下方法检查新文件只有文件名而不是完整路径(默认值:false)。将此设置为“true”时,以下文件将被视为同一文件,因为它们的文件名“dataset.txt”是相同的: “file:///dataset.txt” “s3:// a / dataset.txt“ ”s3n://a/b/dataset.txt“ ”s3a://a/b/c/dataset.txt“ 对于特定于文件格式的选项, DataStreamReader (Scala / Java / Python / R)。例如,“镶木地板”格式选项请参阅DataStreamReader.parquet() 。 此外,还有会话配置会影响某些文件格式。有关更多详细信息,请参见SQL编程指南。例如,对于“镶木地板”,请参阅镶木地板配置部分。 |
是 | 支持glob路径,但不支持多个以逗号分隔的路径/ globs。 |
套接字源 | host :要连接的主机,必须指定port :要连接的端口,必须指定 |
没有 | |
评价来源 | rowsPerSecond (例如100,默认值:1):每秒应生成多少行。rampUpTime (例如5s,默认值:0s):在生成速度变为之前加速多长时间rowsPerSecond 。使用比秒更精细的粒度将被截断为整数秒。numPartitions (例如10,默认值:Spark的默认并行性):生成的行的分区号。源代码将尽力达到目标 rowsPerSecond ,但查询可能会受到资源限制,并且numPartitions 可以进行调整以帮助达到所需的速度。 |
是 | |
kafka | 请参阅Kafka集成指南。 |
使用结构化流式传输时,滑动事件时间窗口上的聚合非常简单,并且与分组聚合非常相似。在分组聚合中,为用户指定的分组列中的每个唯一值维护聚合值(例如计数)。在基于窗口的聚合的情况下,为每个窗口维护一行的事件时间的聚合值。让我们通过一个例子来理解这一点。
想象一下,我们的快速示例已被修改,流现在包含行以及生成行的时间。我们不想运行字数,而是计算10分钟内的单词,每5分钟更新一次。也就是说,在10分钟窗口12:00-12:10,12:05-12:15,12:10-12:20等之间收到的单词数量。请注意,12:00 - 12:10表示数据在12:00之后但在12:10之前到达。现在,考虑一下在12:07收到的一个字。这个词应该增加对应于两个窗口12:00 - 12:10和12:05 - 12:15的计数。因此,计数将由分组键(即单词)和窗口(可以从事件时间计算)索引。
结果表看起来如下所示。
由于此窗口类似于分组,因此在代码中,您可以使用groupBy()
和window()
操作来表示窗口化聚合。
处理延迟数据和水印
现在考虑如果其中一个事件到达应用程序的后期会发生什么。例如,应用程序在12:11可以接收在12:04(即事件时间)生成的单词。应用程序应使用时间12:04而不是12:11来更新窗口的旧计数12:00 - 12:10
。这在我们基于窗口的分组中自然发生 - 结构化流可以长时间维持部分聚合的中间状态,以便后期数据可以正确更新旧窗口的聚合,如下所示。
但是,要运行此查询数天,系统必须限制它累积的中间内存中状态的数量。这意味着系统需要知道何时可以从内存状态中删除旧聚合,因为应用程序不再接收该聚合的后期数据。为了实现这一点,我们在Spark 2.1中引入了 水印,使引擎能够自动跟踪数据中的当前事件时间并尝试相应地清理旧状态。您可以通过指定事件时间列来定义查询的水印,并根据事件时间确定数据预计的延迟时间。对于从时间开始的特定窗口T
,引擎将保持状态并允许延迟数据更新状态直到(max event time seen by the engine - late threshold > T)
。换句话说,阈值内的后期数据将被聚合,但是晚于阈值的数据将开始被丢弃(参见 本节后面的确切保证)。让我们通过一个例子来理解这一点。我们可以使用withWatermark()
如下所示的前一个示例轻松定义水印。
详细见:http://spark.apache.org/docs/latest/structured-streaming-programming-guide.html
哈哈哈...不搬砖了。。。。
个人小结:
Structured Streaming是Spark2.0版本提出的新的实时流框架(2.0和2.1是实验版本,从Spark2.2开始为稳定版本),相比于Spark Streaming,优点如下:
同样能支持多种数据源的输入和输出,参考上面的的数据流图
以结构化的方式操作流式数据,能够像使用Spark SQL处理离线的批处理一样,处理流数据,代码更简洁,写法更简单
基于Event-Time,相比于Spark Streaming的Processing-Time更精确,更符合业务场景
解决了Spark Streaming存在的代码升级,DAG图变化引起的任务失败,无法断点续传的问题(Spark Streaming的硬伤!!!)
Structured Streaming将实时流抽象成一张无边界的表,输入的每一条数据当成输入表的一个新行,同时将流式计算的结果映射为另外一张表,完全以结构化的方式去操作流式数据。
输入的流数据是以batch为单位被处理,每个batch会触发一次流式计算,计算结果被更新到Result Table。
设定batch长度为1s,每一秒从输入源读取数据到Input Table,然后触发Query计算,将结果写入Result Table.
c、最后,Result Table的结果写出到外部存储介质(如Kafka)。
一共有三种Output模式:
Append模式:只有自上次触发后在Result Table表中附加的新行将被写入外部存储器。重点模式,一般使用它。
Complete模式: 将整个更新表写入到外部存储。每次batch触发计算,整张Result Table的结果都会被写出到外部存储介质。
Update模式:只有自上次触发后在Result Table表中更新的行将被写入外部存储器。注意,这与完全模式不同,因为此模式不输出未更改的行。
2、基于Event-Time聚合&延迟数据处理
StructedStreaming相比于SparkStreaming的另一个优点是基于Event time(事件时间)聚合
a、基于Event time聚合,更准确
首先,介绍下两种时间的概念:
Event time 事件时间: 就是数据真正发生的时间,比如用户浏览了一个页面可能会产生一条用户的该时间点的浏览日志。
Process time 处理时间: 则是这条日志数据真正到达计算框架中被处理的时间点,简单的说,就是你的Spark程序是什么时候读到这条日志的。
事件时间是嵌入在数据本身中的时间。对于许多应用程序,用户可能希望在此事件时间操作。例如,如果要获取IoT设备每分钟生成的事件数,则可能需要使用生成数据的时间(即数据中的事件时间),而不是Spark接收他们的时间。事件时间在此模型中非常自然地表示 - 来自设备的每个事件都是表中的一行,事件时间是该行中的一个列值。这允许基于窗口的聚合(例如每分钟的事件数)仅仅是时间列上的特殊类型的分组和聚合 - 每个时间窗口是一个组,并且每一行可以属于多个窗口/组。因此,可以在静态数据集(例如,来自收集的设备事件日志)以及数据流上一致地定义这种基于事件时间窗的聚合查询,操作起来更方便。
b、延迟数据处理Watermark
Structured Streaming基于Event time能自然地处理延迟到达的数据,保留或者丢弃。
由于Spark正在更新Result Table,因此当存在延迟数据时,它可以完全控制更新旧聚合,以及清除旧聚合以限制中间状态数据的大小。
使用Watermark,允许用户指定数据的延期阈值,并允许引擎相应地清除旧的状态。
基于Event-Time聚合&延迟数据处理的详细实例参考 《StructuredStreaming + Kafka程序怎么写》
3、容错性
a、容错语义
流式数据处理系统的可靠性语义通常是通过系统可以处理每个记录的次数来定义的。系统可以在所有可能的操作情形下提供三种类型的保证(无论出现何种故障):
At most once:每个记录将被处理一次或不处理。
At least once: 每个记录将被处理一次或多次。 这比“最多一次”更强,因为它确保不会丢失任何数据。但可能有重复处理。
Exactly once:每个记录将被精确处理一次 - 不会丢失数据,并且不会多次处理数据。 这显然是三者中最强的保证。
Structured Streaming能保证At least once的语义,目标是Exactly once。
b、容错机制
在故障或故意关闭的情况下,用户可以恢复先前进度和状态,并继续在其停止的地方,简称断点续传。这是通过使用检查点checkpoint和预写日志write ahead logs来完成的。
用户可以指定checkpoint的位置,Spark将保存所有进度信息(如每个触发器中处理的offset偏移范围)和正在运行的聚合到checkpoint中。任务重启后,使用这些信息继续执行。
注:checkpoint的目录地址必须是HDFS兼容文件系统中的路径。
checkpoint的设置参考《StructuredStreaming + Kafka程序怎么写》
如果觉得上述叙述还不够详细,请看一篇详解Structured Streaming