join算子

本文深入探讨了Spark中各种Join操作(如左连接、右连接、全连接)的实现原理,通过cogroup功能模拟这些连接操作,并解析了collect、reduce及foreach等常见操作的底层机制。

join:

val rdd1 =sc.parallelize(List(("spark",1),("hadoop",1),("spark",2),("hadoop",2),("hive",2)))

 sc.parallelize(List(("spark",3),("hive",3),("spark",4),("hadoop",2)))

------->d.RDD[(String, (Int, Int))

这个有点类似

val  rdd0 = sc.flatMapValues(List("a"Array(1,2,3),("b",Array(4,5,6))))

----> (a,1),(a,2),(a,3)(b,4),(b,5),(b,6)

cogroup实现join的功能,

1.通过cogroup 实现局部分组聚合,好全局分组聚合从而得到(以Spark为例,list1中("spark"(1,2)),)

list2中("spark",3),完成区内聚合

2.通过flatmapValue----> ("spark",((1,2),3)) 然后遍历 t._.1iterator 和t._2.iterator 如果同时都存在组返回所以最后得出的结果为

("spark",(1,3)) ("spark",(2,3))

 

  //join,leftJoin,rightjoin,fulljoin
    //join底层默认的cogroup,cogroup用的是hashPartitioner,如果没有指定分区器,则默认为2个分区器
    //
    val conf = new SparkConf().setAppName("JoinDemo").setMaster("local[*]")
    val sc = new SparkContext(conf)

    val rdd1 = sc.parallelize(List(("spark",1), ("hadoop", 1), ("spark", 2), ("hive", 2),("flink", 2)), 2)

    val rdd2 = sc.parallelize(List(("spark", 3), ("hive", 3), ("hadoop", 4)), 2)

    //通过cogroup实现join类似的功能,从而了解join底层源码的工作原理
    val rdd3: RDD[(String, (Iterable[Int], Iterable[Int]))] = rdd1.cogroup(rdd2)
    val rdd4: RDD[(String, (Int, Int))] = rdd3.flatMapValues(t => {
      for (x <- t._1.iterator; y <- t._2.iterator) yield (x, y)
    })
    rdd4.saveAsTextFile("out3-join")
    

leftOutJoin


val conf = new SparkConf().setAppName("JoinDemo").setMaster("local[*]")
val sc = new SparkContext(conf)

val rdd1 = sc.parallelize(List(("spark",1), ("hadoop", 1), ("spark", 2), ("hive", 2),("flink", 2)), 2)

val rdd2 = sc.parallelize(List(("spark", 3), ("hive", 3), ("hadoop", 4)), 2)

val rdd: RDD[(String, (Int, Option[Int]))] = rdd1.leftOuterJoin(rdd2)

//使用cogroup实现类似leftOuterJoin的功能
val rdd3: RDD[(String, (Iterable[Int], Iterable[Int]))] = rdd1.cogroup(rdd2)
//Int来自于第一个RDD的value,Option[Int]来自于第二个RDD的value,如果有就是some,如果没有就none
val rdd4: RDD[(String, (Int, Option[Int]))] = rdd3.flatMapValues(t => {
  if (t._2.nonEmpty) {
    t._1.map((_, None))
  } else {
    for (x <- t._1.iterator; y <- t._2.iterator) yield (x, Some(y))
  }
})
rdd4.saveAsTextFile("out-leftjoin")

 

 

rightOuterJoin

    val conf = new SparkConf().setAppName("JoinDemo").setMaster("local[*]")
    val sc = new SparkContext(conf)

    val rdd1 = sc.parallelize(List(("spark",1), ("hadoop", 1), ("spark", 2), ("hive", 2),("flink", 2)), 2)

    val rdd2 = sc.parallelize(List(("spark", 3), ("hbase", 3), ("hadoop", 4)), 2)

    //val rdd: RDD[(String, (Option[Int], Int))] = rdd1.rightOuterJoin(rdd2)
    //使用cogroup实现类似rightOuterJoin的功能
    val rdd3: RDD[(String, (Iterable[Int], Iterable[Int]))] = rdd1.cogroup(rdd2)

    val rdd4: RDD[(String, (Option[Int], Int))] = rdd3.flatMapValues(t => {
      if (t._1.isEmpty) {
        t._2.map((None, _))
      } else {
        for (x <- t._1.iterator; y <- t._2.iterator) yield (Some(x), y)
      }
    })
    rdd4.saveAsTextFile("out-rightjoin")

  }

fullOuterJoin

 


    val conf = new SparkConf().setAppName("FullOuterJoinDemo").setMaster("local[*]")
    val sc = new SparkContext(conf)

    val rdd1 = sc.parallelize(List(("spark",1), ("hadoop", 1), ("spark", 2), ("hive", 2),("flink", 2)), 2)

    val rdd2 = sc.parallelize(List(("spark", 3), ("hbase", 3), ("hadoop", 4)), 2)

    //val rdd: RDD[(String, (Option[Int], Option[Int])] = rdd1.fullOuterJoin(rdd2)
    //使用cogroup实现类似rightOuterJoin的功能
    val rdd3: RDD[(String, (Iterable[Int], Iterable[Int]))] = rdd1.cogroup(rdd2)

    val rdd4: RDD[(String, (Option[Int], Option[Int]))] = rdd3.flatMapValues{
      case (i1, Seq()) => i1.iterator.map(x => (Some(x), None))
      case (Seq(), i2) => i2.iterator.map(y => (None, Some(y)))
      case (i1, i2) => for(a <- i1.iterator; b <- i2.iterator) yield (Some(a), Some(b))
    }
    rdd4.saveAsTextFile("out-fulljoin")

 

collect

    val conf = new SparkConf().setAppName("CollectDemo").setMaster("local[*]")
    val sc = new SparkContext(conf)
    val rdd1 = sc.parallelize(List(1,2,3,  4,5,6), 2)

    val rdd2: RDD[Int] = rdd1.map(_ * 10)
    //底层执行数据操作的是在executor里,然后把执行完成的数据返回到Driver端,
    //在调用runjob的时候,数据已经*10完成,
    //runjob是传入rdd,计算的逻辑函数,从第0 个分区一直算到最后一个分区
    


    val res: Array[Int] = rdd2.collect()

 

面试题:    如果collect的数据特别大会出现什么情况,spark只会collect一部分数据,Driver端收集多少数据根Drive内存有关系,占一个百分比,

如果超过这个内存的百分比,那么就会不再收集,

面试:   把设计好的数据是在Driver端写,还是在executor里面写,

在executor里面写,因为,在executor里面写.可以在多个Task,可以并行写,增加处理数据的效率,同时避免通过网络把数据收集到Driver端,如果收集到Driver端,必须要走网络,那么上传和处理的速度会大大减慢,

reduce;  是一个shuffle

底层传入rdd,局部聚合,全局聚合,与fold底层逻辑相似,只不过fold多了一个初始值

 

val  rdd1 = Array((1,2,3),(4,5,6),2)

aggregate: rdd1.aggregate(100)(_+_,_+_)   最终结果为321

初始值在每个分区会使用一边,然后再全局分组聚合的时候也会执行一遍 

 foreach 在执行的时候是在executor里面打印的

foreachPartition()  可以将数据以分区的形式拿出来.

rdd1.foreachPartition(it=>{it.forach(println)}) ------>打印到executor里面了

//对于foreachPartition来说,底层就是传入一个函数,底层有一个函数,这个函数就是迭代器,然后把迭代器作为参数传入到你所应用到的函数中

rdd1.foreachPartition(it=>{

//建立数据库或者从连接池中取出连接

//对迭代器中的多条数据进行操作

对于foreach来说,底层就是当你传入函数时,就是一条调用一次,一条数据就返回iter,然后迭代器里面的数据,每一条都应用一下你 传入的函数,这两个都是Action,因为底层调用了sc,runJob

it.foreach(t=>{

//使用事先创建好的连接,写入到数据库(反复使用连接)

 

})

//关闭连接或将连接换回连接池

)}

 

 

 

### Spark Join 算子的使用方法与示例 在 Spark 中,`join` 是一种常见的操作,用于将两个分布式集合(RDD 或 DataFrame)按照相同的键进行连接。以下是关于 `join` 算子的核心概念、分类以及具体的使用方法。 --- #### 1. **核心概念** - **Join**:将两个数据集中的记录按指定条件关联起来。 - **Key-Based Operation**:基于键的操作,要求两个数据集中存在共同的键字段。 Spark 提供了多种类型的 `join` 操作,包括但不限于内部连接 (`inner join`)、外部连接 (`outer join`) 和左/右半连接 (`left/right semi join`)。 --- #### 2. **RDD 上的 Join** ##### (1)基本语法 对于 RDD 数据结构,`join` 的基本形式如下: ```scala def join[W](other: RDD[(K, W)]): RDD[(K, (V, W))] ``` 其中: - 输入为两个 RDD,分别表示 `(key, value)` 形式的键值对。 - 输出是一个新的 RDD,其值由两个输入 RDD 中对应键的组合构成。 --- ##### (2)代码示例 以下展示了如何在 Scala 中使用 `join` 进行简单连接: ```scala val rdd1 = sc.parallelize(Seq(("a", 1), ("b", 2), ("c", 3))) val rdd2 = sc.parallelize(Seq(("a", 4), ("b", 5))) // 执行 inner join val joinedRDD = rdd1.join(rdd2) joinedRDD.collect().foreach(println) /* 输出结果: (a,(1,4)) (b,(2,5)) */ ``` 此示例中,只有键 `"a"` 和 `"b"` 同时存在于两个 RDD 中,因此它们被保留下来并组成一个新的 RDD[^4]。 --- #### 3. **DataFrame 上的 Join** 相比于 RDD,DataFrame 提供了更高级别的抽象和更简洁的接口来完成各种类型的连接操作。 ##### (1)基本语法 在 DataFrame 中,`join` 方法的一般形式为: ```scala def join(right: Dataset[_], usingColumns: Seq[String], joinType: String): DataFrame ``` 参数说明: - `right`: 表示要连接的右侧表。 - `usingColumns`: 列名数组,指明哪些列作为公共键参与连接。 - `joinType`: 字符串类型,定义连接的具体种类(如 `"inner"`, `"left_outer"`, `"right_outer"`, `"full_outer"` 等)。 --- ##### (2)代码示例 下面展示了一些典型的 DataFrame 加入案例: ```scala import org.apache.spark.sql.SparkSession val spark = SparkSession.builder.appName("Example").getOrCreate() // 创建第一个 DataFrame val dataFrame1 = Seq( ("Alice", 1), ("Bob", 2) ).toDF("name", "id") // 创建第二个 DataFrame val dataFrame2 = Seq( (1, "Engineer"), (2, "Doctor") ).toDF("id", "profession") // Inner Join val innerJoinedDF = dataFrame1.join(dataFrame2, Seq("id"), "inner") innerJoinedDF.show() /* +---+-----+----------+ | id| name| profession| +---+-----+----------+ | 1|Alice| Engineer | | 2| Bob | Doctor | +---+-----+----------+ */ // Left Outer Join val leftOuterJoinDF = dataFrame1.join(dataFrame2, Seq("id"), "left_outer") leftOuterJoinDF.show() /* 假设 dataFrame2 缺少一些匹配项,则显示 null 值 */ // Full Outer Join val fullOuterJoinDF = dataFrame1.join(dataFrame2, Seq("id"), "full_outer") fullOuterJoinDF.show() /* 即使某一方缺少匹配项也会全部展现出来 */ ``` 这些不同的连接方式可以根据实际需求灵活选用[^3]。 --- #### 4. **注意事项** - **Shuffle 开销**: 如果涉及大规模数据集间的连接操作,可能会触发大量的 shuffle,进而影响性能。可以通过调整分区策略或者预处理数据减少不必要的移动成本。 - **内存消耗**: 特别是在执行宽依赖型作业时需要注意资源分配情况以免发生 OOM 错误。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值