Scala Spark UDF未正确给出滚动计数

Scala Spark UDF未正确给出滚动计数,scala,apache-spark,user-defined-functions,Scala,Apache Spark,User Defined Functions,我有一个Spark UDF来计算列的滚动计数,精确到wrt时间。如果我需要计算24小时的滚动计数,例如时间为2020-10-02 09:04:00的条目,我需要回顾到2020-10-01 09:04:00(非常精确) 如果我在本地运行,滚动计数UDF工作正常并给出正确的计数,但当我在集群上运行时,它给出的结果不正确。下面是示例输入和输出 输入 预期产量 +---------+-----------------------+-----+ |OrderName|Time

我有一个Spark UDF来计算列的滚动计数,精确到wrt时间。如果我需要计算24小时的滚动计数,例如时间为2020-10-02 09:04:00的条目,我需要回顾到2020-10-01 09:04:00(非常精确)

如果我在本地运行,滚动计数UDF工作正常并给出正确的计数,但当我在集群上运行时,它给出的结果不正确。下面是示例输入和输出

输入

预期产量

+---------+-----------------------+-----+
|OrderName|Time                   |Count|
+---------+-----------------------+-----+
|a        |2020-07-11 23:58:45.538|1    |
|a        |2020-07-12 00:00:07.307|2    |
|a        |2020-07-12 00:01:08.817|3    |
|a        |2020-07-12 00:02:15.675|1    |
|a        |2020-07-12 00:05:48.277|1    |
+---------+-----------------------+-----+

最后两个输入值在本地为4和5,但在集群上不正确。我的最佳猜测是,数据正在跨执行器分发,udf也在每个执行器上并行调用。由于UDF的一个参数是column(本例中为Partition key-OrderName),如果是这样,我如何控制/纠正集群的行为。因此,它以正确的方式计算每个分区的正确计数。请提供任何建议

根据您的评论,您希望统计过去24小时内每条记录的总记录数

import org.apache.spark.sql.expressions.Window
import org.apache.spark.sql.types.LongType

//A sample data (Guessing from your question)
val df = Seq(("a","2020-07-10 23:58:45.438","1"),("a","2020-07-11 23:58:45.538","1"),("a","2020-07-11 23:58:45.638","1")).toDF("OrderName","Time","Count")

// Extract the UNIX TIMESTAMP for your time column
val df2 = df.withColumn("unix_time",concat(unix_timestamp($"Time"),split($"Time","\\.")(1)).cast(LongType))

val noOfMilisecondsDay : Long = 24*60*60*1000

//Create a window per `OrderName` and select rows from `current time - 24 hours` to `current time` 
val winSpec = Window.partitionBy("OrderName").orderBy("unix_time").rangeBetween(Window.currentRow - noOfMilisecondsDay, Window.currentRow)

// Final you perform your COUNT or SUM(COUNT) as per your need
val finalDf = df2.withColumn("tot_count", count("OrderName").over(winSpec))

//or val finalDf = df2.withColumn("tot_count", sum("Count").over(winSpec))

你能展示你的UDF代码吗?我不能完全共享它,它有点像UDF{(ordername:Partition,time:Range,Long)进程:{},UDF的初始要求,同一分区内的所有记录都是按日期排序的。它所做的是针对每个分区(此处的订单名称),如果新记录是针对现有分区的,则将记录添加到队列中,增加计数,然后检查wrt当前时间,如果队列中到目前为止的所有记录都在24小时内,如果不在24小时内,则从开始处删除记录(因为它是队列)如果您可以显示:
input data/dataframe
expected output/expected dataframe
,这将非常有用。我用input和expected output dataframes更新了问题。输入和输出都是数据帧,上面的方法很久以前就尝试过了,但它不适用于单个分区中包含大量数据的集群,比如说一百万条记录。spark作业永远不会完成,这就是使用UDF的原因,它通过在队列中维护来减少记录数,但这可能导致错误计数,因为数据正在分发。强制执行一个接一个地处理所有分区数据记录(顺序执行)的方法可以解决这个问题,但不确定您是如何理解这一点的。这是一个倾斜问题。请看一看。我们的想法是为
OrderName
创建一个24小时的窗口,明白了,但我有UDF,试图减少数据倾斜,但问题是,分区数据分布在不同的执行者之间,导致计数不正确。您不认为UDF可以解决我前面解释的队列方法的问题吗。可能我必须尝试像使用mapPartitions的迭代器转换一样,不确定如何做,需要探索。有什么建议吗?你共享的链接也是同样的问题,可能就是我认识的发布它的人。谢谢你分享这个链接,当这个问题被激活时,我正在与这个人讨论,这就是为什么我直接向你推荐工作解决方案:)。试试看,它肯定会减少倾斜。
import org.apache.spark.sql.expressions.Window
import org.apache.spark.sql.types.LongType

//A sample data (Guessing from your question)
val df = Seq(("a","2020-07-10 23:58:45.438","1"),("a","2020-07-11 23:58:45.538","1"),("a","2020-07-11 23:58:45.638","1")).toDF("OrderName","Time","Count")

// Extract the UNIX TIMESTAMP for your time column
val df2 = df.withColumn("unix_time",concat(unix_timestamp($"Time"),split($"Time","\\.")(1)).cast(LongType))

val noOfMilisecondsDay : Long = 24*60*60*1000

//Create a window per `OrderName` and select rows from `current time - 24 hours` to `current time` 
val winSpec = Window.partitionBy("OrderName").orderBy("unix_time").rangeBetween(Window.currentRow - noOfMilisecondsDay, Window.currentRow)

// Final you perform your COUNT or SUM(COUNT) as per your need
val finalDf = df2.withColumn("tot_count", count("OrderName").over(winSpec))

//or val finalDf = df2.withColumn("tot_count", sum("Count").over(winSpec))