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操作有什么区别
我在使用这两种操作时出现了混淆,下面来看下它们之间的区别
核心区别
- Window操作
Window操作是一种逐行计算的操作,它不会改变数据的行数,只是为每一行添加新的计算列。 - 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
这个操作:
- 为每一行添加一个 segment_ count 列,表示该segment_id的总记录数
- 然后过滤掉记录数小于100的行
- 最后删除临时列 segment_count
- 行数可能会减少,但不是通过聚合,而是通过过滤
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
这个操作:
- 先对数据进行聚合,得到每个segment_id的记录数
- 过滤掉记录数小于100的segment_id
- 将结果与原始数据进行join操作
- 行数可能会减少,通过聚合和连接实现
Shuffle情况分析
-
Window版本:
- 不触发shuffle(假设数据已经按segment_id分区)
- 在每个分区内独立计算
-
GroupBy版本:
- 触发shuffle(需要将相同segment_id的数据发送到同一节点)
- 然后再触发一次shuffle(join操作需要将数据重新分布)
为什么结果相同但性能不同
虽然两种方法最终得到的数据是相同的,但它们的执行过程完全不同:
-
Window版本:
- 保持原有数据行数
- 通过窗口函数计算每个分组的统计信息
- 通过过滤操作移除不需要的行
- 只在必要时触发shuffle
-
GroupBy版本:
- 先聚合减少行数
- 再通过join操作恢复详细数据
- 多次触发shuffle
使用Window函数的版本更加高效,因为它:
- 避免了多次shuffle操作
- 保持了数据的局部性
- 减少了网络传输开销
- 实现了相同的业务逻辑
这就是为什么我们推荐使用Window函数替代GroupBy + Join的原因