Scala 在Spark结构化流媒体中,计算跨越批次的滚动度量的更好方法是什么?
我正在使用Azure上的事件中心的事件,并希望计算不是基于单个事件而是基于成对事件的度量。我发现了大量关于如何处理聚合的信息,但没有太多顺序相关的度量(例如超前、滞后)。我知道Spark Streaming中没有这种现成的功能 确定问题范围的假设:Scala 在Spark结构化流媒体中,计算跨越批次的滚动度量的更好方法是什么?,scala,apache-spark,spark-structured-streaming,Scala,Apache Spark,Spark Structured Streaming,我正在使用Azure上的事件中心的事件,并希望计算不是基于单个事件而是基于成对事件的度量。我发现了大量关于如何处理聚合的信息,但没有太多顺序相关的度量(例如超前、滞后)。我知道Spark Streaming中没有这种现成的功能 确定问题范围的假设: 我正在接收的设备的事件以正确的顺序出现。从同一设备发送的两条消息中,偏移量较大的一条表示最近的事件 事件中心中的唯一设备多于分区(即分区上的消息序列不能假定属于同一设备) 目标: 能够批量计算所有消息的超前和滞后列,即仅在新的增量到达时计算 +---
+---------+-----------------+--------+-----------+------------+
| batchId | deviceReference | offset | lagOffset | leadOffset |
+---------+-----------------+--------+-----------+------------+
| 100 | a | 1 | null | 3 |
| 100 | b | 2 | null | 5 |
| 100 | a | 3 | 1 | 6 |
| 200 | c | 4 | null | 7 |
| 200 | b | 5 | 2 | null |
| 300 | a | 6 | 3 | null |
| 300 | c | 7 | 4 | null |
+---------+-----------------+--------+-----------+------------+
lagOffset和leadOffset是我要计算的列,batchId是在给定时间从事件中心消耗的事件的标识符。显然,如果不访问deviceReference的上一个和下一个偏移量,每个batchId中的第一个和最后一个记录都将具有lag和leadOffset null,因此我想我应该尝试缓存
尝试
创建空DF并持久化(仅当第一次运行时,否则缓存存在)
创建窗口函数,用于提前期/滞后期计算,并用于排除最后一条记录(因为它将缺少提前期值,并将在下一批中保留(如果提前期已到达)
工艺流程
反射:
- 这感觉非常脆弱,就像我搞乱了batchCache DF中的状态一样,我会搞得一团糟。我本想使用内置功能作为检查点,但当我需要更改批处理范围以排除每个设备的最后一条记录时,我不知道如何使它们对我起作用
- 显然,我可以将超前滞后应用于流作业的目标表,但这会使计算变得更大、冗余,并且还会引入数据更新,除非每个设备的最后一条记录被切断(超前为空的记录)
- 我想我遗漏了一些重要的东西。查找事件的相邻事件对于我要计算的度量非常重要,我相信我不是唯一的一个。如果所有设备都有自己的分区,我想问题会得到解决,但由于事件中心中的分片机制是散列,我不知道如何实现有人能告诉我我是不是在错误的地方攻击这个吗?
- 如果我没有在错误的地方攻击它,那么我应该使用哪些Spark特性来增强它的健壮性呢
if (!positionCacheExists)
spark.createDataFrame(sc.emptyRDD[Row], batchCacheSchema)
.write
.partitionBy("deviceReference")
.format("json")
.mode(org.apache.spark.sql.SaveMode.Append)
.save(batchCachePath)
val w = Window.partitionBy("deviceReference").orderBy(desc("offset"))
val leadLag = Window.partitionBy("deviceReference").orderBy("offset")
incomingDeviceStream.writeStream
.foreachBatch { (batchDF: DataFrame, batchId: Long) =>
//empty if first time, otherwise contains the second latest events per device from previous batch
val batchCache = spark.read.schema(lastBatchSchema).format("json").load(positionCachePath)
val modBatch = batchDF.withColumn("maxRow", rank().over(w))
modBatch.join(batchCache,
modBatch("deviceReference")===batchCache("deviceReference") &&
modBatch("offset")>batchCache("offset"))
.withColumn("last", batchCache("offset"))
.union(modBatch.join(batchCache,
modBatch("deviceReference")===batchCatche("deviceReference"),
"left_anti")
.withColumn("last", lit(null)))
.withColumn("lagOffset", coalesce(lag(col("offset"),1).over(leadLag), col("last")).as("lagOffset"))
.withColumn("leadOffset", lead(col("offset"),1).over(leadLag).as("leadOffset"))
.filter(col("maxRow")>1)
.write
.partitionBy("deviceReference")
.format("parquet")
.mode(Append)
.save(deviceEventPath)
//Store the second latest event per device as the last will have null as lead and will be set when the next event comes
modBatch.filter(col("maxRow")===2)
.write
.partitionBy("deviceReference")
.format("json")
.mode(Overwrite)
.save(batchCachePath)
}
.trigger(Trigger.Once())
.start()
.awaitTermination()