Struct Streaming的流-流连接

本文转自:https://databricks.com/blog/2018/03/13/introducing-stream-stream-joins-in-apache-spark-2-3.html
流 - 流连接的案例:广告货币化
想象一下,您有两个流 - 一个广告展示流(即,向用户显示广告时)和另一个广告点击流(即,当用户点击显示的广告时)。要通过广告获利,您必须匹配导致点击的广告展示。换句话说,您需要根据公共密钥加入这些流,公共密钥是两个流的事件中存在的每个广告的唯一标识符。在高级别,问题如下所示。
在这里插入图片描述

虽然这在概念上是一个简单的想法,但仍有一些核心技术挑战需要克服。

使用缓冲处理延迟/延迟数据:展示事件及其相应的点击事件可能无序到达,并且它们之间存在任意延迟。因此,流处理引擎必须通过适当地缓冲它们直到它们匹配来解决这种延迟。尽管所有连接(静态或流式传输)都可以使用缓冲区,但真正的挑战是避免缓冲区无限制地增长。
限制缓冲区大小:限制流连接缓冲区大小的唯一方法是将延迟数据丢弃超过某个阈值。用户可以配置此最大延迟阈值,具体取决于业务要求与系统资源限制之间的平衡。

定义良好的语义:在静态连接和流连接之间保持一致的SQL连接语义,有或没有上述阈值。

我们已经在流 - 流连接中解决了所有这些挑战。因此,您可以使用SQL连接的明确语义来表达计算,并控制相关事件之间的延迟。我们来看看如何。

首先让我们假设这些流是两个不同的Kafka主题。您可以按如下方式定义流式DataFrame:

impressions = ( # schema - adId: String, impressionTime: Timestamp, …
spark
.readStream
.format(“kafka”)
.option(“subscribe”, “impressions”)

.load()
)

clicks = ( # schema - adId: String, clickTime: Timestamp, …
spark
.readStream
.format(“kafka”)
.option(“subscribe”, “clicks”)

.load()
)
然后你需要做的内部equi-join他们如下。

impressions.join(clicks, “adId”) # adId is common in both DataFrames
如同所有的结构化数据流的查询,该代码是完全一样的,你会写,如果DataFrames impressions和clicks静态数据进行定义。执行此查询时,结构化流媒体引擎将根据需要将点击次数和展示次数缓冲为流式传输状态。对于特定广告,一旦接收到两个相关事件(即,一旦接收到第二事件),就将生成加入的输出。当数据到达时,将以递增方式生成连接的输出并将其写入查询接收器(例如,另一个Kafka主题)。
在这里插入图片描述

最后,如果连接查询已应用于两个静态数据集(即,与SQL连接相同的语义),则连接的累积结果也不会有所不同。事实上,即使一个被呈现为流而另一个被呈现为静态数据集,它也将是相同的。但是,在此查询中,我们没有给出任何关于引擎应该缓冲事件以找到匹配的时间的指示。因此,引擎可以永久地缓冲事件并累积无限量的流状态。让我们看看我们如何在查询中提供额外的信息以限制状态。

管理流 - 流连接的流状态
要限制流 - 流连接维护的流状态,您需要知道有关您的用例的以下信息:

在各自来源生成两个事件之间的时间范围是多少?在我们的用例的上下文中,我们假设在相应的展示后0秒到1小时内可能发生点击。
在源和处理引擎之间传输事件的最长持续时间是多少?例如,来自浏览器的广告点击可能会因间歇性连接而延迟,并且比预期更晚到达且无序。我们可以说,展示次数和点击次数最多可分别延迟2小时和3小时。

利用每个事件的这些时间约束,处理引擎可以自动计算需要缓冲的事件多长时间以生成正确的结果。例如,它将评估以下内容。

展示需要最多4小时(在活动时间内)进行缓冲,因为3小时后点击可能与4小时前的展示相匹配(即3小时 - 晚到+最多1小时延迟展示和点击)。
相反,点击需要缓冲最多2小时(在事件时间内),因为2小时后的展示可能与2小时前收到的点击相匹配。

因此,当引擎确定任何缓冲事件未来预期不会获得任何匹配时,引擎可以从流中丢弃旧的印象和点击。

在高级别,此动画演示了如何使用事件时间以及状态清理来更新水印。

来源:imgur.com

这些时间约束可以在查询中编码为水印和时间范围连接条件。

水印:结构化流中的水印是一种通过指定要考虑的后期数据来限制所有有状态流操作中的状态的方法。具体地,水印是事件时间中的移动阈值,其落后于查询在处理数据中看到的最大事件时间。尾随间隙(也称为水印延迟)定义引擎等待迟到数据到达的时间,并在查询中使用指定withWatermark。在我们之前关于流聚合的博客文章中更详细地阅读它。对于我们的流内部连接,您可以选择指定水印延迟,但必须指定限制两个流上的所有状态。
时间范围条件:这是一个连接条件,它限制每个事件可以连接的其他事件的时间范围。这可以指定以下两种方式之一:

时间范围连接条件(例如… JOE ON leftTime BETWEEN rightTime AND rightTime + INTERVAL 1 HOUR),
加入事件时间窗口(例如… JOIN ON leftTimeWindow = rightTimeWindow)。
总之,我们对广告货币化的内在联系将如下所示。

from pyspark.sql.functions import expr

Define watermarks

impressionsWithWatermark = impressions
.selectExpr(“adId AS impressionAdId”, “impressionTime”)
.withWatermark(“impressionTime”, "10 seconds ") # max 10 seconds late

clicksWithWatermark = clicks
.selectExpr(“adId AS clickAdId”, “clickTime”)
.withWatermark(“clickTime”, “20 seconds”) # max 20 seconds late

Inner join with time range conditions

impressionsWithWatermark.join(
clicksWithWatermark,
expr("""
clickAdId = impressionAdId AND
clickTime >= impressionTime AND
clickTime <= impressionTime + interval 1 minutes
“”"
)
)
有了这个,引擎将自动计算前面提到的状态限制并相应地删除旧事件。与结构化流中的所有状态一样,检查点确保您获得完全一次的容错保证。

以下是与此帖相关联的Databricks笔记本中运行的查询的屏幕截图。请注意第三个图表,查询状态中的记录数量,在一段时间后变平,表示通过水印清理状态正在清理旧数据。
在这里插入图片描述

使用流 - 流外连接
较早的内部联接将仅输出已收到这两个事件的广告。换句话说,根本不会报告未收到任何点击的广告。相反,您可能希望报告所有广告展示次数(包括或不包含关联的点击数据),以便稍后启用其他分析(例如点击费率)。这使我们流入外连接。您需要做的就是指定连接类型。

from pyspark.sql.functions import expr

Left outer join with time range conditions

impressionsWithWatermark.join(
clicksWithWatermark,
expr("""
clickAdId = impressionAdId AND
clickTime >= impressionTime AND
clickTime <= impressionTime + interval 1 hour
“”"),
“leftOuter” // only change: set the outer join type
)
正如预期的外连接一样,此查询将开始为每次展示生成输出,无论是否使用(即使用NULLS)点击数据。但是,外连接还有一些需要注意的附加点。

与内连接不同,水印和事件时间约束对于外连接不是可选的。这是因为为了生成NULL结果,引擎必须知道事件何时不会与将来的任何其他事件匹配。因此,必须指定水印和事件时间约束以启用状态到期并生成正确的外部联接结果。
因此,外部NULL结果将产生延迟,因为引擎必须等待一段时间以确保既不存在也不存在任何匹配。此延迟是引擎针对每个事件计算的最大缓冲时间(wrt到事件时间),如前面部分所述(即,展示次数为4小时,点击次数为2小时)。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值