我必须实现一个以事件为中心的Windowing批处理,并使用不同数量的事件名称。
规则如下,对于某个事件,每次发生时,我们都会根据特定的时间窗口对所有其他事件求和。
action1 00:01
action2 00:02
action1 00:03
action3 00:04
action3 00:05
对于上述数据集,应为:
window_before: Map(action1 -> 1)
window_after: Map(action1 -> 1, action3 -> 2)
为了实现这一点,我们使用WindowSpec
和一个自定义udaf,它将所有计数器聚合到一个映射中。 udaf是必需的,因为操作名称的数目是完全任意的。
当然,起初,UDAF使用的Spark催化转化器非常慢。
现在我已经达到了我认为是一个不错的最佳选择,在这里我只维护了带有不可变列表(较短的GC时间,较低的迭代器开销)的键和值数组,这些列表都序列化为二进制,因此Scala运行时可以处理装箱/取消装箱,而不使用Spark,而是使用字节数组而不是字符串。
问题在于,某些散乱问题非常棘手,并且无法并行化工作负载,这与我们具有静态列数并且仅对数字列求和/计数不同。
我尝试测试另一种技术,即我创建了等于事件最大基数的列数,然后聚合回地图,但是投影中的列数只是扼杀了火花(轻松地思考一千列)
问题之一是庞大的散乱数据,大多数情况下,即使适当地重新分区,单个分区(诸如userid,app之类)的占用时间也比中位数长100倍。
还有其他人遇到类似的问题吗?
示例WindowSpec
:
val windowSpec = Window
.partitionBy($"id", $"product_id")
.orderBy("time")
.rangeBetween(-30days, -1)
然后
df.withColumn("over30days", myUdaf("name", "count").over(windowSpec))
UDAF的原始版本:
class UDAF[A] {
private var zero: A = ev.zero
val dt = schemaFor[A].dataType
override def bufferSchema: StructType =
StructType(StructField("actions", MapType(StringType, dt) :: Nil)
override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
name = row.get(0)
count = row.get(1)
buffer.update(name, buffer.getOrElse(name, ev.zero) + count)
}
}
我的当前版本比上面的朴素版本可读性差,但是有效地执行了相同的两个二进制数组来规避CatalystConverters
。