【Spark原理系列】 Window窗口计算原理用法示例源码分析

本文深入探讨Spark窗口计算的原理,包括数据分区、排序和窗口函数计算。介绍了Window对象、WindowSpec和sparksql window function的相关概念,并通过示例和源码分析展示了窗口函数如RowNumber、CumeDist、NTile等的用法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spark Window窗口计算原理用法示例源码分析 点击这里免费看全文

原理

Spark的窗口计算是通过以下步骤实现的:

  1. 数据分区:首先,数据集根据指定的分区键进行分区。每个分区中的数据将作为一个独立的窗口进行计算。

  2. 排序:在每个分区内,根据指定的排序规则对数据进行排序。这确保了在窗口函数应用之前,数据按照正确的顺序进行处理。

  3. 窗口框架定义:根据窗口规范中定义的窗口帧类型、起始边界和结束边界,确定每个数据行所属的窗口范围。

  4. 窗口函数计算:在每个窗口内,应用指定的窗口函数进行计算。窗口函数可以是聚合函数(如SUMAVG)或分析函数(如LEADLAG)。

  5. 窗口函数结果返回:计算完窗口函数后,将结果返回给调用方。通常,结果会随着输入数据的顺序进行排序,并按照指定的输出模式进行返回。

Spark的窗口计算是通过在各个阶段之间进行数据洗牌和排序来实现的。这涉及到对数据进行划分、排序和重新组合的操作,因此需要一定的计算和资源开销。

窗口计算在Spark中非常有用,它可以支持各种分析和处理需求,如行级别的聚合、排名、窗口函数等。通过使用窗口计算,可以在数据集上进行更精细和灵活的操作,并提供更详细的分析结果。

用法

Spark sql functions中的窗口函数

窗口函数是一种只能在窗口操作符的上下文中计算的函数。窗口函数提供了对数据分区内的数据进行聚合、排序和分析的功能。

常用的窗口函数,包括RowNumber、CumeDist、NTile、Rank、DenseRank和PercentRank等。这些窗口函数都继承自AggregateWindowFunction类,并实现了特定的逻辑来计算窗口函数的结果。

1.RowNumber函数

为每一行分配一个唯一的连续编号,根据窗口分区内的行的顺序进行编号。

2.CumeDist函数

计算值相对于分区中所有值的位置。它返回在分区中按顺序排列的当前行之前或与当前行相等的行数除以窗口分区中的总行数。

3.NTile函数

将窗口分区的行划分为指定数量的桶,范围从1到最多n。如果分区中的行数不能被均匀分成桶数,则余数值将按顺序分布在每个桶中,从第一个桶开始。

4.Rank函数

计算值在一组值中的排名。结果是在分区的排序中在当前行之前或与当前行相等的行数加上1。

5.DenseRank函数

计算值在一组值中的密集排名。结果是先前分配的排名值加1。与Rank不同,DenseRank不会在排名序列中产生间隙。

6.PercentRank函数

计算值在一组值中的百分比排名。结果是(rank - 1) / (n - 1),其中rank为排名值,n为窗口分区中的总行数。如果分区只包含一行,则函数返回0。

7.lag函数

返回当前行之前的offset行的值。如果当前行之前不足offset行,则返回null或指定的默认值。

8.lead函数

返回当前行之后的offset行的值。如果当前行之后不足offset行,则返回null或指定的默认值。

示例1

package org.example.spark

import org.apache.spark.sql.expressions.Window
import org.apache.spark.sql.functions._
import org.apache.spark.sql.{
   
   Column, SparkSession}

object WindowMethodsExample extends App {
   
   
  val spark = SparkSession.builder()
    .appName("WindowMethodsExample")
    .master("local[*]")
    .getOrCreate()

  import spark.implicits._

  // 创建示例数据集
  val data = Seq(
    ("A", 10),
    ("A", 20),
    ("B", 15),
    ("B", 25),
    ("C", 30),
    ("C", 40)
  )
  val df = data.toDF("group", "value")

  // 定义窗口规范
  val windowSpec = Window.partitionBy($"group").orderBy($"value")

  // 使用 orderBy 方法指定排序列(字符串形式)
  val orderedByString = df.withColumn("ordered_by_string", row_number().over(windowSpec.orderBy("value")))
  orderedByString.show()
//  +-----+-----+-----------------+
//  |group|value|ordered_by_string|
//  +-----+-----+-----------------+
//  |    B|   15|                1|
//  |    B|   25|                2|
//  |    C|   30|                1|
//  |    C|   40|                2|
//  |    A|   10|                1|
//  |    A|   20|                2|
//  +-----+-----+-----------------+

  // 使用 orderBy 方法指定排序列(Column 对象形式)
  val orderedByColumn = df.withColumn("ordered_by_column", row_number().over(windowSpec.orderBy($"value")))
  orderedByColumn.show()
//  +-----+-----+-----------------+
//  |group|value|ordered_by_column|
//  +-----+-----+-----------------+
//  |    B|   15|                1|
//  |    B|   25|                2|
//  |    C|   30|                1|
//  |    C|   40|                2|
//  |    A|   10|                1|
//  |    A|   20|                2|
//  +-----+-----+-----------------+


  // 使用 partitionBy 方法指定分区列(字符串形式)
  val partitionedByString = df.withColumn("partitioned_by_string", sum($"value").over(windowSpec.partitionBy("group")))
  partitionedByString.show()
//  +-----+-----+---------------------+
//  |group|value|partitioned_by_string|
//  +-----+-----+---------------------+
//  |    B|   15|                   15|
//  |    B|   25|                   40|
//  |    C|   30|                   30|
//  |    C|   40|                   70|
//  |    A|   10|                   10|
//  |    A|   20|                   30|
//  +-----+-----+---------------------+

  // 使用 partitionBy 方法指定分区列(Column 对象形式)
  val partitionedByColumn = df.withColumn("partitioned_by_column", sum($"value").over(windowSpec.partitionBy($"group")))
  partitionedByColumn.show()
//
//  +-----+-----+---------------------+
//  |group|value|partitioned_by_column|
//  +-----+-----+---------------------+
//  |    B|   15|                   15|
//  |    B|   25|                   40|
//  |    C|   30|                   30|
//  |    C|   40|                   70|
//  |    A|   10|                   10|
//  |    A|   20|                   30|
//  +-----+-----+---------------------+

  // 使用 rangeBetween 方法指定窗口范围(Column 对象形式)
  val rangeBetweenColumn = df.withColumn("range_between_column", sum($"value").over(windowSpec.rangeBetween(-3, 3)))
  rangeBetweenColumn.show()

//  +-----+-----+--------------------+
//  |group|value|range_between_column|
//  +-----+-----+--------------------+
//  |    B|   15|                  15|
//  |    B|   25|                  25|
//  |    C|   30|                  30|
//  |    C|   40|                  40|
//  |    A|   10|                  10|
//  |    A|   20|                  20|
//  +-----+-----+--------------------+


  // 使用 rangeBetween 方法指定窗口范围(Long 值形式)
  val rangeBetweenLong = df.withColumn("range_between_long", sum($"value").over(windowSpec.rangeBetween(Window.unboundedPreceding, Window.currentRow)))
  rangeBetweenLong.show()
//  +-----+-----+------------------+
//  |group|value|range_between_long|
//  +-----+-----+------------------+
//  |    B|   15|                15|
//  |    B|   25|                40|
//  |    C|   30|                30|
//  |    C|   40|                70|
//  |    A|   10|                10|
//  |    A|   20|                30|
//  +-----+-----+------------------+

  // 使用 rowsBetween 方法指定窗口范围
  val rowsBetween = df.withColumn("rows_between", sum($"value").over(windowSpec.rowsBetween(Window.unboundedPreceding, Window.currentRow)))
  rowsBetween.show()

//  +-----+-----+------------+
//  |group|value|rows_between|
//  +-----+-----+------------+
//  |    B|   15|          15|
//  |    B|   25|          40|
//  |    C|   30|          30|
//  |    C|   40|          70|
//  |    A|   10|          10|
//  |    A|   20|          30|
//  +-----+-----+------------+
  

  // 使用 unboundedFollowing 和 unboundedPreceding 方法指定无界边界
  val unboundedFollowing = df.withColumn("unbounded_following", sum($"value").over(windowSpec.rowsBetween(Window.unboundedPreceding, Window.unboundedFollowing)))
  unboundedFollowing.show()
//  +-----+-----+-------------------+
//  |group|value|unbounded_following|
//  +-----+-----+-------------------+
//  |    B|   15|                 40|
//  |    B|   25|                 40|
//  |    C|   30|                 70|
//  |    C|   40|                 70|
//  |    A|   10|                 30|
//  |    A|   20|                 30|
//  +-----+-----+-------------------+
  
  val unboundedPreceding = df.withColumn("unbounded_preceding", sum($"value").over(windowSpec.rowsBetween(Window.unboundedPreceding, Window.currentRow)))
  unboundedPreceding.show()
//  +-----+-----+-------------------+
//  |group|value|unbounded_preceding|
//  +-----+-----+-------------------+
//  |    B|   15|                 15|
//  |    B|   25|                 40|
//  |    C|   30|                 30|
//  |    C|   40|                 70|
//  |    A|   10|                 10|
//  |    A|   20|                 30|
//  +-----+-----+-------------------+


  spark.stop()
}

示例2

object WindowFunctionsDemo {
   
   
  def main(args: Array[String]): Unit = {
   
   
    val spark = SparkSession.builder()
      .appName("Window Functions Demo")
      .master("local")
      .getOrCreate()

    import spark.implicits._

    // 创建示例数据
    val data = Seq(
      ("Alice", "A", 100),
      ("Bob", "B", 200),
      ("Charlie", "C", 150),
      ("Dave", "A", 300),
      ("Eve", "B", 250),
      ("Frank", "C", 400)
    )
    val df = data.toDF("Name", "Group", "Value")

    // 定义窗口规范
    val windowSpec = Window.partitionBy("Group").orderBy("Value")

    // 使用窗口函数进行分析
    val result = df.withColumn("CumeDist", functions.cume_dist().over(windowSpec))
      .withColumn("DenseRank", functions.dense_rank().over(windowSpec))
      .withColumn("LagValue", functions.lag($"Value", 1).over(windowSpec))
      .withColumn("LeadValue", functions.lead($"Value", 1).over(windowSpec))
      .withColumn("Ntile", functions.ntile(3).over(windowSpec))
      .withColumn("PercentRank", functions.percent_rank().over(windowSpec))
      .withColumn("Rank", functions.rank().over(windowSpec))
      .withColumn("RowNumber", functions.row_number().over(windowSpec))

    result.show()

//    +-------+-----+-----+--------+---------+--------+---------+-----+-----------+----+---------+
//    |   Name|Group|Value|CumeDist|DenseRank|LagValue|LeadValue|Ntile|PercentRank|Rank|RowNumber|
//    +-------+-----+-----+--------+---------+--------+---------+-----+-----------+----+---------+
//    |    Bob|    B|  200|     0.5|        1|    null|      250|    1|        0.0|   1|        1|
//    |    Eve|    B|  250|     1.0|        2|     200|     null|    2|        1.0|   2|        2|
//    |Charlie|    C|  150|     0.5|        1|    null|      400|    1|        0.0|   1|        1|
//    |  Frank|    C|  400|     1.0|        2|     150|     null|    2|        1.0|   2|        2|
//    |  Alice|    A|  100|     0.5|        1|    null|      300|    1|        0.0|   1|        1|
//    |   Dave|    A|  300|     1.0|        2|     100|     null|    2|        1.0|   2|        2|
//    +-------+-----+-----+--------+---------+--------+---------+-----+-----------+----+---------+
  }
}

中文源码

Window

DataFrame中定义窗口的实用函数Window对象包含了多个静态方法,用于构建窗口规范。

其中,orderBy方法用于指定窗口规范中的排序列。它接受一个或多个列名,并将它们转换为Column对象,然后使用functions.sort方法对这些列进行排序。最终,它返回一个Column对象,该对象可以用作窗口规范中的排序规则。

partitionBy方法用于指定窗口规范中的分区列。它接受一个或多个列名,并将它们转换为Column对象,然后使用这些列创建一个新的WindowSpec对象。这个WindowSpec对象表示窗口规范中的分区规则。

除了上述两个方法外,Window对象还提供其他用于构建窗口规范的方法,例如rangeBetweenrowsBetween等。

总结起来,Window对象是一个辅助类,用于创建窗口规范。它提供了多个静态方法,用于构建窗口规范中的排序列和分区列,并且还提供了其他方法用于定义窗口帧的范围。

/**
 * DataFrame中定义窗口的实用函数。
 *
 * {
   
   {
   
   {
 *   // 按照country分区,按照date排序,行范围在无界前至当前行之间
 *   Window.partitionBy("country").orderBy("date")
 *     .rowsBetween(Window.unboundedPreceding, Window.currentRow)
 *
 *   // 按照country分区,按照date排序,行范围在3行前至3行后之间
 *   Window.partitionBy("country").orderBy("date").rowsBetween(-3, 3)
 * }}}
 *
 * @note 当未定义排序时,默认使用无界窗口帧(rowFrame,unboundedPreceding,unboundedFollowing)。
 *       当定义了排序时,默认使用增长型窗口帧(rangeFrame,unboundedPreceding,currentRow)。
 *
 * @since 1.4.0
 */
@InterfaceStability.Stable
object Window {
   
   

  /**
   * 使用指定的列名进行分区创建[[WindowSpec]]。
   * @since 1.4.0
   */
  @scala.annotation.varargs
  def partitionBy
<think>好的,用户需要关于大数据开发岗位的简历优化建议。首先,我得结合用户提供的引用内容,特别是引用[3]提到数据库编程是必备技能,所以简历中要突出相关技术。另外,引用[1]和[2]涉及性能调优和数据倾斜处理,这些都是大数据中的关键点,应该作为重点展示。 用户可能希望简历既符合岗位要求,又能突出实际项目经验。需要分结构清晰的部分,比如技术栈、项目经验、优化成果等。要注意使用行业关键词,比如Hadoop、Spark、Flink,这些是简历筛选时的重点。 另外,用户提供的引用中有提到调优前后的对比,比如map个数调整带来的性能提升,这部分在项目描述中应该具体量化,展示实际效果。同时,数据倾斜处理的方法,如Flink的Window优化,也是值得强调的点,能体现问题解决能力。 可能还需要考虑简历的格式和简洁性,避免冗长,用STAR法则描述项目,突出行动和结果。另外,教育背景和证书部分也要简明扼要,特别是相关的大数据认证,能增加竞争力。 最后,用户可能需要避免常见错误,比如技术栈堆砌而不深入,或者项目描述过于笼统。需要提醒用户量化成果,使用具体数字,并针对不同岗位定制简历,突出相关经验。</think>### 大数据开发岗位简历优化建议 以下从技术展示、项目描述、成果量化等角度提供优化思路,结合行业需求与招聘方关注点: --- #### 一、**技术栈模块:精准匹配岗位关键词** 1. **核心技术分层展示** - 分布式计算框架:$Spark$、$Flink$、$Hadoop$(需标注生态组件如$HDFS/YARN$) - 数据处理工具:$Kafka$、$Hive$、$HBase$ - 编程语言:$Scala$(大数据开发核心语言)、$Python$、$SQL$ - 云平台:$AWS EMR$、$阿里云MaxCompute$(根据投递企业调整) > *示例:熟悉Spark性能调优,包括$shuffle$优化、$RDD$缓存策略[^1]* 2. **避免堆砌技术名词** 重点描述**实际应用场景**,例如: > "使用$Flink$实现实时数据管道,解决$Window$数据倾斜问题(通过两阶段聚合+动态分区)[^2]" --- #### 二、**项目经验:STAR法则+量化成果** 1. **优化类项目模板** - **背景**:原始任务存在$MapReduce$效率低(如单节点处理$2TB$日志) - **行动**:重构为$Spark SQL$,调整$partition$数为$200$,启用$broadcast join$ - **结果**:执行时间从$4.5h$降至$28min$,资源消耗减少$40%$ 2. **故障排查案例** > "发现$Kafka$消费者延迟,通过调整$fetch.min.bytes$与$max.poll.records$参数,将吞吐量提升$3倍$" --- #### 三、**附加亮点:凸显工程化能力** 1. **数据治理经验** - 描述数据质量监控体系设计(如用$Great Expectations$实现校验规则) - 参与过$元数据管理$或$数据血缘追踪$项目 2. **开源贡献** 若有$Spark/Flink$源码研究或$GitHub$项目,需单独列出并附链接 --- #### 四、**避坑指南** 1. **避免笼统描述** - ✖ "使用Hive进行数据分析" - ✔ "构建$Hive$分层数仓(ODS->DWD->DWS),设计$拉链表$处理缓慢变化维" 2. **证书选择** 优先列出$AWS Certified Data Analytics$、$Cloudera CCAH$等含金量高的认证 --- #### 五、**简历模板示例** ```markdown ### 大数据开发工程师 **技术栈** - 流处理: Flink(状态管理、CEP)、Kafka Connect - 性能调优: Spark内存模型、JVM垃圾回收策略 ### 项目经历 **实时风控系统(Flink+Redis)** - 设计基于时间窗口的异常检测规则,QPS达12万/秒 - 使用$布隆过滤器$减少Redis查询压力,降低集群成本35% ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BigDataMLApplication

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值