【Spark四十四】RDD算子逻辑执行图第四部分

本文详细解析了Apache Spark中sortByKey与cartesian操作的原理与应用,包括其依赖关系、源代码实现及具体示例。通过实例演示,帮助读者掌握如何在Spark环境下对数据进行排序与组合。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.sortByKey

2.cartesian

 

1.sortByKey

1.示例代码

 

 

1.1 RDD依赖关系

(3) ShuffledRDD[3] at sortByKey at SparkRDDSortByKey.scala:15 []
 +-(3) ParallelCollectionRDD[0] at parallelize at SparkRDDSortByKey.scala:14 []

1.2 计算结果

part-00000:

(A,5)
(A,4)
(B,4)
(B,2)

part-00001:

(C,3)
(D,2)

part-00002:

(E,1)
(Z,3)

 

2.RDD依赖图



 

3.sortByKey源代码

 

 /**
   * Sort the RDD by key, so that each partition contains a sorted range of the elements. Calling
   * `collect` or `save` on the resulting RDD will return or output an ordered list of records
   * (in the `save` case, they will be written to multiple `part-X` files in the filesystem, in
   * order of the keys).
   */
  // TODO: this currently doesn't work on P other than Tuple2!
  def sortByKey(ascending: Boolean = true, numPartitions: Int = self.partitions.size)
      : RDD[(K, V)] =
  {
    val part = new RangePartitioner(numPartitions, self, ascending)
    new ShuffledRDD[K, V, V](self, part)
      .setKeyOrdering(if (ascending) ordering else ordering.reverse)
  }

3.1 sortByKey是OrderedRDDFunctions的方法,而不是PairRDDFunctions类的方法。它其中使用了RangePartitioner,因此对于同一个Reducer来说,它得到的结果是有序的,即part-00000中的数据是有序的,part-00001中的数据也是有序的,同时part-00001中的数据较part-00000要么为大(升序),要么为小(降序).

3.2 如果Reducer的个数大于1个,那么这些reducer的排序不是全局有序的?不是这么理解的,在一个节点上的Reducer任务,得到的结果是有序的,但是不同节点上的数据不是全量有序的。

3.3 sortByKey只支持K,V类型的sort,即按照Key进行排序。然后通过隐式转换转换到OrderedRDDFunctions类上。

 

2. cartesian

1. 示例代码

package spark.examples

import org.apache.spark.{SparkContext, SparkConf}

/**
 * Created by yuzhitao on 2/6/2015.
 */
object SparkRDDCartesian {
  def main(args : Array[String]) {
    val conf = new SparkConf().setAppName("SparkRDDCartesian").setMaster("local");
    val sc = new SparkContext(conf);

    //第一个参数是集合,第二个参数是分区数
    val rdd1 = sc.parallelize(List((1,2),(2,3), (3,4),(4,5),(5,6)), 3)
    val rdd2 = sc.parallelize(List((3,6),(2,8)), 2);

    val pairs = rdd1.cartesian(rdd2);

    pairs.saveAsTextFile("file:///D:/cartesian" + System.currentTimeMillis());

    println(pairs.toDebugString)
  }
}

 

 

1.1 RDD依赖关系

(6) CartesianRDD[2] at cartesian at SparkRDDCartesian.scala:18 []
 |  ParallelCollectionRDD[0] at parallelize at SparkRDDCartesian.scala:14 []
 |  ParallelCollectionRDD[1] at parallelize at SparkRDDCartesian.scala:15 []

1.2 计算结果

part-00000: ((1,2),(3,6))

part-00001:((1,2),(2,8))

part-00002:((2,3),(3,6)) ((3,4),(3,6))

part-00003: ((2,3),(2,8)) ((3,4),(2,8))

part-00004: ((4,5),(3,6)) ((5,6),(3,6))

part-00005:((4,5),(2,8)) ((5,6),(2,8))

 

2. RDD依赖图

 

 

 

 

3. cartesian源代码

 

  /**
   * Return the Cartesian product of this RDD and another one, that is, the RDD of all pairs of
   * elements (a, b) where a is in `this` and b is in `other`.
   */
  def cartesian[U: ClassTag](other: RDD[U]): RDD[(T, U)] = new CartesianRDD(sc, this, other)

 

3.1 子RDD的每个partition完全依赖于父RDD的一个partion,又完全依赖于另一个父RDDb的partition,那么这个为什么称为窄依赖???对于窄依赖,NarrowDependency的文档如下。可见,不是说父RDD的partition只能被一个子RDD的partition使用才是窄依赖。一个父RDD的partition可以被多个子RDD依赖

/**
 * :: DeveloperApi ::
 * Base class for dependencies where each partition of the child RDD depends on a small number
 * of partitions of the parent RDD. Narrow dependencies allow for pipelined execution.
 */

3.2 CartesianRDD的源代码:

 

class CartesianRDD[T: ClassTag, U: ClassTag](
    sc: SparkContext,
    var rdd1 : RDD[T],
    var rdd2 : RDD[U])
  extends RDD[Pair[T, U]](sc, Nil)
  with Serializable {

  val numPartitionsInRdd2 = rdd2.partitions.size

  override def getPartitions: Array[Partition] = {
    // create the cross product split
    val array = new Array[Partition](rdd1.partitions.size * rdd2.partitions.size)
    for (s1 <- rdd1.partitions; s2 <- rdd2.partitions) {
      val idx = s1.index * numPartitionsInRdd2 + s2.index
      array(idx) = new CartesianPartition(idx, rdd1, rdd2, s1.index, s2.index)
    }
    array
  }

  override def getPreferredLocations(split: Partition): Seq[String] = {
    val currSplit = split.asInstanceOf[CartesianPartition]
    (rdd1.preferredLocations(currSplit.s1) ++ rdd2.preferredLocations(currSplit.s2)).distinct
  }

  override def compute(split: Partition, context: TaskContext) = {
    val currSplit = split.asInstanceOf[CartesianPartition]
    for (x <- rdd1.iterator(currSplit.s1, context);
         y <- rdd2.iterator(currSplit.s2, context)) yield (x, y)
  }
  ///都是窄依赖
  override def getDependencies: Seq[Dependency[_]] = List(
    new NarrowDependency(rdd1) {
      def getParents(id: Int): Seq[Int] = List(id / numPartitionsInRdd2)
    },
    new NarrowDependency(rdd2) {
      def getParents(id: Int): Seq[Int] = List(id % numPartitionsInRdd2)
    }
  )

  override def clearDependencies() {
    super.clearDependencies()
    rdd1 = null
    rdd2 = null
  }
}

 

再论宽窄依赖:

依赖是针对分区来说的,所以洗牌就是对分区的数据进行重新整理,重新分配等,所以需要重组分区数据之类的操作,在理论上才是一种Shuffle

有shuffle就是宽,没shuffle就是窄

shuffle的含义是数组重组,而有些情况下,数据需要重组,也有可能不需要重组,简单把整个数据传出去
  • 第一种 1:1 的情况被称为 OneToOneDependency。
  • 第二种 N:1 的情况被称为 N:1 NarrowDependency。
  • 第三种 N:N 的情况被称为 N:N NarrowDependency。不属于前两种情况的完全依赖都属于这个类别。
  • 第四种被称为 ShuffleDependency。

 

 

 

 

 

在 IDEA 中实现 Spark 相关任务(包括 RDD 基本算子实验和 WordCount 综合实验),需要先设置好开发环境,再基于具体的业务需求编写代码。下面详细介绍如何实现实验内容: ### 第一部分:掌握 RDD 算子的使用 #### 实现步骤 1. **准备环境** - 创建一个 Maven 工程。 - 修改 `pom.xml` 添加 Spark 依赖: ```xml <dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-core_2.12</artifactId> <version>3.0.1</version> </dependency> ``` 2. **初始化 SparkSession 和 SparkContext** 这是所有 Spark 操作的基础。 ```java import org.apache.spark.SparkConf; import org.apache.spark.api.java.JavaRDD; import org.apache.spark.api.java.JavaSparkContext; public class RddOperatorDemo { private static final String SPARK_MASTER_URL = "local"; public static void main(String[] args) { // 初始化 Spark Context SparkConf conf = new SparkConf().setAppName("RddOperator").setMaster(SPARK_MASTER_URL); JavaSparkContext sc = new JavaSparkContext(conf); rddOperators(sc); // 调用算子练习函数 sc.close(); // 关闭上下文 } /** * 测试各种 RDD 转换算子与行动算子的功能 */ private static void rddOperators(JavaSparkContext sc) { List<String> wordsList = Arrays.asList("hello", "world", "hello spark", "hadoop"); // 生成初始 RDD JavaRDD<String> initialRDD = sc.parallelize(wordsList); // map 示例:将每个单词转成大写形式 System.out.println("--- Map Operator ---"); JavaRDD<String> mappedRDD = initialRDD.map(s -> s.toUpperCase()); mappedRDD.collect().forEach(System.out::println); // distinct 示例:去重操作 System.out.println("\n--- Distinct Operator ---"); JavaRDD<String> distinctRDD = initialRDD.distinct(); distinctRDD.collect().forEach(System.out::println); // flatMap 示例:对字符串按空格拆分出单个词语集合 System.out.println("\n--- FlatMap Operator ---"); JavaRDD<String> flattenWordsRDD = initialRDD.flatMap(s -> Arrays.asList(s.split(" ")).iterator()); flattenWordsRDD.collect().forEach(System.out::println); // filter 示例:过滤掉长度小于等于4的元素 System.out.println("\n--- Filter Operator ---"); JavaRDD<String> filteredRDD = flattenWordsRDD.filter(word -> word.length() > 4); filteredRDD.collect().forEach(System.out::println); // reduceByKey & mapValues 示例:词频统计前处理阶段 System.out.println("\n--- ReduceByKey and MapValues Operators ---"); JavaPairRDD<String, Integer> pairRDD = flattenWordsRDD.mapToPair(word -> new Tuple2<>(word, 1)); JavaPairRDD<String, Integer> reducedRDD = pairRDD.reduceByKey((v1, v2) -> v1 + v2); reducedRDD.collect().forEach(tuple -> System.out.println(tuple._1 + ": " + tuple._2)); // groupByKey 示例:按照 key 分组值列表 System.out.println("\n--- GroupByKey Operator ---"); JavaPairRDD<String, Iterable<Integer>> groupedRDD = pairRDD.groupByKey(); groupedRDD.collect().forEach(entry -> System.out.printf("%s => %s\n", entry._1(), Lists.newArrayList(entry._2()))); // sortByKey 示例:按键排序键值对 System.out.println("\n--- SortByKey Operator ---"); JavaPairRDD<Integer, String> invertedPairsRDD = flattenedWordsRDD.mapToPair(word -> new Tuple2<>(word.hashCode(), word)); invertedPairsRDD.sortByKey(true).collect().forEach(pair -> System.out.println(pair._2())); } } ``` --- ### 第二部分Spark 算子综合实验 —— WordCount (词频统计) #### 实现步骤 1. 同样在上面基础上继续新增一段针对 WordCount 的完整代码逻辑。 ```java public static void runWordCountExample(JavaSparkContext sc){ String inputFilePath = "/path/to/input/text/file"; // 替换为实际输入路径 JavaRDD<String> linesRDD = sc.textFile(inputFilePath); // 加载文本数据作为行级 RDD // 单词分割、映射到(key,value) JavaRDD<String> allWords = linesRDD.flatMap(line -> Arrays.asList(line.toLowerCase().split("\\W+")).iterator()); JavaPairRDD<String,Integer> pairs = allWords.mapToPair(word->new Tuple2<>(word,1)); // 计算每种词汇的数量总计 JavaPairRDD<String,Integer> counts = pairs.reduceByKey(Integer::sum); // 输出最终结果集 counts.foreach(tup->{System.out.println(tup._1()+" : "+tup._2());}); } ``` 2. 最后记得修改主方法调用新加入的任务入口: ``` public static void main(String[] args) { ... runWordCountExample(sc); ...} ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值