【问题标题】:How to deduplicate and keep latest based on timestamp field in spark structured streaming?如何根据火花结构化流中的时间戳字段进行重复数据删除和保持最新?
【发布时间】:2020-10-25 13:18:36
【问题描述】:

Spark dropDuplicates 保留第一个实例并忽略该键的所有后续出现。是否可以在保留最近出现的情况下删除重复项?

例如,如果下面是我获得的微批次,那么我想保留每个国家/地区的最新记录(按时间戳字段排序)。

batchId:0

Australia, 10, 2020-05-05 00:00:06
Belarus, 10, 2020-05-05 00:00:06

batchId:1

Australia, 10, 2020-05-05 00:00:08
Belarus, 10, 2020-05-05 00:00:03

那么batchId 1之后的输出应该低于-

Australia, 10, 2020-05-05 00:00:08
Belarus, 10, 2020-05-05 00:00:06

更新 1 这是我拥有的当前代码

//KafkaDF is a streaming dataframe created from Kafka as source
val streamingDF = kafkaDF.dropDuplicates("country")

streamingDF.writeStream
    .trigger(Trigger.ProcessingTime(10000L))
    .outputMode("update")
    .foreachBatch {
      (batchDF: DataFrame, batchId: Long) => {
        println("batchId: "+ batchId)
        batchDF.show()
      }
    }.start()

我想输出所有新行或时间戳大于迄今为止处理的先前批次中的任何记录的所有行。下面的例子

batchId: 0 之后 - 这两个国家都是第一次出现,所以我应该让它们输出

Australia, 10, 2020-05-05 00:00:06
Belarus, 10, 2020-05-05 00:00:06

batchId: 1 之后 - 白俄罗斯的时间戳比我们在第 0 批中收到的时间戳要旧,所以我不会在输出中显示它。显示澳大利亚是因为它的时间戳比我目前看到的更新。

Australia, 10, 2020-05-05 00:00:08

现在假设 batchId 2 将两条记录都作为迟到的记录出现,那么它不应该在该批次的输出中显示任何内容。

输入batchId:2

Australia, 10, 2020-05-05 00:00:01
Belarus, 10, 2020-05-05 00:00:01

batchId: 2 之后

.

Update-2

为每个批次添加输入和预期记录。用红色标记的行将被丢弃,并且不会在输出中显示为具有相同国家名称的另一行,并且在之前的批次中可以看到更近的时间戳

【问题讨论】:

  • 我发现了一个问题,它确切地询问了我在寻找什么,但不幸的是,没有答案。 stackoverflow.com/questions/50823468/… 全部,我正在寻找的是 dropDuplicates in reverse order 以便它保留最新记录而不是第一个
  • 你在哪里写数据??
  • @Srinivas 我将把它写到像 Oracle 这样的持久数据库中,但我不想使用查找 oracle 来解决它,因为我想在开始时过滤迟到的记录
  • 迟到的记录意味着持续多长时间..想想两天前回来的相同记录今天可以回来..你打算怎么处理??
  • 我可以放心地假设我的活动不会迟到一个多小时。

标签: apache-spark spark-streaming spark-structured-streaming drop-duplicates


【解决方案1】:

为了避免流式应用程序中迟到的事件,您需要在应用程序中保持一个状态,在您的情况下跟踪每个键的最新处理事件,它是country

case class AppState(country:String, latestTs:java.sql.Timestamp)

对于微批处理,您可能会收到多个事件,当您执行groupByKey(_.country) 时,您将获得属于key(country) 的事件,您需要将其与状态进行比较以找到最新的输入事件并更新使用密钥的最新时间戳声明并继续处理最新事件以进行进一步处理。对于迟到的事件,它应该返回一个Option[Event],并在后续过程中过滤掉它。

详细说明请参考blog

【讨论】:

  • 我非常感谢您的回答。虽然,我在过去几天读到了关于有状态转换的文章,但你的回答让我有信心深入研究并尝试一下,它确实有效。
  • 嗨@conetfun,当你说试一试时,你的意思是“博客”文章?是不是类似于stackoverflow.com/questions/50933606/…,它谈论的是 mapGroupsWithState
  • 嗨@Sivakumar,1)。我不确定我理解你所说的For late arriving events, it should return an Option[Event] and filter out the same in subsequent process 是什么意思。 2)state case类是否只需要保留ke​​y和latestTs?它不需要保留其他值吗?这意味着我们需要重新加入以获取其他值。
  • 嗨@conetfun,我发布了一个与您的案例相关的问题。如果可能,请查看并评论/回答:stackoverflow.com/questions/63916475/…
【解决方案2】:

尝试在 Spark Streaming 中使用window 函数,如下所示。

val columns = Seq("country","id").map(col(_))
df.groupBy(window($"timestamp","10 minutes","5 minutes"), columns:_*)

您也可以检查相同的in this question,解决方案在python中。

【讨论】:

  • 谢谢,我很抱歉,因为我的问题之前不是很清楚。我进行了编辑以使其详细。在您的示例中,您正在对窗口进行分组,因此每个窗口将显示一条记录。我只想输出我在更新中所说的记录-没有基于窗口的组
  • 嗨@conetfun,那么有或没有窗口有什么区别?没有窗口,您在历史记录中只显示 1 条记录?每次触发检查时,您都显示 1 条记录,并检查整个窗口长度?谢谢。
  • @Minnie - 对于我的要求,滑动窗口将不起作用,因为我需要从头开始获取所有数据以将其与当前批次数据进行比较,以过滤出迟到的记录(跨批次)。我可以使用mapGroupsWithState 解决同样的问题,但我的最终解决方案是不使用它,因为它会在每个批次中将状态写入磁盘,因为触发器执行需要更长的时间。我最终使用 scala map 维护状态并为每个新批次重新创建一个 RDD,过滤迟到的行,并使用当前批次的最新值更新映射。
  • @conetfun,我还发现一旦我开始使用mapGroupsWithState,它就开始为每个触发器/批处理写入输出,即使没有处理新数据 - if 我使用超时配置和处理时间。 mapGroupsWithState 签名表示,虽然对于流式数据集,将在每个触发器中为每个组重复调用该函数,而 updates to each group's state will be saved across invocations. 这意味着 跨调用保存.... 但是 如果我将 timeout conf 与 eventtime 一起使用,它不会写出空输出。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-10-14
  • 1970-01-01
  • 2015-07-06
  • 2019-11-07
  • 2021-11-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多