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 在大数据处理中如何高效实现连接操作。

相关推荐
Data跳动2 小时前
Spark内存都消耗在哪里了?
大数据·分布式·spark
lucky_syq4 小时前
流式处理,为什么Flink比Spark Streaming好?
大数据·flink·spark
goTsHgo8 小时前
在 Spark 上实现 Graph Embedding
大数据·spark·embedding
程序猿小柒9 小时前
【Spark】Spark SQL执行计划-精简版
大数据·sql·spark
隔着天花板看星星9 小时前
Spark-Streaming集成Kafka
大数据·分布式·中间件·spark·kafka
lucky_syq2 天前
Spark和Hive的区别
大数据·hive·spark
隔着天花板看星星2 天前
Spark-Streaming receiver模式源码解析
大数据·分布式·spark
Data跳动3 天前
Spark 运行时对哪些数据会做缓存?
java·缓存·spark
隔着天花板看星星3 天前
Spark-Streaming性能调优
大数据·分布式·spark