結構化流式傳輸支持將Dataset/DataFrame與靜態Dataset/DataFrame以及另一個流式Dataset/DataFrame連接起來。流連接的結果以遞增方式生成,類似於上一節中的流聚合的結果。本節,我們將探討再上述情況下支持哪種類型的連接(即內部,外部等)。注意,在所有受支持的連接類型中,與流式Dataset/DataFrame的連接結果與使用包含流中相同數據的靜態Dataset/DataFrame的結果完全相同。
靜態-流 Join
自Spark2.0引入以來,Structured Streaming支持流和靜態Dataset/DataFrame之間的連接(內連接和某種類型的外連接)。下面是一個簡單的例子。
Dataset<Row> staticDf = spark.read(). ...;
Dataset<Row> streamingDf = spark.readStream(). ...;
// inner equi-join with a static DF
streamingDf.join(staticDf, "type");
// right outer join with a static DF
streamingDf.join(staticDf, "type", "right_join");
注意,靜態流連接不是有狀態的,因此不需要進行狀態管理。但是,尚不支持集中類型的靜態流式外連接。
流-流 Join
在Spark2.3中,添加了對Stream-Stream Join的支持,也就是說,我們可以加入兩個流Dataset/DataFrame。在兩個數據流之間生成連接結果的挑戰是,在任何時間點,數據集的視圖對於連接的兩側都是不完整的,這使得在輸入之間找到匹配更加困難。從一個輸入流接收的任何行都可以與來自另一個輸入流的任何未來的,尚未接收的行匹配。因此,對於兩個輸入流,Spark將過去的輸入緩衝爲流狀態,以便可以將每個未來輸入與過去的輸入相匹配,從而生成連接結果。此外,類似於流聚合,Spark自動處理遲到的無序數據,並可以使用水印限制狀態。
帶有水印的內連接
支持任何類型的列上的內部鏈接以及任何類型的連接條件。但是,當流運行時,流狀態的大小將無線增長,因爲必須保存所有過去的輸入,因爲任何新輸入都可以與過去的任何輸入匹配。爲了避免無界狀態,我們必須定義其他連接條件,以便於無限期使舊輸入無法與將來的輸入匹配,從而可以從狀態清除。換句話說,我們必須在連接中執行以下附加步驟。
- 定義兩個輸入上的水印延遲,以便引擎直到輸入的延遲時間(類似於流式聚合)
- 在兩個輸入上定義事件事件約束,以便引擎可以確定何時不需要一個輸入的舊行(即不滿足時間約束)與另一個輸入匹配。可以用兩種方式之一定義該約束。
- 時間範圍連接條件(例如
...JOIN ON leftTime BETWEN rightTime AND rightTime + INTERVAL 1 HOUR
), - 加入事件時間窗口(例如
...JOIN ON leftTimeWindow = rightTimeWindow
)。
- 時間範圍連接條件(例如
讓我們通過一個例子來理解這一點。
假設我們希望加入一系列廣告展示次數(展示廣告時),並在廣告上添加另一個用戶點擊流,以便在展示次數達到可獲利的點擊時進行關聯。要在Stream-Stream連接中允許狀態清理,我們必須指定水印延遲和時間約束,如下所示。
- 水印延遲:比如說,展示次數和相應的點擊次數可以分別在時間時間內延遲/無序,最多2個小時和3個小時。
- 事件時間範圍條件:假設,在相應的印象後0秒到1小時的時間範圍內可能發生咔噠聲。
示例代碼如下:
Dataset<Row> impressions = spark.readStream(). ...
Dataset<Row> clicks = spark.readStream(). ...
// Apply watermarks on event-time columns
Dataset<Row> impressionsWithWatermark = impressions.withWatermark("impressionTime", "2 hours");
Dataset<Row> clicksWithWatermark = clicks.withWatermark("clickTime", "3 hours");
// Join with event-time constraints
impressionsWithWatermark.join(
clicksWithWatermark,
expr(
"clickAdId = impressionAdId AND " +
"clickTime >= impressionTime AND " +
"clickTime <= impressionTime + interval 1 hour ")
);
具有水印的流內部連接的語義保證
這類似於通過聚合水印提供的保證。水印延遲“2小時”可確保引擎永遠不會丟失任何延遲小於2小時的數據。但延遲2小時以上的數據不能保證會得到處理。
帶水印的外連接
雖然水印+事件時間約束對於內連接是可選的,但對於左外連接和右外連接,必須指定它們。這是因爲爲了在外連接中生成NULL結果,引擎必須直到輸入行何時不會與將來的任何內容匹配。因此,必須指定水印+事件時間約束以生成正確的結果。因此,使用外部連接的查詢看起來與之前的廣告貨幣化示例非常相似,只是會有一個附加參數將其指定爲外部連接。
impressionsWithWatermark.join(
clicksWithWatermark,
expr(
"clickAdId = impressionAdId AND " +
"clickTime >= impressionTime AND " +
"clickTime <= impressionTime + interval 1 hour "),
"leftOuter" // can be "inner", "leftOuter", "rightOuter"
);
具有水印的流 - 流外連接的語義保證
關於水印延遲以及數據是否會被丟棄,外連接與內部連接具有相同的保證。
注意事項
關於如何生成外部結果,有一些重要的特徵需要注意。
- 將生成外部NULL結果,延遲取決於指定的水印延遲和時間範圍條件。這是因爲引擎必須等待那麼長時間以確保沒有匹配,並且將來不會再有匹配。
- 在微批量引擎的當前實現中,水印在微批次結束時前進,並且下一個微批次使用更新的水印來清理狀態並輸出外部的結果。由於我們僅在存在要處理的新數據時才觸發微批處理,因此如果在流中沒有接收到新的數據,則外部結果的生成可能會延遲。簡而言之,如果連接的兩個輸入流中的任何一個在一段時間內沒有接收到數據,則外部(兩種情況,左側或右側)輸出可能會延遲。
支持流式查詢中的連接矩陣
左輸入 | 右輸入 | 連接類型 | |
Static | Static | 所有類型 | 支持,因爲它不在流數據上,即使它可以存在於流式查詢中 |
Stream | Static | 內連接 | 支持,無狀態 |
左外連接 | 支持,無狀態 | ||
右外連接 | 不支持 | ||
全外連接 | 不支持 | ||
Static | Stream | 內連接 | 支持,無狀態 |
左外連接 | 不支持 | ||
右外連接 | 支持無狀態 | ||
全外連接 | 不支持 | ||
Stream | Stream | 內連接 | 支持,可選擇在兩側指定水印+狀態清理的時間限制 |
左外連接 | 有條件支持,必須在右側指定水印 + 時間約束上以獲得正確的結果,可選擇在左側指定水印以進行所有狀態清理 | ||
右外連接 | 有條件支持,必須在左側指定水印 + 時間約束上以獲得正確的結果,可選擇在右側指定水印以進行所有狀態清理 | ||
全外連接 | 不支持 |
有關支持的連接大的其他詳細信息:
- 連接可以級聯,也就是說,我們可以做到df1.join(df2,...).join(df3,...).join(df4,...) 。
- 從Spark2.3開始,只有在查詢出於追加輸出模式時才能使用連接。其他輸出模式尚不支持。
- 從Spark2.3開始,在連接之前不能使用其他非類似map的操作。以下時一些不能使用的例子。
- 在join之前無法使用流式聚合。
- 在join之前,無法在更新模式下使用mapGroupsWithState和flatMapGroupsWithState。