Spark复习 Day01:SparkCore(一)

Spark复习 Day01

1. Driver 驱动器
---------------------------------------------------
    - 执行开发程序的Main方法的进程
    - 创建SparkContext、RDD、转换和行动
    - 主要职责:
        1. 将用户代码转化成job
        2. 跟踪Executor的运行状况
        3. 为执行器节点调度任务
        4. UI展示运行状况


2. Executor 执行器
---------------------------------------------------
    - 运行Task
    - 主要职责:
        1. 负责运行Spark的Task, 并将结果返回给Driver
        2. 通过自身的BlockManager,缓存RDD


3. Spark On Yarn
---------------------------------------------------
    - Spark 客户端能够直接连接Yarn集群
    - 有yarn-client 和 yarn-cluster 两种模式
        1. yarn-client: Driver运行在Client上,适用于调试
        2. yarn-cluster: Driver运行在RM启动的AM上,适用于生产
    - 流程:
        1. SparkClient --> spark-submit --> ResourceManager
        2. ResourceManager 开始进行资源调度与分配, 查找一个NM, 去开启并监控AppMaster
        3. AM 与各个NodeManager 建立联系
        4. NodeManager 管理每个自身节点上的资源, 并向AM汇报
        5. AM向RM 请求执行程序的资源, RM给AM分配资源, AM 联系分配给自己资源的NM
        6. NM开启一个资源容器container,里面包含CPU,内存等
        7. Container 里面创建Spark Executor, 并反向注册到AM
        8. AM 开始进行任务的调度与分配, 给Executor分配任务Task


4. RDD 弹性分布式数据集
---------------------------------------------------
    - 什么是RDD?
        1. 弹性分布式数据集,Spark最基本的数据抽象
        2. 是一个不可变,可分区,里面的元素可并行计算的数据集合
        3. 将数据处理的逻辑,进行封装

    - RDD的属性
        1. 一组分区,数据集的基本组成单位
        2. 一个计算每个分区的函数, 算子中携带的计算逻辑
        3. RDD之间的依赖关系
        4. 一个Partitioner分区器, RDD的分区函数
        5. 一个列表,存储每个Partition的优先位置

    - RDD的特点
        1. RDD 的数据集是只读的
        2. 只能通过RDD转换操作,形成新的RDD
        3. RDD之间存在依赖
        4. RDD的执行是按照依赖关系,前后计算的
        5. 如果RDD依赖关系很长,可以通过持久化来切断依赖

    - RDD分区函数
        1. 将RDD的数据,通过分区函数,进行分区
        2. 如果RDD是直接读取系统数据,那么分区函数就是读取系统数据,然后分区
        如果是其他RDD转换而来的,那么分区函数,就执行本RDD带的计算逻辑,进行转换操作

    - RDD的宽依赖和窄依赖
        1. 窄依赖:父子RDD之间是一一对应的,map,filter等,没有进行shuffle
        2. 宽依赖:父子RDD之间是多对多的关系,下游RDD的一个分区,依赖上游RDD的所有分区。 比如reduce,keyby等

    - RDD的缓存
        1. 应用多次使用同一个RDD,可以将这个RDD缓存到内存中
        2. 这样,只有第一次使用RDD的时候需要上下游的计算
        3. 第二次,第三次等再次使用时,就不需要计算了,可以直接使用

    - CheckPoint
        1. RDD是可以容错的,应为有血缘关系,你可以通过上下游RDD去计算,从而恢复某个RDD的数据
        2. 但是,如果RDD血缘很复杂,长迭代,重建RDD很耗费性能
        3. 为此,可以进行RDD的持久化,将RDD持久化到存储中
        4. 一旦RDD出现了错误,不必再次上下游恢复,直接CheckPoint中读取即可


5. RDD的创建
---------------------------------------------------
    - 从集合中创建RDD
        1. sc.parallelize(seq)
        2. sc.makeRDD(seq)

    - 从外部存储中创建RDD
        1. sc.textFile("path")
            - val path1 = "hdfs://xxx/xx/xx"
            - val path2 = "file:///d:/Test/1.txt"
            - val path3 = "d:/Test/1.txt"

    - 从其他RDD创建
        val rdd2 = rdd.map((_,1))


6. 单Value RDD算子
---------------------------------------------------
    - map(func) 算子:
        1. 返回一个新的RDD,新RDD的每个元素由原来的元素经过FUNC函数转换而成
        2. 例:[1,2,3,4,5] -- 返回[6,7,8,9,10]
            var rdd = sc.makeRDD(List(1,2,3,4,5))
            // 1.map
            rdd = rdd.map(_+5)
            rdd.collect().foreach(println(_))

    - mapPartitions(func) 算子:
        1. 类似于map, 但是针对的是每一个分区。对每个分区应用func函数
        2. 假设N个元素,M个分区,那么map将对每一个元素执行一遍函数,共执行N次
        mapPartitions对每个分区执行一次,共执行M次
        3. rdd = rdd.mapPartitions(x => x.map(_*2))
        4. 与map功能一样,但照比map,能够减少网络io,性能更优。但是,由于一个分区整体进入一个Excutor
        可能导致OOM
        5. 例:rdd = rdd.mapPartitions(x => x.map(_*2))

    - mapPartitionsWithIndex(func) 算子:
        1. 但是针对的是每一个分区。对每个分区应用func函数,并且跟踪原始RDD的分区索引
        2. 例:
            val rdd1 = rdd.mapPartitionsWithIndex({
              case (index,datas) => {
                println("分区index:", index)
                datas
              }
            })

    - flatMap(func) 算子:
        1. 对每一个输入元素,经过一个func函数,返回一个或者多个元素
        2. func的返回值是一个序列,而不是一个元素
        3. 典型 1 变 多。 比如句子分词
        4. 例:
            val rdd = sc.textFile(path = path2)
            val rdd2 = rdd.flatMap(x => x.split("\\s+"))

    - glom() 算子:
        1. 将每一个分区形成一个数组,形成新的RDD[Array[T]], 多变1
        2. 例:
            var rdd = sc.makeRDD(List(1,2,3,4,5),2)
            val rdd2: RDD[Array[Int]] = rdd.glom()

    - groupby(func) 算子:
        1. 按照传入的函数的返回值作为key进行分组,将相同key对应的值,放入同一个迭代器
        2. 返回值为一个kv元组, key为分组的key, v 为分组的数据集合
        3. 例:
            val rdd2: RDD[(Int, Iterable[Int])] = rdd.groupBy(x => x % 2)

    - filter(func) 算子:
        1. 过滤, 返回一个新的RDD,此RDD由 经过func过滤函数处理,返回值为True的输入元素组成
        2. 例:
            var rdd = sc.makeRDD(List(1,2,3,4,5),2)
            val rdd2: RDD[Int] = rdd.filter(x => x % 2 == 0)

    - sample(withReplacement, fraction, seed):RDD[T] 算子:
        1. 以指定的随机种子seed, 随机抽样数量为fraction的出数据样本
        2. 参数说明:
            - withReplacement:元素可以多次抽样(在抽样时替换)
            - fraction:期望样本的大小作为RDD大小的一部分,
                当withReplacement=false时:选择每个元素的概率;分数一定是[0,1] ;
                当withReplacement=true时:选择每个元素的期望次数; 分数必须大于等于0。
            - seed:随机数生成器的种子。建议第三个参数seed可以默认,不好把控
        3. 例:
            - 元素不可以多次抽样:withReplacement=false,每个元素被抽取到的概率为0.5:fraction=0.5
                JavaRDD<Integer> rdd = sc.parallelize(Arrays.asList(6,2,8,4));
                JavaRDD<Integer> res = rdd.sample(false, 0.5);
                res.foreach(x -> System.out.print(x +" "));
                //结果:2 8 4
            - 元素可以多次抽样:withReplacement=true,每个元素被抽取到的期望次数为2:fraction=2
                JavaRDD<Integer> rdd = sc.parallelize(Arrays.asList(6,2,8,4));
                JavaRDD<Integer> res = rdd.sample(true, 2);
                res.foreach(x -> System.out.print(x +" "));
                //结果:6 2 8 8 8 8 8 4
        4. 返回一个RDD[T]

    - takeSample(withReplacement: Boolean,
                     num: Int,
                     seed: Long = Utils.random.nextLong):Array[T]
        1. 该方法仅在预期结果数组很小的情况下使用,因为所有数据都被加载到driver的内存中
        2. 参数说明:
            - withReplacement:元素可以多次抽样(在抽样时替换)
            - num:返回的样本的大小
            - seed:随机数生成器的种子。建议第三个参数seed可以默认,不好把控
        3. 例:
            - 当不可以多次抽样:withReplacement=false;样本个数num大于父本个数时,只能返回父本个数
                JavaRDD<Integer> rdd = sc.parallelize(Arrays.asList(6, 2, 8, 4));
                List<Integer> res = rdd.takeSample(false, 8);
                System.out.println(res);
                //结果:[6, 8, 2, 4]
            - 当不可以多次抽样:withReplacement=false;样本个数num小于父本个数时,返回样本个数
                  JavaRDD<Integer> rdd = sc.parallelize(Arrays.asList(6, 2, 8, 4));
                  List<Integer> res = rdd.takeSample(false, 3);
                  System.out.println(res);
                  //结果:[8, 4, 2]
            - 当可以多次抽样:withReplacement=true;样本个数num大于父本个数时,返回样本个数
                  JavaRDD<Integer> rdd = sc.parallelize(Arrays.asList(6, 2, 8, 4));
                  List<Integer> res = rdd.takeSample(true, 8);
                  System.out.println(res);
                  //结果:[4, 6, 8, 2, 6, 2, 4, 2]
            - 当不可以多次抽样:withReplacement=true;样本个数num小于父本个数时,返回样本个数
                  JavaRDD<Integer> rdd = sc.parallelize(Arrays.asList(6, 2, 8, 4));
                  List<Integer> res = rdd.takeSample(true, 3);
                  System.out.println(res);
                  //结果:[8, 8, 2]
        4. 返回一个Array[T]


7.单Value RDD算子 - Shuffle算子
---------------------------------------------------
    - distinct(numPartitions: Int):RDD[T]
        1. 去重:对RDD进行去重,返回一个新的分区数为numPartitions的RDD
        2. numPartitions 可选参数,默认为8个。新RDD的分区数
        3. 例:
            val rdd1 = sc.parallelize(List(1,1,1,1,2,3,4,5,5,5,5))
            val rdd2 = rdd1.distinct(numPartitions = 2)
            // 返回一个新的RDD 4,1,2,3,5
        4. 注:其中涉及到了shuffle操作[消耗性能]

    - coalesce(numPartitions: Int, shuffle: Boolean = false,
                 partitionCoalescer: Option[PartitionCoalescer] = Option.empty)
                (implicit ord: Ordering[T] = null)
            : RDD[T]
        1. 缩减RDD的分区数。用于大数据集的过滤后,提高小数据集的运行效率
        2. 例:将4个分区变为2个分区
            val rdd1 = sc.parallelize(1 to 16,4)
            val rdd2 = rdd1.coalesce(2)
        3. 如果没有shuffle,那么会挪动和合并原RDD分区的数据,不洗牌
           如果是shuffle, 那么会将数据打散,然后重新分配到新的RDD的分区

    - repartition(numPartitions: Int): RDD[T]
        1. 重分区:根据指定的分区数,从新通过网络随机shuffle所有数据,平均分配到指定分区数的分区上
        2. 例:
            val rdd1 = sc.parallelize(1 to 16,4)
            val rdd2 = rdd1.repartition(3)
            val rdd3 = rdd2.mapPartitionsWithIndex((index,it) => {
              it.map(_.toString() + "_" + index)
            })
        3.涉及到了shuffle
        4.repartition 与 coalesce 的区别
            - repartition = coalesce(shuffle = true)

    - sortBy[K](
            f: (T) => K,
            ascending: Boolean = true,
            numPartitions: Int = this.partitions.length): RDD[T]
        1. 作用:
            - 使用f: (T) => K, 对数据进行处理,得出一个key, 然后按照key进行排序
            - 默认为正序
        2. 参数说明:
            - f: (T) => K:将RDD的数据转换成一个key值
            - ascending: 是否升序。默认true
            - numPartitions: 返回的RDD的分区数
        3. 返回值
            一个新的RDD[T]


8. 双Value RDD 算子
--------------------------------------------------------
    - union(other: RDD[T]): RDD[T]
        1. 作用:对源RDD和参数RDD求并集,然后返回一个新的RDD
        2. 默认是保留两个RDD的所有元素。不去重。如果去重,可以考虑使用distinct
            val rdd1 = sc.parallelize(List(1,2,3,4,5,6),4)
            val rdd2 = sc.parallelize(List(6,7,8,9))
            val rdd3 = rdd1.union(rdd2)
        3. 返回值:返回一个新的RDD
        4. 例:
            val rdd1 = sc.parallelize(List(1,2,3,4,5,6),4)
            val rdd2 = sc.parallelize(List(6,7,8,9))
            val rdd3 = rdd1.union(rdd2)
            rdd3.collect().foreach(x => print(x.toString + ','))
            // 结果: 1,2,3,4,5,6,6,7,8,9

    - subtract(
            other: RDD[T],
            p: Partitioner): RDD[T]
        1. 作用: 求两个RDD的差集。返回一个新的RDD,新的RDD中的元素,在原RDD里,但是不在otherRDD里
        2. 例:
            val rdd1 = sc.parallelize(List(1,2,3,4,5,6),4)
            val rdd2 = sc.parallelize(List(6,7,8,9))
            val rdd3 = rdd1.subtract(rdd2)
            rdd3.collect().foreach(x => print(x.toString + ','))
            // 结果:4,1,5,2,3

    - intersection(other: RDD[T]): RDD[T]
        1. 求两个RDD的交集。
        2. 返回一个新的RDD,注,此RDD是已经去重了的
        3. 例:
            val rdd1 = sc.parallelize(List(1,2,2,2,2,3,4,5,6,6,6,7,7),4)
            val rdd2 = sc.parallelize(List(6,7,8,9))
            val rdd3 = rdd1.intersection(rdd2)
            rdd3.collect().foreach(x => print(x.toString + ','))
            // 结果:6,7

    - cartesian[U: ClassTag](other: RDD[U]): RDD[(T, U)]
        1. 笛卡尔积,尽量避免使用
        2. 例:
            val rdd1 = sc.parallelize(List(1,2,3),4)
            val rdd2 = sc.parallelize(List(6,7))
            val rdd3 = rdd1.cartesian(rdd2)
            rdd3.collect().foreach(x => print(x.toString + ','))
            // 结果:(1,6),(1,7),(2,6),(2,7),(3,6),(3,7)

    - zip[U: ClassTag](other: RDD[U]): RDD[(T, U)]
        1. 将两个RDD组合成k-v形式,形成一个新的RDD[(T,U)]
        2. 注意: 两个RDD的分区数,以及元素数量都要相同,否则会抛出异常
        3. 例:
            val rdd1 = sc.parallelize(List(1,2),4)
            val rdd2 = sc.parallelize(List(6,7),4)
            val rdd3 = rdd1.zip(rdd2)
            rdd3.collect().foreach(x => print(x.toString + ','))
            // (1,6),(2,7)


9.Key-Value RDD 算子
-------------------------------------------------
    - partitionBy(partitioner: Partitioner): RDD[(K, V)]
        1. 作用:对pairRDD 进行分区操作
        2. 如果原分区和现有分区数一致,就不进行分区,不一致就重新分区
        3. 重新分区会产生shuffle过程
        4. 分区器Partitioner:HashPartitioner, RangePartitioner
        5. 例:
            val rdd1 = sc.parallelize(List(1,2,3,4,5),4)
            val rdd2 = sc.parallelize(List(6,7,8,9,0),4)
            val rdd3 = rdd1.zip(rdd2).partitionBy(new HashPartitioner(2))
            rdd3.glom().foreach(x => println(x.mkString(",")))
            // 结果: (2,7),(4,9)
                      (1,6),(3,8),(5,0)

    - groupByKey(): RDD[(K, Iterable[V])]
        1. 作用:分组,对每个key进行操作,将key相同的values 聚合到一个seq中
        2. 例:
            val rdd1 = sc.parallelize(List(1,1,3,4,5),4)
            val rdd2 = sc.parallelize(List(6,7,8,9,0),4)
            val rdd3: RDD[(Int, Iterable[Int])] = rdd1.zip(rdd2).groupByKey()
            val rdd4 = rdd3.map(x => {(x._1,x._2.toList.sum)})
            rdd4.collect().foreach(print)
            // 结果:(4,9)(1,13)(5,0)(3,8)

    - reduceByKey(func: (V, V) => V, numPartitions: Int): RDD[(K, V)]
        1. 在kv格式的RDD上调用,返回一个kv格式新的RDD
        2. 将相同key的value, 聚合到一起,形成values, 然后对values所有元素
        应用 reduce函数 func, 从而形成一个新的newValue。 values ==[reduce func]==> newValue
        继而返回一个新的RDD[key,newValue]
        3. 可以指定新生成RDD的numPartitions
        4. 例:
            val rdd1 = sc.parallelize(List(1,1,3,4,5),4)
            val rdd2 = sc.parallelize(List(6,7,8,9,0),4)
            val rdd4 = rdd1.zip(rdd2).reduceByKey(_ + _)
            rdd4.collect().foreach(print)
            // 结果:(4,9)(1,13)(5,0)(3,8)
        5. reduceByKey 和 groupByKey的区别
            - reduceByKey: 先按照key对RDD数据进行分组,然后组内预聚合,然后再去shuffle.
            - groupByKey: 按照key进行分组,然后直接进行shuffle
            - reduceByKey 比 groupByKey 多了一个预聚合,减少了网络IO,业务首选。但是注意别影响了业务逻辑

    - aggregateByKey[U: ClassTag](zeroValue: U, numPartitions: Int)(seqOp: (U, V) => U,
            combOp: (U, U) => U): RDD[(K, U)]
        1. 这个函数操作的是分区内和分区间
        2. zeroValue: 零值,即为初始值。 给分区内的每一个key一个初始值u0
        3. seqOp 分区内聚合函数,参数为原RDDkey对用的value v0和初始零值u0, 返回一个U类型的新值u1。seqOp(v0,u0) => u1。
        即 (RDD[k,v0], u0) ==[seqOp]==> RDD[k,u1]
        4. combOp 分区间聚合函数,参数为分区内聚合结果k,u1。
        即 RDD[k,u1] ==[combOp]==> RDD[(k, u2)]
        5. 过程为:
            - 对于kv的RDD,对key相同的数据进行分组
            - 然后在分组内,对每个key的value和设定的初始值u0进行迭代的seqOp操作,得到新的value u1
            - 从而得到新的RDD[k,u1]
            - 然后对新RDD的各个分区,对相同的key, 进行分区间聚合comOp操作
            - 最后得到一个最终的RDD[(k, u2)]
        6. 例:
            // 对pairRDD 取出每个分区相同key的最大值,然后分区间相加
            val rdd1 = sc.parallelize(List("a","a","c","b","c","c"),2)
            val rdd2 = sc.parallelize(List(3,2,4,3,6,8),2)
            rdd1.zip(rdd2).glom().foreach(x => println("分区数据:" + x.mkString(",")))
            val rdd4 = rdd1.zip(rdd2).aggregateByKey(zeroValue = 0)(seqOp = (x,y)=>math.max(x,y), combOp = (x,y)=>x + y)
            rdd4.collect().foreach(print)
            // 结果:分区数据: (a,3),(a,2),(c,4)
                    分区数据:(b,3),(c,6),(c,8)
                    最终结果:(b,3)(a,3)(c,12)
            // 分区内: (a,3),(a,2),(c,4) =max=> (a,3),(c,4)
            //          (b,3),(c,6),(c,8) =max=> (b,3),(c,8)
            // 分区间:(a,3),(c,4) || (b,3),(c,8) =(_ + _)=> (a,3),(b,3),(c,8)

    - foldByKey(zeroValue: V, numPartitions: Int)(func: (V, V) => V): RDD[(K, V)]
        1. aggregateByKey的简化操作,seqOp == comOp
        2. 例:
            // 对pairRDD 取出每个分区相同key的值相加,然后分区间相加
            val rdd1 = sc.parallelize(List("a","a","c","b","c","c"),2)
            val rdd2 = sc.parallelize(List(3,2,4,3,6,8),2)
            rdd1.zip(rdd2).glom().foreach(x => println("分区数据:" + x.mkString(",")))
            val rdd4 = rdd1.zip(rdd2).foldByKey(0)(_ + _)
            rdd4.collect().foreach(print)
            // 结果:分区数据: (a,3),(a,2),(c,4)
                  分区数据:(b,3),(c,6),(c,8)
                  最终结果:(b,3)(a,5)(c,18)

    - combineByKey[C](
            createCombiner: V => C,
            mergeValue: (C, V) => C,
            mergeCombiners: (C, C) => C
            ): RDD[(K, C)]
        1. 针对相同的key,将value 合并成一个集合
        2. 照比 aggregateByKey, 多了一个createCombiner
        3. 参数函数需要手动指定类型,因为没有运行时推断标签[U:ClassTag]
        4. 参数说明:
            - createCombiner:
                - 创建累加器[聚合器]的初始值,其实就是对k,v ==> k,C, v => C, 将RDD的value改变形状[类型],以适用于后面的计算
                - aggregateByKey的初始值是指定的,第一次指定什么就是什么,不能改变,也不能通过与v 计算得出,有局限性
                - createCombiner对分区内的每种key只调用一次[仅第一次出现调用],得出key对应的初始值C0
            - mergeValue:分区内函数。对相同key的数据进行分组,在分组内,进行mergeValueOp操作,参数为初始值U0和原始value,得到新的value newValue[C]表示
            - mergeCombiners: 分区间函数。 对分区间相同key的newValue[C] 进行mergeCombinersOp操作,得到新的newValue[C]
            - 最后返回一个新的RDD RDD[(K, C)]
        5. 例:求一个RDD[k,v],每个key的平均值[sum(v) / n]
            // 对pairRDD 取出每个分区相同key的最大值,然后分区间相加
            val rdd1 = sc.parallelize(List("a","a","c","b","c","c"),2)
            val rdd2 = sc.parallelize(List(3,2,4,3,6,8),2)
            rdd1.zip(rdd2).glom().foreach(x => println("分区数据:" + x.mkString(",")))
            // 分区数据: (a,3),(a,2),(c,4)
            // 分区数据:(b,3),(c,6),(c,8)
            val rdd4 = rdd1.zip(rdd2).combineByKey(
              createCombiner = x => (x,1),
              mergeValue = (acc:(Int,Int),v) => (acc._1 + v, acc._2 + 1),
              mergeCombiners = (x:(Int,Int),y:(Int,Int)) => ((x._1 + y._1),(x._2 + y._2))
            )
            // rdd4
            // (b,(3,1))
            //(a,(5,2))
            //(c,(18,3))
            val rdd5 = rdd4.map(x => (x._1, x._2._1 / x._2._2))
            rdd4.collect().foreach(println)
            rdd5.collect().foreach(println)
            // 结果:
            // (b,3)
            // (a,2)
            // (c,6)

    - sortByKey(ascending: Boolean = true, numPartitions: Int = self.partitions.length)
            : RDD[(K, V)]
        1. 在一个k,v的RDD上调用。
        2. 返回一个按照key的正序[倒序] 排序的新的RDD
        3. 要求 K必须实现 Ordered接口
        4. 例:
            val rdd1 = sc.parallelize(List("a","a","c","b","c","c"),2)
            val rdd2 = sc.parallelize(List(3,2,4,3,6,8),2)
            rdd2.zip(rdd1).glom().foreach(x => println("分区数据:" + x.mkString(",")))
            // 分区数据: (3,a),(2,a),(4,c)
            // 分区数据:(3,b),(6,c),(8,c)
            val rdd4 = rdd2.zip(rdd1).sortByKey()
            rdd4.collect().foreach(println)
            //结果: (2,a)
            //(3,a)
            //(3,b)
            //(4,c)
            //(6,c)
            //(8,c)

    - mapValues[U](f: V => U): RDD[(K, U)]
        1. 遍历kvRDD的每个key,对其value应用func,得到新的value
        2. 返回新的kvRDD
        3. 例:
            val rdd1 = sc.parallelize(List("a","a","c","b","c","c"),2)
            val rdd2 = sc.parallelize(List(3,2,4,3,6,8),2)
            rdd2.zip(rdd1).glom().foreach(x => println("分区数据:" + x.mkString(",")))
            // 分区数据: (3,a),(2,a),(4,c)
            // 分区数据:(3,b),(6,c),(8,c)
            val rdd4 = rdd2.zip(rdd1).mapValues(_ * 2)
            rdd4.collect().foreach(println)
            //结果:(3,aa)
            //(2,aa)
            //(4,cc)
            //(3,bb)
            //(6,cc)
            //(8,cc)

    - join[W](other: RDD[(K, W)]): RDD[(K, (V, W))]
        1. 在类型为K,V 和 K,W的RDD上调用,返回一个相同key的所有元素
        2. a RDD, b RDD, a的每个元素(k,v) 去b中查找相同key的元素,进行join, 有就join.然后切换下一个元素
        2. 例:
            val rdd1 = sc.parallelize(List((1,"a"),(1,"aa"),(2,"bb")),3)
            val rdd2 = sc.parallelize(List((1,"c"),(1,"cc"),(2,"d")),2)
            val rdd5 = rdd1.join(rdd2)
            rdd5.collect().foreach(println)
            //结果:(1,(a,c))
            //(1,(a,cc))
            //(1,(aa,c))
            //(1,(aa,cc))
            //(2,(bb,d))

    - cogroup[W1, W2, W3](other1: RDD[(K, W1)], other2: RDD[(K, W2)], other3: RDD[(K, W3)])
            : RDD[(K, (Iterable[V], Iterable[W1], Iterable[W2], Iterable[W3]))]
        - 对于rdd1,other1,other2,other3们
        - rdd内部根据key进行分组,将values, 形成Iterator[values]
        - rdd之间,根据相同的key进行分组,形成Tuple[Iterator[values],Iterator[values]...]
        - 注:不是每个RDD全都有的key,也会参与运算
        - 例:
            val rdd1 = sc.parallelize(List((1,"a"),(1,"aa"),(2,"bb")),3)
            val rdd2 = sc.parallelize(List((1,"c"),(1,"cc"),(2,"d")),2)
            val rdd5 = rdd1.cogroup(rdd2,rdd2,rdd2)
            rdd5.collect().foreach(println)
            //结果:(1,(CompactBuffer(a, aa),CompactBuffer(c, cc),CompactBuffer(c, cc),CompactBuffer(c, cc)))
            //(2,(CompactBuffer(bb),CompactBuffer(d),CompactBuffer(d),CompactBuffer(d)))

10. RDD行动算
------------------------------------------------------------
    - reduce(f: (T, T) => T): T
        1. 通过func函数,聚集RDD内的所有元素。先聚合分区内的数据,再聚合分区间的数据
        2. 例:
            var rdd = sc.makeRDD(List(1,2,3,4,5),2)
            val res = rdd.reduce(_ + _)
            println(res)
            // 结果15

    - collect(): Array[T]
        1. 以数组的形式返回RDD的所有元素
        2. 例:
            var rdd = sc.makeRDD(List(1,2,3,4,5),2)
            val ints: Array[Int] = rdd.collect()

    - count(): Long
        1. 返回RDD的元素的个数
        2. 例:
            var rdd = sc.makeRDD(List(1,2,3,4,5),2)
            val l: Long = rdd.count()

    - first():T
        1. 取出RDD的第一个元素

    - take(n):Array[T]
        1. 返回一个由RDD的前N个元素组成的数组

    - takeOrdered(n):Array[T]
        1. 返回该RDD排序后的前n个元素组成的数组,默认升序

    - aggregate[U: ClassTag](zeroValue: U)(seqOp: (U, T) => U, combOp: (U, U) => U): U
        1. 直接对RDD中的元素进行aggregate操作
        2. 原理参照aggregateByKey操作
        3. 唯一的区别: aggregateByKey 分区间的操作是不会带上初始值的,只有每个分区内部会带上初始值
                       aggregate 不管是分区内还是分区间,计算都会带上这个初始值

    - fold(zeroValue: T)(op: (T, T) => T): T
        1. 原理参照foldByKey操作
        2. 相当于 分区内,分区间操作一样的aggregate

    - saveAsTextFile(path)
        1. 将数据以textFile的形式保存到本地或者hdfs上
        2. 保存时,会调用toString方法

    - saveAsSequenceFile(path)
        1. 将数据以Hadoop SequenceFile的形式保存到hdfs上(或者支持hadoop的系统上)

    - saveAsObjectFile(path)
        1. 将RDD中的元素序列化成对象,保存到文件中

    - countByKey()
        1. 针对kv类型的RDD,返回一个k,int类型的map
        2. 统计每一个key的个数count

    - foreach(func)
        1. 遍历数据集上的每一个元素,并对其应用func


11. RDD分区与Executor的关系、任务划分、以及任务调度、并行度
---------------------------------------------------------------------------------
    - RDD的一个分区,对应一个任务,这个任务只会进入到一个Executor中去执行
    - 并行度 - 其实就是RDD分区的大小,Task数量的代销
        1. 读取hadoop文件形成的RDD的并行度
            - Task的数量与InputSplit一一对应,InputSplit是由若干个Block组成的
            并且读取文件时,可以参数指定。取Max(InputSplit, Args)
            - Map操作不改变分区数,也就是不改变Task,和并行度
            - Reduce操作并行度依赖顺序
                方法第二个参数指定 > spark.default.param > 所有RDD族谱中分区最多的那个
    - 文件块Block
        1. 输入可能以多个文件的形式存储在HDFS上,每个File都包含了很多块,称为Block
    - 输入切片InputSplit
        1. 当Spark读取这些文件作为输入时,会根据具体数据格式对应的InputFormat进行解析,一般是将若干个Block合并成一个输入分片,称为InputSplit,注意InputSplit不能跨越文件
    - 任务Task
        1. 这些切分好的分片与Task一一对应
        2. 每个Task都会被分配到集群某个节点的某个Executor上去执行
    - 节点以及Executor
        1. 每个节点可以有一个或者多个Executor
        2. 每个Executor由若干个core组成
        3. 每个Executor的每个core一次只能执行一个Task
        4. 每个Task的执行结果,就生成了目标RDD的一个partition, 即InputSplits = Tasks=Partitions
    - RDD的并行度
        1. 在RDD一个算子的计算中,RDD同时被几个Executor的几个core执行
        2. 计算方式为:
            一个Partition对应 一个Task 对应一个Executor 对应多个cores
            PartitionsNums * ExecutorNums * Cores

12. 案例分析
-------------------------------------------------
    - 数据结构:
        时间戳         省份      城市      用户      广告点击次数   中间字段使用空格分隔
     1516609143867      7        9         64       12
     1516609143877      3        1         12       88
     1516609143887      3        5         24       25
     1516609143889      3        5         24       38
     1516609143897      1        3         44       48
     1516609143967      1        2         15       77

    - 统计每一个省份的点击次数最高的城市
      /**
        * 统计每个省份的广告点击次数Top2的城市以及其点击次数
        */
      def accLog() ={
        val conf = new SparkConf().setMaster("local").setAppName("accLog")
        val sc = new SparkContext(conf)
        val file: RDD[String] = sc.textFile("D:\\Test\\acc.log")
        // 首先分割数据,拆出有用的列,形成tuple
        file.map(line => {
          val array = line.split("\\s+")
          ((array(1),array(2)),Integer.parseInt(array(4)))
        })
          // 按照城市进行聚合,得出每个城市的广告点击率
          .reduceByKey(_ + _)
          // 按照点击率降序排列
          .sortByKey()
          // 改变tuple的形状以便于按照省份进行reduce操作
          .map(x => (x._1._1, (x._1._2, x._2)))
          // 对每个省份,查找点击数的topN
          .groupBy(_._1)
          .mapValues(x => {
            if(x.size >= 2){
              List(x.toList(0)._2,x.toList(1)._2)
            }else{
              x.map(_._2).toList
            }
          })
          .collect().foreach(println)
      }
      // 返回结果:
      // (7,List((9,12)))
      // (3,List((1,88), (5,63)))
      // (1,List((2,77), (3,48)))














  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值