http://spark.apache.org/docs/2.4.3/structured-streaming-programming-guide.html
建議看官網,官網最權威
注意點
1、該outputMode爲update模式,即只會輸出那些有更新的數據!!
2、該開窗窗口長度爲10min,步長5min,水印爲eventtime-10min,(需理解開窗規則)
3、官網案例trigger(Trigger.ProcessingTime("5 minutes")),但是測試的時候不建議使用這個
4、未輸出數據不代表已經在內存中被剔除,只是由於update模式的原因
5、建議比對append理解水印
個人測試案例
object WaterMarkUpdate {
def main(args: Array[String]): Unit = {
val spark: SparkSession = SparkSession.builder().appName("chenchi").master("local[2]").getOrCreate()
import spark.implicits._
spark.readStream
.format("socket")
.option("host", "hadoop102")
.option("port", "1234")
// .option("includeTimestamp", true) // 給產生的數據自動添加時間戳
.load()
.as[String]
.map(x=>{
val split: Array[String] = x.split(",")
(split(0),Timestamp.valueOf(split(1)))
})
.toDF("word", "time")
.withWatermark("time","10 minutes")
.groupBy(
window($"time", "10 minutes", "5 minutes"),
$"word"
)
.count()
.writeStream
.format("console")
.outputMode("update")
//.trigger(Trigger.ProcessingTime("5 minutes"))
.option("truncate","false")
.start()
.awaitTermination()
}
}
測試數據和官網一樣
dog,2019-09-25 12:07:00
owl,2019-09-25 12:08:00
dog,2019-09-25 12:14:00
cat,2019-09-25 12:09:00
cat,2019-09-25 12:15:00
dog,2019-09-25 12:08:00
owl,2019-09-25 12:13:00
owl,2019-09-25 12:21:00
owl,2019-09-25 12:17:00
第一次輸入
dog,2019-09-25 12:07:00
owl,2019-09-25 12:08:00
得到結果
這個主要是因爲我輸入兩條數據的時候兩條數據時間有間隔,所以會出現兩次,兩次結果合起來和官網一樣,
batch2輸出的是因爲update模式,不出輸出dog,因爲dog中的內容沒有更新
第二次輸入
與官網一樣
第三次輸入數據
cat,2019-09-25 12:15:00
dog,2019-09-25 12:08:00
owl,2019-09-25 12:13:00
owl,2019-09-25 12:21:00
這個又是因爲trigger時間沒有設置,屬於儘快模式,只輸出更新內容,官網那裏12:20下面那裏.....省略了12:21owl這條數據的輸出,這個不是我錯了,
其實更新模式很簡單,都不想寫了再堅持一下。
第四次入內容
owl,2019-09-25 12:17:00
這個也是官網省略了一條記錄,不是我這裏多了一條記錄,到這裏明白髮現我還是錯了,就用那個trigger設置一下時間好點,這樣顯示結果容易觀看,
其實個人覺得update模式有弊端,
1、數據已經被剔除,比如原先內存已經存了12:00-12:10 ,dog ,5 ,此時內存裏有5個dog,但是到後面水印比如爲12:20,根據水印,這個5個dog已經過期會在內存裏被剔除,但是我新增一條數據 dog,12:24,結果會顯示dog:6,這樣根本看不出數據是否過期
當然有可能是我的想法太狹隘,感覺不到這個的好處
————————————————————————————————————————————————————————
下面我將解釋下append模式下的 watermark作用
上述代碼修改下
.outputMode("append")
.trigger(Trigger.ProcessingTime("3 minutes"))
測試數據
dog,2019-09-25 12:07:00
owl,2019-09-25 12:08:00
dog,2019-09-25 12:14:00
cat,2019-09-25 12:09:00
cat,2019-09-25 12:15:00
dog,2019-09-25 12:08:00
owl,2019-09-25 12:13:00
owl,2019-09-25 12:21:00
donkey,2019-09-25 12:04:00
owl,2019-09-25 12:26:00
owl,2019-09-25 12:17:00
cat,2019-09-25 12:09:00
還是之前的數據
第一次輸入
dog,2019-09-25 12:07:00
owl,2019-09-25 12:08:00
就是空數據!!官網也爲空
第二次輸入
dog,2019-09-25 12:14:00
cat,2019-09-25 12:09:00
沒錯還是空數據,官網也是空, 這又是爲啥,稍後再說
第三次輸入
cat,2019-09-25 12:15:00
dog,2019-09-25 12:08:00
owl,2019-09-25 12:13:00
owl,2019-09-25 12:21:00
這裏輸出了兩次
這裏要說下,console輸出的時候,有時候你輸入一次,一下出來兩個batch?。我覺得是你輸入一次數據(一條數據或者一批),一批數據可能存在時間差,處理的時間不一樣,就會出現多個batch,不知道我的猜想對不對
第四次輸入
donkey,2019-09-25 12:04:00
owl,2019-09-25 12:26:00
owl,2019-09-25 12:17:00
這裏出現數據差異的原因
第五次輸入
cat,2019-09-25 12:09:00
batch=null
附上一張統計表,比較直觀點
數據內容 | 此時內存中的數據有 | 控制檯輸出的結果 | 解析 | |
第一次輸入的數據 | dog,2019-09-25 12:07:00 owl,2019-09-25 12:08:00 |
12:00-12:10 dog 1 12:00-12:10 owl 1 12:05-12:15 dog 1 12:05-12:15 owl 1 |
null | 無內存中的數據剔除 |
第二次輸入的數據 | dog,2019-09-25 12:14:00 cat,2019-09-25 12:09:00 |
12:00-12:10 dog 1 12:00-12:10 owl 1 12:00-12:10 cat 1 12:05-12:15 dog 2 12:05-12:15 owl 1 12:05-12:15 cat 1 12:10-12:20 dog 1 |
null | 無內存中的數據剔除 |
第三次輸入的數據 | cat,2019-09-25 12:15:00 dog,2019-09-25 12:08:00 owl,2019-09-25 12:13:00 owl,2019-09-25 12:21:00 |
12:00-12:10 dog 2 12:00-12:10 owl 1 12:00-12:10 cat 1 12:05-12:15 dog 3 12:05-12:15 owl 2 12:05-12:15 cat 1 注意這個 12:10-12:20 dog 1 12:10-12:20 cat 1 12:10-12:20 owl 1 12:15-12:25 cat 1 12:15-12:25 owl 1 12:20-12:30 owl 1 |
batch3:null和 Batch: 4 +------------------------------------------+----+-----+ |window |word|count| +------------------------------------------+----+-----+ |[2019-09-25 12:00:00, 2019-09-25 12:10:00]|dog |2 | |[2019-09-25 12:00:00, 2019-09-25 12:10:00]|owl |1 | |[2019-09-25 12:00:00, 2019-09-25 12:10:00]|cat |1 | +------------------------------------------+----+-----+ |
此時這裏的watermarke爲12:21-10=12:11 這裏輸出了兩段一段爲空 一段中爲小於12:11的所有數據,即表明這個watermark起了作用 |
第四次輸入的數據 | donkey,2019-09-25 12:04:00 owl,2019-09-25 12:26:00 owl,2019-09-25 12:17:00 |
數據太多就不寫了其中 donkey12:04<第三次的水印12:11 屬於過期數據,不參與計算 |
Batch: 5 null Batch: 6 |window |word|count| +------------------------------------------+----+-----+ |[2019-09-25 12:05:00, 2019-09-25 12:15:00]|owl |2 | |[2019-09-25 12:05:00, 2019-09-25 12:15:00]|dog |3 | |[2019-09-25 12:05:00, 2019-09-25 12:15:00]|cat |1 | +------------------------------------------+----+-----+ |
此時這裏的watermark=12::26-10=12:16 batch6輸出的內容是第三次輸出數據後內存中<12:16的數據 12:00-12:10的已經被剔除了,所以此次剔除的是12:05-12:15中的數據 |
第四次輸入的數據 | cat,2019-09-25 12:09:00 | cat同理過期 | batch7 :null | 此時沒有輸出,因爲此時水印還是12:16,過期數據已經被刪除完了 |
這裏主要說明兩點
1、我第四次輸出結果是 owl 2 dog 3 cat 1 spark 結果是owl 2 dog 3 cat 2
出現差異的原因是,第三次輸入的數據cat,2019-09-25 12:15:00這個的開窗出現了差異根據源碼
* The windows are calculated as below:
* maxNumOverlapping <- ceil(windowDuration / slideDuration)
* for (i <- 0 until maxNumOverlapping)
* windowId <- ceil((timestamp - startTime) / slideDuration)
* windowStart <- windowId * slideDuration + (i - maxNumOverlapping) * slideDuration + startTime
* windowEnd <- windowStart + windowDuration
* return windowStart, windowEnd
windowStart =12:15-ceil(10/5)*5=12:05 所以應該劃分12:05-12:15 12:10-12:20這兩個窗口
spark官方應該是劃分爲12:10-12:20 12:15-12:25,個人猜測.
2、就是輸出時間的問題,我是在第三次和第四次輸入數據後,控制檯就打印了結果,spark官方圖上顯示的是第四次和第五次輸出的結果 先來段spark官方原話
The engine waits for “10 mins” for late date to be counted, then drops intermediate state of a window < watermark, and appends the final counts to the Result Table/sink. For example, the final counts of window 12:00 - 12:10
is appended to the Result Table only after the watermark is updated to 12:11
.
翻譯:只有(只要)當水印=12:11的時候,之前的12:00 - 12:10
窗口數據就會被追加到結果表 即輸出到控制檯
是隻有還是隻要?第三次輸入數據後水印已經到達12:11,然後就把12:00-12:10的數據輸出了,同時這裏有兩個batch,個人感覺是隻要
當一個批次的數據的時候,structured-streaming 先獲得最大的水印,然後排除那些同批次的過期數據,然後會把一個批次的數據全部加載進內存計算出結果後,再剔除過期的內存數據
如果劃分到一條條數據,有個疑問,是此條最大水印的數據輸入後,立馬開始清理內存,還是在下一條數據來臨的時候再清理
歡迎大家探討,這個只是自己學習時的一點想法,不一定正確,歡迎批評,但別太嚴厲。。。。。。