Scala 使用多个spark窗口是件坏事吗?

Scala 使用多个spark窗口是件坏事吗?,scala,dataframe,apache-spark,apache-spark-sql,Scala,Dataframe,Apache Spark,Apache Spark Sql,最近,我开始研究spark窗口,试图了解在应用这些窗口功能时,spark executors的引擎盖下会发生什么。 我的问题是,由于每个窗口都必须使用partitionBy函数创建,这意味着在集群中洗牌数据,所以使用多个窗口是否正常 例如,我有一个数据帧: cli date item1 item2 --------- ---------- ------- ------- 1234567 20191030 A D

最近,我开始研究spark窗口,试图了解在应用这些窗口功能时,spark executors的引擎盖下会发生什么。 我的问题是,由于每个窗口都必须使用partitionBy函数创建,这意味着在集群中洗牌数据,所以使用多个窗口是否正常

例如,我有一个数据帧:

    cli       date     item1   item2     
 --------- ---------- ------- -------
  1234567   20191030   A       D         
  1234567   20191030   B       D         
  1234567   20191029   A       E         
  1234567   20191026   A       F         
  7456123   20191026   C       D         
  7456123   20191025   C       F         
这里的目的是根据历史计算每个客户在每个日期的每个项目的频率

例如,20191030的客户1234567使用了20191030中的4项_1,并向后使用,因此A的频率为3/4,B的频率为1/4

我选择使用windows计算每天的这些频率,因为它为每行计算一个值,但我需要使用三个窗口:

// This will give me the number of items used by a client
// in that day and all history.
val lByCliWindow = Window.partitionBy(col("cli")) 

// This will give me how many times a client used this exact item_1 A in
// that day and back in history (here my history is 120 days)
val lByCliItem1Window = Window
    .partitionBy(col("cli"), col("item_1"))
    .orderBy(to_timestamp(col("date"), "yyyyMMdd").cast(LongType))
    .rangeBetween(-86400*120,0) 

// This will give me how many times a client used this exact item_3 F in
// that day and back in history (here my history is 120 days)
val lByCliItem2Window = Window
    .partitionBy(col("cli"), col("item_2"))
    .orderBy(to_timestamp(col("date"), "yyyyMMdd").cast(LongType))
    .rangeBetween(-86400*120,0) 
预期产出为:

  cli       date         frequency_item1              frequency_item2
 --------- ---------- ------------------------- --------------------------------
  1234567   20191030   Map(A -> 3/4, B -> 1/4)   Map(D -> 2/4, E -> 1/4, F 1/4)
  1234567   20191029   Map(A -> 2/2)             Map(E -> 1/2, F -> 1/2)            
  1234567   20191026   Map(A -> 1/1)             Map(F -> 1/1)
  7456123   20191026   Map(C -> 2/2)             Map(D -> 1/2, F -> 1/2)
  7456123   20191025   Map(C -> 1/1)             Map(F -> 1/1)
当我解释这种方法时,我可以看到很多hashpartitioning等的交换计划,这是非常值得期待的,因为我们每次都在做partitionBy

假设我有将近30个变量,这意味着30倍的数据分区。这是大量的洗牌

我想了解的是这种方法正常吗?spark在并行分区上的工作是否会在同一时间创建多个窗口,从而在同一时间或顺序以多种不同的方式对数据帧进行分区

我们可以使用多个窗口吗?groupBy shuffle或partitionBy windows shuffle的成本更高的是什么


感谢您的回复,请毫不犹豫地提出使用windows计算频率的不同方法。

我有一个只涉及一个窗口的解决方案。我会用评论来解释

//您感兴趣的专栏 val items=df.columns.filter uu启动带有项目的 //收集列表聚合。它避免重复。我们将按cli和日期分组。 val aggs=items.mapc=>collect_listcolc as c //cli上的窗口,按日期排序。 val win=Window.partitionBycli.orderBydate //计算所需频率的UDF //由于我们进行的第一次聚合,它将seq的seq作为输入 val compute_freqs=udfs:Seq[Seq[String]=>{ val flat_s=s展平 val total=平面尺寸 flat_.groupByidentity.mapValues_.size.toDouble/ToTotal } //对于每个项目,我们收集窗口上的值,并计算频率 val频率_列=项目 .mapitem=>compute\u frequenccollect\u listcolitem胜出 .aliassffrequency_u$item //那我们什么都用 val结果=df .groupBycli,日期 .aggs.head,aggs.tail:_* .selectSeqcli,date.mapcol++frequency_列:_* .orderBy$cli,$date desc 结果如下:

scala>result.showfalse +----+----+-----------+----------------+ |cli |日期|频率|项目1 |频率|项目2| +----+----+-----------+----------------+ |1234567 | 20191030 |[A->0.75,B->0.25]|[D->0.5,F->0.25,E->0.25]| |1234567 | 20191029 |[A->1.0]|[F->0.5,E->0.5]| |1234567 | 20191026 |[A->1.0]|[F->1.0]| |7456123 | 20191026 |[C->1.0]|[D->0.5,F->0.5]| |7456123 | 20191025 |[C->1.0]|[F->1.0]| +----+----+-----------+----------------+
你能提供你期望的结果吗?此外,字符串版本而不是数据的图像也会有很大帮助-你好,奥利,谢谢你的支持,我按照你的建议做了。非常感谢您的回答,事实上,我想提一下,我想避免UDF,因为我的数据库是非常巨大的10亿条记录,所以序列化所有数据非常昂贵,我只想使用spark sql内置函数。但我真的很感激你的方法,我会尝试一下,然后再回复你,但与此同时,你能不能对这个问题进行投票,以便我们能得到更多的想法?我理解你为什么想要避免UDF。如果您使用spark 2.4,可能有一种解决方法。但请注意,尽管UDF非常昂贵,但与几十次洗牌数据相比,这算不了什么。ps:10亿对Spark来说并不算多。这段代码可能只需要很少的时间。试试看,让我知道。如果速度不够快,我们会想到别的办法;另外,请注意,并非所有内容都将被序列化。每个客户端只有一条记录,日期元组。您好,奥利,我尝试了您的解决方案,该解决方案导致了MetadataFetchFailedException异常:shuffle 1缺少一个输出位置,这显然与内存开销有关。我尝试将每个执行器的内存增加到32Gb,用于20个执行器(共2个核)。他们产生了12GB的随机写入,然后在接下来的200个任务中,它在执行随机读取时失败,出现异常。我很困惑,仅仅12 gb的随机播放怎么会失败?这太多了吗?一般来说,这是完全可以管理的。执行器上有多少磁盘空间?