流重复数据的删除

可以使用一个唯一身份标识符来删除数据流中的重复数据记录。这与使用唯一标识符列对静态数据进行重复数据的删除完全相同,查询将从以前的记录中存储必要数量的数据以用来过滤重复的记录。与聚合类似,这里也可以使用带或不带水印的重复数据删除。

1).使用水印,如果数据的到达存在一个延迟多久到达的上限,那么可以在事件时间列上定义一个水印,删除重复数据时将同时使用guid和事件时间列。查询将使用水印从过去的记录中删除不希望得到任何重复的旧状态数据,这限制了查询必须维护的状态的数量。

2).不使用水印,由于没有限制重复记录何时到达,因此查询将所有过去记录中的数据存储为状态。

val streamingDf = spark.readStream. ...  // 列包括guid,eventTime,..等等

// 使用guid列不使用水印
streamingDf.dropDuplicates("guid")

// 使用guid和eventTime列 使用水印
streamingDf
  .withWatermark("eventTime", "10 seconds")
  .dropDuplicates("guid", "eventTime")

处理多个水印的策略

一个流查询可以有多个联合或连接在一起的输入流。每个输入流都可以有不同的延迟数据阈值,对于有状态操作,这些阈值是需要被接纳的,在每个输入流上都可以使用withWatermarks(“eventTime”,delay)指定这些阈值。例如,一个inputStream1和inputStream2之间具有流-流连接的查询。

inputStream1.withWatermark(“eventTime1”, “1 hour”) .join( inputStream2.withWatermark(“eventTime2”, “2 hours”), joinCondition)

在执行查询时,结构化流单独跟踪每个输入流中的最大事件时间,根据相应的时延计算水印。Spark还会选择一个全局水印,默认情况下,选择最小的那个作为全局水印,因为它可以确保,如果其中一个流落后于其他流(例如,其中一个流由于上游故障而停止接收数据),不会因为太晚而意外地删除数据。换句话说,全局水印将以最慢的流的速度安全地移动,查询输出将相应地延迟。

然而,在某些情况下,我们可能希望获得更快的结果,哪怕这意味着从最慢的流删除数据。从Spark 2.4开始,可以通过设置SQL配置spark.sql.streaming.multipleWatermarkPolicy=max(默认值是min)来设置最大值来作为全局水印。这使得全局水印以最快的流的速度移动。这种情况,来自较慢流的数据将可能会被删除。因此,需要根据自己的需求谨慎地使用这种配置。

不支持的操作

有一些DataFrame/Dataset操作是流Dataframe /Dataset不支持的。其中一些如下:

1.流Dataset中还不支持多个流聚合(即流DF上的聚合链)。

2.流Dataset不支持限制和获取前N行。

3.不支持流Dataset上的Distinct操作。

4.只有在聚合之后并在完全输出模式下,流DataSet才支持排序操作。

5.流式Join操作中有些外部连接操作不能支持,具体参见上篇。

此外,还有一些Dataset方法不能在流Dataset上起作用。它们是立即运行查询并返回结果的操作,这在流Dataset中没有意义。相反,这些功能可以通过显式地启动流查询来实现。

1.count() -不能从流式Dataset中返回单个计数。相反,使用ds.groupBy().count()将返回一个流数据集,其中包含一个正在运行的count。

2.foreach() -需要改为使用ds.writeStream.foreach(…)。

3.show()——使用控制台接收器(console sink)代替(参见下一节)。

如果在流式Dataset/Dataframe上尝试这些操作的话,程序将抛出AnalysisException,比如“流Dataset/Dataframe不支持XYZ操作”。虽然其中一些可能在Spark的未来版本中得到支持,但还有一些从根本上难以在流数据上有效实现。例如,不支持对输入流进行排序,因为它需要跟踪流中接收的所有数据,而流中的数据是动态增加的。因此,从根本上说,这很难有效地执行。