Spark RDD各种join算子从源码层分析实现方式

在 Spark RDD 中,joinleftOuterJoinrightOuterJoinfullOuterJoin 等多个 Join 操作符都使用了 cogroup 进行底层实现。cogroup 是 Spark 中的一种底层分组操作,可以将两个或多个 RDD 中同一键的数据分组到一起,为各种 Join 操作提供了基础。下面我们从源码实现角度来分析这些 Join 操作符的实现原理,并列出相关的核心代码。

1. join

join 是最常用的连接操作,它会返回两个 RDD 中键相同的元素对。join 操作底层依赖 cogroup 实现:

scala 复制代码
def join[W](other: RDD[(K, W)], numPartitions: Int): RDD[(K, (V, W))] = {
  this.cogroup(other, numPartitions).flatMapValues {
    case (vs, ws) => for (v <- vs.iterator; w <- ws.iterator) yield (v, w)
  }
}

实现过程

  1. cogroup 将两个 RDD 中相同的键的数据组合成 (K, (Iterable[V], Iterable[W])) 形式。
  2. 然后通过 flatMapValues 遍历所有相同键的值对 (v, w),形成最终的 (K, (V, W)) 格式。

示例

python 复制代码
rdd1 = sc.parallelize([("a", 1), ("b", 2)])
rdd2 = sc.parallelize([("a", 3), ("b", 4)])
joined = rdd1.join(rdd2)
print(joined.collect())
# 输出: [('a', (1, 3)), ('b', (2, 4))]

2. leftOuterJoin

leftOuterJoin 会返回左侧 RDD 的所有键,并对右侧 RDD 中匹配的键进行连接,未匹配到的则返回 None

scala 复制代码
def leftOuterJoin[W](other: RDD[(K, W)], numPartitions: Int): RDD[(K, (V, Option[W]))] = {
  this.cogroup(other, numPartitions).flatMapValues {
    case (vs, ws) =>
      if (ws.isEmpty) vs.iterator.map(v => (v, None))
      else for (v <- vs.iterator; w <- ws.iterator) yield (v, Some(w))
  }
}

实现过程

  1. 使用 cogroup 分组得到 (K, (Iterable[V], Iterable[W])) 格式。
  2. 对于左侧 RDD 的值 vs,如果右侧 RDD 的 ws 为空,则返回 None;否则返回 (v, Some(w))

示例

python 复制代码
rdd1 = sc.parallelize([("a", 1), ("b", 2)])
rdd2 = sc.parallelize([("a", 3)])
left_joined = rdd1.leftOuterJoin(rdd2)
print(left_joined.collect())
# 输出: [('a', (1, Some(3))), ('b', (2, None))]

3. rightOuterJoin

rightOuterJoin 返回右侧 RDD 的所有键,未匹配的左侧 RDD 值则为 None

scala 复制代码
def rightOuterJoin[W](other: RDD[(K, W)], numPartitions: Int): RDD[(K, (Option[V], W))] = {
  this.cogroup(other, numPartitions).flatMapValues {
    case (vs, ws) =>
      if (vs.isEmpty) ws.iterator.map(w => (None, w))
      else for (v <- vs.iterator; w <- ws.iterator) yield (Some(v), w)
  }
}

实现过程

  1. cogroup 将两个 RDD 分组。
  2. 对右侧 RDD 中的值 ws,如果左侧 RDD 中 vs 为空,则返回 None;否则返回 (Some(v), w)

示例

python 复制代码
rdd1 = sc.parallelize([("a", 1)])
rdd2 = sc.parallelize([("a", 3), ("b", 4)])
right_joined = rdd1.rightOuterJoin(rdd2)
print(right_joined.collect())
# 输出: [('a', (Some(1), 3)), ('b', (None, 4))]

4. fullOuterJoin

fullOuterJoin 会返回左右两个 RDD 的所有键。未匹配的键对应的值为 None

scala 复制代码
def fullOuterJoin[W](other: RDD[(K, W)], numPartitions: Int): RDD[(K, (Option[V], Option[W]))] = {
  this.cogroup(other, numPartitions).flatMapValues {
    case (vs, ws) =>
      if (vs.isEmpty) ws.iterator.map(w => (None, Some(w)))
      else if (ws.isEmpty) vs.iterator.map(v => (Some(v), None))
      else for (v <- vs.iterator; w <- ws.iterator) yield (Some(v), Some(w))
  }
}

实现过程

  1. 使用 cogroup 进行键的分组。
  2. 对于每个键的 vsws,分别判断它们是否为空,如果为空则返回 None

示例

python 复制代码
rdd1 = sc.parallelize([("a", 1), ("b", 2)])
rdd2 = sc.parallelize([("a", 3), ("c", 4)])
full_joined = rdd1.fullOuterJoin(rdd2)
print(full_joined.collect())
# 输出: [('a', (Some(1), Some(3))), ('b', (Some(2), None)), ('c', (None, Some(4)))]

cogroup 的作用

cogroup 是 Spark 中连接操作的核心,通过对两个或多个 RDD 进行键分组,将相同键的数据放到一个集合中,使得不同类型的 Join 操作能够灵活实现。通过 cogroup,Spark 可以高效地将键相同的值合并在一起,而不需要在每个连接操作中重写分组逻辑。

总结

  • 底层共用 :所有 join 操作都使用 cogroup 进行分组,然后通过对分组后的键值对进行遍历和组合来实现不同的 Join 类型。
  • 性能优化cogroup 提供了键值对的高效分组机制,减少了 Join 操作中的数据传输量,从而提升了连接操作的性能。

这几种 Join 操作的实现逻辑和底层调用可以帮助我们理解 Spark 在大数据处理中如何高效实现连接操作。

相关推荐
abcy07121313 小时前
spark核心组件
flink·spark
极光代码工作室2 天前
基于Spark的日志监控与分析平台
大数据·hadoop·python·spark·数据可视化
Leo.yuan3 天前
数据仓库建设怎么做?从源数据到分析报表全流程讲清
大数据·分布式·spark
OneNobody4 天前
Spark SQL AQE工作原理源码剖析
大数据·sql·spark
humbinal4 天前
同时支持 gui & cli 的 parquet 文件查看工具,高性能小清新!
hive·python·rust·spark·开源·github·parquet
阿里云大数据AI技术4 天前
阿里云 EMR Serverless Spark 全托管 Ray 再进化:加速构建全模态数据处理新基建
人工智能·spark
LitchiCheng5 天前
轻量化微调 Qwen2.5 LoRA 训练全流程详解
大数据·人工智能·spark
董可伦5 天前
Spark 源码 | Yarn Client 模式提交流程(五)
大数据·spark
Gent_倪6 天前
万字详解:数据库、数据仓库、数据湖、湖仓一体
数据库·数据仓库·spark