spark性能优化2:Window操作和groupBy操作的区别

GroupBy操作

什么时候触发shuffle

python 复制代码
# 以下操作都会触发shuffle
df.groupBy("column").count()
df.groupBy("column").sum("value")
df.groupBy("column").agg(F.sum("value"), F.avg("value"))

为什么触发shuffle

因为groupBy需要将具有相同key的数据重新分布到同一个分区中进行聚合计算。

Window操作

什么时候不触发shuffle

python 复制代码
pythonfrom pyspark.sql.window import Window

# 如果数据已经按partitionBy的列进行了分区,则不会触发shuffle
window1 = Window.partitionBy("segment_id")
df.withColumn("count", F.count("*").over(window1))

# 即使添加排序也不会触发shuffle(只要数据已经按partitionBy列分区)
window2 = Window.partitionBy("segment_id").orderBy("event_time")
df.withColumn("rank", F.row_number().over(window2))

什么时候会触发shuffle

Window操作本身不会触发shuffle,但如果数据没有按照 partitionB y 的列进行预先分区,则在执行Window操作前可能需要进行shuffle来重新组织数据。

Window操作和GroupBy操作有什么区别

我在使用这两种操作时出现了混淆,下面来看下它们之间的区别

核心区别

  1. Window操作
    Window操作是一种逐行计算的操作,它不会改变数据的行数,只是为每一行添加新的计算列。
  2. GroupBy操作
    GroupBy操作是一种聚合操作,它会将多行数据聚合成一行,减少数据的行数。
    举个具体例子

假设我们有以下数据:

bash 复制代码
segment_id	value
A	10
A	20
B	30
B	40

使用Window操作:

python 复制代码
pythonwindow_spec = Window.partitionBy("segment_id")
df.withColumn("sum_value", F.sum("value").over(window_spec))

结果:

bash 复制代码
segment_id	value	sum_value
A	10	30
A	20	30
B	30	70
B	40	70

使用GroupBy操作:

python 复制代码
pythondf.groupBy("segment_id").sum("value")

结果:

bash 复制代码
segment_id	sum(value)
A	30
B	70

Window版本 (优化后):

python 复制代码
def segment_count_over_100(df):
    """保留分段条数大于100的数据"""
    # 使用Window函数避免shuffle操作
    window_spec = Window.partitionBy("segment_id")
    df_with_count = df.withColumn("segment_count", F.count("*").over(window_spec)) \
                     .filter(F.col("segment_count") > 100) \
                     .drop("segment_count")
    return df_with_count

这个操作:

  1. 为每一行添加一个 segment_ count 列,表示该segment_id的总记录数
  2. 然后过滤掉记录数小于100的行
  3. 最后删除临时列 segment_count
  4. 行数可能会减少,但不是通过聚合,而是通过过滤

GroupBy版本 (原始):

python 复制代码
def segment_count_over_100(df):
    """保留分段条数大于100的数据"""
    # 统计segment的数据的条数,保留大于100条数的数据
    count_df = df.groupBy("segment_id").count().filter(F.col("count") > 100)
    df = df.join(count_df, "segment_id").drop("count")
    return df

这个操作:

  1. 先对数据进行聚合,得到每个segment_id的记录数
  2. 过滤掉记录数小于100的segment_id
  3. 将结果与原始数据进行join操作
  4. 行数可能会减少,通过聚合和连接实现

Shuffle情况分析

  • Window版本:

    • 不触发shuffle(假设数据已经按segment_id分区)
    • 在每个分区内独立计算
  • GroupBy版本:

    • 触发shuffle(需要将相同segment_id的数据发送到同一节点)
    • 然后再触发一次shuffle(join操作需要将数据重新分布)

为什么结果相同但性能不同

虽然两种方法最终得到的数据是相同的,但它们的执行过程完全不同:

  1. Window版本:

    • 保持原有数据行数
    • 通过窗口函数计算每个分组的统计信息
    • 通过过滤操作移除不需要的行
    • 只在必要时触发shuffle
  2. GroupBy版本:

    • 先聚合减少行数
    • 再通过join操作恢复详细数据
    • 多次触发shuffle

使用Window函数的版本更加高效,因为它:

  1. 避免了多次shuffle操作
  2. 保持了数据的局部性
  3. 减少了网络传输开销
  4. 实现了相同的业务逻辑

这就是为什么我们推荐使用Window函数替代GroupBy + Join的原因

相关推荐
天天进步20152 小时前
从零开始构建现代化React应用:最佳实践与性能优化
前端·react.js·性能优化
勇哥的编程江湖3 小时前
本地搭建Flinkcdc-mysql-kafka-flink-Doris实时数据集成
大数据·flink
百胜软件@百胜软件3 小时前
百胜软件做客华为云生态直播间:全渠道中台如何赋能零售数字化与全球布局?
大数据·数据库架构
九河云3 小时前
华为云ECS与Flexus云服务器X实例:差异解析与选型指南
大数据·运维·服务器·网络·人工智能·华为云
AI优秘企业大脑3 小时前
如何提升自动化业务流程的效率?
大数据·人工智能
007tg3 小时前
Telegram SCRM 系统构建指南:自动化营销与客户管理实战
大数据·运维·自动化
IvanCodes4 小时前
openGauss安装部署详细教程
大数据·数据库·sql·opengauss
小武~4 小时前
嵌入式Linux系统性能优化:深入剖析I/O性能瓶颈
linux·运维·性能优化
John_ToDebug4 小时前
【深度解析】Performance API 与 UKM:从开发者工具到浏览器遥测,全面解锁 Web 性能优化格局
chrome·性能优化