gpt4 book ai didi

apache-spark - Spark Structured Streaming 是否可以进行适当的事件时间 session ?

转载 作者:行者123 更新时间:2023-12-04 16:13:42 25 4
gpt4 key购买 nike

一直在玩 Spark Structured Streaming 和 mapGroupsWithState (具体遵循 Spark 源中的 StructuredSessionization 示例)。我想确认一些我认为存在于 mapGroupsWithState 中的限制鉴于我的用例。

就我而言, session 是用户的一组不间断事件,这样两个按时间顺序(按事件时间,而不是处理时间)排序的事件之间的间隔不会超过某个开发人员定义的持续时间(30 分钟很常见)。

在跳入代码之前,一个示例将有所帮助:

{"event_time": "2018-01-01T00:00:00", "user_id": "mike"}
{"event_time": "2018-01-01T00:01:00", "user_id": "mike"}
{"event_time": "2018-01-01T00:05:00", "user_id": "mike"}
{"event_time": "2018-01-01T00:45:00", "user_id": "mike"}

对于上面的流, session 定义为 30 分钟的不事件时间。在流式上下文中,我们应该以一个 session 结束(第二个尚未完成):

[
{
"user_id": "mike",
"startTimestamp": "2018-01-01T00:00:00",
"endTimestamp": "2018-01-01T00:05:00"
}
]

现在考虑以下 Spark 驱动程序:

import java.sql.Timestamp

import org.apache.spark.sql.{Row, SparkSession}
import org.apache.spark.sql.execution.streaming.MemoryStream
import org.apache.spark.sql.types.StructType
import org.apache.spark.sql.functions._
import org.apache.spark.sql.streaming.{GroupState, GroupStateTimeout}

object StructuredSessionizationV2 {

def main(args: Array[String]): Unit = {
val spark = SparkSession
.builder
.master("local[2]")
.appName("StructredSessionizationRedux")
.getOrCreate()
spark.sparkContext.setLogLevel("WARN")
import spark.implicits._

implicit val ctx = spark.sqlContext
val input = MemoryStream[String]

val EVENT_SCHEMA = new StructType()
.add($"event_time".string)
.add($"user_id".string)

val events = input.toDS()
.select(from_json($"value", EVENT_SCHEMA).alias("json"))
.select($"json.*")
.withColumn("event_time", to_timestamp($"event_time"))
.withWatermark("event_time", "1 hours")
events.printSchema()

val sessionized = events
.groupByKey(row => row.getAs[String]("user_id"))
.mapGroupsWithState[SessionState, SessionOutput](GroupStateTimeout.EventTimeTimeout) {
case (userId: String, events: Iterator[Row], state: GroupState[SessionState]) =>
println(s"state update for user ${userId} (current watermark: ${new Timestamp(state.getCurrentWatermarkMs())})")
if (state.hasTimedOut) {
println(s"User ${userId} has timed out, sending final output.")
val finalOutput = SessionOutput(
userId = userId,
startTimestampMs = state.get.startTimestampMs,
endTimestampMs = state.get.endTimestampMs,
durationMs = state.get.durationMs,
expired = true
)
// Drop this user's state
state.remove()
finalOutput
} else {
val timestamps = events.map(_.getAs[Timestamp]("event_time").getTime).toSeq
println(s"User ${userId} has new events (min: ${new Timestamp(timestamps.min)}, max: ${new Timestamp(timestamps.max)}).")
val newState = if (state.exists) {
println(s"User ${userId} has existing state.")
val oldState = state.get
SessionState(
startTimestampMs = math.min(oldState.startTimestampMs, timestamps.min),
endTimestampMs = math.max(oldState.endTimestampMs, timestamps.max)
)
} else {
println(s"User ${userId} has no existing state.")
SessionState(
startTimestampMs = timestamps.min,
endTimestampMs = timestamps.max
)
}
state.update(newState)
state.setTimeoutTimestamp(newState.endTimestampMs, "30 minutes")
println(s"User ${userId} state updated. Timeout now set to ${new Timestamp(newState.endTimestampMs + (30 * 60 * 1000))}")
SessionOutput(
userId = userId,
startTimestampMs = state.get.startTimestampMs,
endTimestampMs = state.get.endTimestampMs,
durationMs = state.get.durationMs,
expired = false
)
}
}

val eventsQuery = sessionized
.writeStream
.queryName("events")
.outputMode("update")
.format("console")
.start()

input.addData(
"""{"event_time": "2018-01-01T00:00:00", "user_id": "mike"}""",
"""{"event_time": "2018-01-01T00:01:00", "user_id": "mike"}""",
"""{"event_time": "2018-01-01T00:05:00", "user_id": "mike"}"""
)
input.addData(
"""{"event_time": "2018-01-01T00:45:00", "user_id": "mike"}"""
)
eventsQuery.processAllAvailable()
}

case class SessionState(startTimestampMs: Long, endTimestampMs: Long) {
def durationMs: Long = endTimestampMs - startTimestampMs
}

case class SessionOutput(userId: String, startTimestampMs: Long, endTimestampMs: Long, durationMs: Long, expired: Boolean)
}

该程序的输出是:
root
|-- event_time: timestamp (nullable = true)
|-- user_id: string (nullable = true)

state update for user mike (current watermark: 1969-12-31 19:00:00.0)
User mike has new events (min: 2018-01-01 00:00:00.0, max: 2018-01-01 00:05:00.0).
User mike has no existing state.
User mike state updated. Timeout now set to 2018-01-01 00:35:00.0
-------------------------------------------
Batch: 0
-------------------------------------------
+------+----------------+--------------+----------+-------+
|userId|startTimestampMs|endTimestampMs|durationMs|expired|
+------+----------------+--------------+----------+-------+
| mike| 1514782800000| 1514783100000| 300000| false|
+------+----------------+--------------+----------+-------+

state update for user mike (current watermark: 2017-12-31 23:05:00.0)
User mike has new events (min: 2018-01-01 00:45:00.0, max: 2018-01-01 00:45:00.0).
User mike has existing state.
User mike state updated. Timeout now set to 2018-01-01 01:15:00.0
-------------------------------------------
Batch: 1
-------------------------------------------
+------+----------------+--------------+----------+-------+
|userId|startTimestampMs|endTimestampMs|durationMs|expired|
+------+----------------+--------------+----------+-------+
| mike| 1514782800000| 1514785500000| 2700000| false|
+------+----------------+--------------+----------+-------+

鉴于我的 session 定义,第二批中的单个事件应触发 session 状态到期,从而触发新 session 。但是,由于水印 ( 2017-12-31 23:05:00.0 ) 没有超过状态的超时时间 ( 2018-01-01 00:35:00.0 ),状态不会过期并且事件被错误地添加到现有 session 中,尽管距离最近的事件已经过去了 30 多分钟上一批的时间戳。

我认为 session 状态过期的唯一方法是,如果在批处理中收到来自不同用户的足够多的事件,以使水印超过 mike 的状态超时时间。 .

我想一个人也可能会弄乱流的水印,但我想不出我将如何做到这一点来完成我的用例。

这是准确的吗?我是否遗漏了如何在 Spark 中正确执行基于事件时间的 session 化?

最佳答案

如果水印间隔大于 session 间隙持续时间,您提供的实现似乎不起作用。

对于您显示的工作逻辑,您需要将水印间隔设置为 < 30 分钟。

如果确实希望水印间隔独立于(或超过) session 间隙持续时间,则需要等到水印通过(水印+间隙)才能使状态过期。合并逻辑似乎是盲目地合并窗口。这应该在合并之前考虑间隙持续时间。

关于apache-spark - Spark Structured Streaming 是否可以进行适当的事件时间 session ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51810460/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com