如何创建WindowSpec在当前行之前和之后对每种类型的行进行计数?

时间:2018-09-04 00:21:30

标签: scala apache-spark apache-spark-sql windowing

我必须实现一个以事件为中心的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

0 个答案:

没有答案