SparkCore笔记总结


1. IO流中的BufferedInputStream体现出装饰者设计模式。
    1)装饰者设计模式由装饰者和被装饰者两部分组成;
    2)装饰者扩增被装饰者的功能,BufferedInputStream(装)增加了FileInputStream(被装)的功能,
    由一个字节一个字节的读取变成一次读取一批数据;
    3)装饰者与被装饰者有相同的方法,这样装饰者使用方法时,就感觉使用被装饰者的方法一样,只不过对功能进行了扩增。
    比如如果是文本,还可以进一步包装,对字节流(FileInpuStream)包装成InputStreamReader(一次读一行),再包装成缓冲流。
2. 在RDD中也体现了装饰者设计模式,RDD是一个抽象类,RDD的方法才叫算子(sc.textFile不叫算子,他不是RDD的方法)。
3. IO流BufferedInputStream只有在调用read方式才真正执行,RDD也是在调用collect方法时才真正执行,都是体现了装饰者设计模式,懒加载。
4. RDD算子相关的代码在Executor执行,RDD算子外的代码在Driver执行。
5. 从HDFS上读取数据,如果放在集合中,那么会实实在在存放在内存中,但是RDD不是真正的把数据拿过来,而存放着业务逻辑。
6.  执行流程:对于sc.textFile("路径").flatmap(_split(" ")).map((_,1)).reduceByKey(_+_).collect
    1)客户端通过sparkSubmit提交jar给RM,发送一个请求。
    2)RM对请求进行处理,将客户端提交的jar包做一个封装,封装成AppMaster。
    3)AppMaster需要在NM上创建Container运行,AppMaster启动一个Driver线程,处理客户端的请求,协调Executor资源;
    4)Executor创建好后,需要在Driver上反向注册,Driver就知道有多少个Executor为他服务;
    5)RDD算子相关的代码在Executor执行,RDD算子外的代码在Driver执行。RDD是抽象的业务逻辑。
    6)对于sc.textFile是在Driver上执行,首先会new HadoopRDD()出来,RDD叫弹性分布式数据集。比如读取时有大量的数据,
       不可能都交给一个Executor去处理,会经过一定的计算规则,将数据分区,不同的分区分为不同Executor。
    7)RDD不一定在一个节点上,他可以跨多个节点,每个Executor处理不同分区的内容,这样就有多个节点并行执行,所以叫分布式;
    8)flatmap(_split(" ")),执行flatmap时,会创建一个新的RDD,new MapPartitionsRDD(this,..)
    9)reduceByKey需要等所有分区的数据计算完毕才能执行,比如有200个分区,有199个执行完毕,reduceByKey做汇总操作,
    不能一直等待,所以需要落盘,避免内存溢出。落盘就是Shuffle过程。
    9)Shuffle落盘可以理解为写的过程,reduceByKey从磁盘读取数据可以理解为读的过程。
    10)collect触发行动执行,是行动算子。行动算子调用之前的算子并未真正执行,只是封装业务逻辑。相当于装饰者不断装饰被装饰者,
      (new ...(new MapPartitionsRDD(new HadoopRDD(),..)..),只能真正调用collect才执行,与BufferedInputStream调用read一样。
    11)spark是基于内存的计算,适合迭代式计算,中间结果不落盘,但是RDD处理过程中有Shuffle还是会落盘的。
        MR计算时也是在内存中,但是不适合迭代式计算,因为中间结果落盘。
    12)一般算子做统计时需要落盘,需要等待其他分区的数据,将数据打乱重组,像..By..。
    13)不同的分区放在不同的Executor上执行。
    
7. RDD特性:RDD抽象类中定义的5个抽象方法
    1)一组分区:RDD的基本组成单位,通过getPartitions()获取分区
    2)计算每个分区的函数:compute()计算数据流向哪个分区,计算单值,分区计算函数。
    3) RDD之间的依赖关系:得到一个分区可能需要多个RDD,将多个RDD之间的关系成为血缘,通过getDependecies()获取RDD之间
        的依赖关系(宽依赖,窄依赖),从而判断哪些RDD需要落盘。
    4)一个Partitoner:不是所有RDD都有,只针对键值对才有,要是RDD处理单值就没有。默认的分区器是HashPartitioner。
    5)getPreferredLocations(split:Partition)记录当前数据的存储位置(RDD的数据从哪里来),每个Patition的优先位置。
        一个节点既是DN,又是NM,RDD执行需要在NM上的Executor上,分配任务时就将Executor分配到有数据的节点。
        移动数据不如移动计算,除非资源不够。当资源不够时,不是立即移动资源,需要有一个等待时间。比如从102移动资源需要
        花费10秒,但是2秒后102就有资源可以计算,那么就等待102一会。
        
8. RDD中的方法叫算子,算子分为两类:
    1)转换算子,并不真正执行,只是进行逻辑转换,每次都会创建新的RDD;
    2)行动算子,真正执行,懒加载,延时计算。
9. 创建RDD的三种方式:
    1)集合:集合是在内存中,RDD从内存中读取数据,因为集合存储数据量有限,所以只是在测试环境中用。
    2)从外部储存创建RDD:磁盘或HDFS
10. 抽象类RDD中的模板方法设计模式
    RDD中只是定义了计算分区的业务逻辑,一套模板,但是具体怎么获取分区没有实现,具体在子类中实现,

    //模板方法
    protected def getPartitions: Array[Partition]
    
    /**
   * Get the array of partitions of this RDD, taking into account whether the
   * RDD is checkpointed or not.
   */
  final def partitions: Array[Partition] = {
    checkpointRDD.map(_.partitions).getOrElse {
      if (partitions_ == null) {
        stateLock.synchronized {
          if (partitions_ == null) {
          //获取分区
            partitions_ = getPartitions
            partitions_.zipWithIndex.foreach { case (partition, index) =>
              require(partition.index == index,
                s"partitions($index).partition == ${partition.index}, but it should equal $index")
            }
          }
        }
      }
      partitions_
    }
  }

11. 转换算子分类:当你调用时会帮你创建一个新的RDD,并不会执行真正的计算操作,只是做计算逻辑的封装,只有行动算子触发后,才会做真正的计算。
    1)value类型:RDD内存放单个值
    2)双value类型:指将两个RDD的值放在一起做运算
    3)keyvalue类型:RDD内存放着一对对键值对
    
12. mapPartitions:相当于批处理,一次处理一个分区的数据,内部再调用map函数,尽管处理次数没变,但是与数据库的连接次数大幅度减少,
    相当于缓冲,连接是一个重量级的操作,消耗性能。当一次把一个分区的数据拿过来,再在函数内部进行处理,比每次只取一个,
    效率明显提高。
    注意:批量处理也不一定好,因为是一次把一个分区的数据放到内存中,对服务器内存要求高。当服务器内存资源不足的情况下,
    还是应该使用map。
13.  mapPartitions:
    1)一般适用于批处理的操作,比如:将RDD中的元素插入到数据库中,需要数据库连接,
    如果每一个元素都创建一个连接,效率很低,可以对每个分区的元素,创建一个连接
    2)rdd.mapPartitions(datas => datas.map(_ * 2)) 中datas.map(_ * 2)的map不叫算子,他只是集合中的方法,只有RDD中的方法叫算子,
     他只是在将一个分区的数据拿到后,当做一个集合(实实在在的集合)进行集合中map方法。
14. Spark是基于内存的,不一定Spark一定比MR好,他们各自有应用场景,内存是有天花板的。有些数据量很大,spark无法解决的场景,还是得用MR。

15.mapPartitionsWithIndex:以分区为单位,对RDD中的元素进行映射,并且带分区编号
    1)解读index为分区号,datas为一组分区,对一组分区的数据map遍历,转变数据类型,添加上分区号,当然也可以不加。
   rdd.mapPartitionsWithIndex((index,datas) => datas.map((index,_)))
   2)rdd.mapPartitionsWithIndex((index,datas)=>datas) 什么处理都不做,原封不动输出

16. flatmap::对集合中的元素进行扁平化处理(把整个转化为个体),但是flatMap只能对元素为集合的进行扁平化,如果不是,则报错
    val rdd1: RDD[Any] = sc.makeRDD(List(List(1, 2), List(3, 4), List(5, 6), List(7, 8), 9), 2)
    val newRDD1: RDD[Int] = rdd1.flatMap(datas => datas) //error
17. glom(): 分区转换为数组(个体转换为整体),因为数组放在内存中,如果数据量大内存可能装不下。
    注意:由于由于将分区转换为数组,所以对于分区这个集合他里面的元素就是一个数组,数组里面再装一个个分区元素。
    
18. 创建RDD分区方式:
    1)通过内存集合创建
        1.1)默认的分区方式:取决于分配给当前应用的CPU核数
        1.2)指定分区:通过positions方法决定
    2)读取外部文件创建
        1.1)默认的分区方式:取决于分配给当前应用的CPU核数和2的最小值
        1.2)指定分区:
                》FileInputFormat  --- getSplits  切片方法
                》LineRecordReader --- next         读取
19. RDD的常用算子
    1)转换算子Transformation:转换算子执行完毕之后,会创建新的RDD,并不会马上执行计算
        1.1)map:对RDD中的元素进行一个个映射
        1.2)mapPartitions:以分区为单位,对RDD中的元素进行映射
        1.3)mapPartitionsWithIndex:以分区为单位,对RDD中的元素进行映射,并且带分区编号
        1.4)flatMap:对RDD中的元素进行扁平化处理
        1.5)glom:将RDD中每一个分区中的单个元素,转换为数组
        1.6)groupBy:按照一定的规则,对RDD中的元素进行分组,注意分组与分区不同。
        1.7) filter: 按照一定的规则,对RDD中的元素进行过滤
        1.8)sample:不可以控制抽样个数
            >参数1: 是否抽样放回  true 放回  fasle 不放回
            >参数2: 
                参数1:true 期望元素出现的次数  >0
                参数2:false 每一个元素出现的概率 [0,1]
            >参数3: 随机算法的初始值(种子)
            >takeSample(行动算子):可以控制抽样个数
        1.9) distinct:去重
            底层是通过map + reduceByKey完成去重操作
        1.10)改变分区:
            >coalesce:一般用于缩减分区,默认不执行Shuffle,当然可以叫Shuffle设置为true,这样就可以扩大分区
            >repartition:一般用于扩大分区,默认执行Shuffle,底层调用的就是coalesce。
        1.11)sortByKey:按照指定规则,对RDD中的元素进行排序,默认升序。
        1.12)pipe:对于RDD中的每一个分区,都会执行pipe算子中指定的脚本,将每个分区中的每个元素传递给脚本处理
        1.13) union合集
        1.14)intersection交集
        1.15) subtract 差集
        1.16)zip拉链:必须要保证分区数以及每一个分区中元素的个数一致。
        1.17)partitionBy:按照指定的分区器,通过key对RDD中的元素进行分区,默认是HashPartitioner,可以自定义分区器
        1.18)reduceByKey:将相同的key放在一起,对value进行聚合操作
        1.19)groupByKey:按照key对RDD中的元素进行分组
        1.20)aggregateByKey(zeroValue)(分区内计算规则,分区间计算规则)
        1.21)foldByKey(zeroValue)(内间计算规则),是aggregateByKey的简化,区内和分区间计算规则相同,
        1.22)combineByKey(对当前key的第一个value进行转换,分区内计算规则,分区间计算规则)
        1.23)几种聚合算子对比:
            >reduceByKey(_+_):预聚合combiner
                combineByKeyWithClassTag[V]((v: V) => v, func, func, partitioner)
            >aggregateByKey(zeroValue)(cleanedSeqOp,combOp): cleanedSeqOp:分区内规则,combOp:分区间规则
                combineByKeyWithClassTag[U]((v: V) => cleanedSeqOp(createZero(), v),cleanedSeqOp, combOp, partitioner)
            >foldByKey:
                combineByKeyWithClassTag[V]((v: V) => cleanedFunc(createZero(), v),cleanedFunc, cleanedFunc, partitioner)
            >combineByKey:
                combineByKeyWithClassTag(createCombiner, mergeValue, mergeCombiners, defaultPartitioner(self))
        1.24)sortByKey:按照RDD中的key对元素进行排序
        1.25)mapValues:只对RDD中的Value进行操作
        1.26)join&cogroup
    2)行动算子Action:行动算子执行后,才会触发计算。
        1.27)reduce:对RDD中的元素进行聚合
        1.28)collect.foreach 和 foreach
            >collect.foreach:将每一个Executor中的数据收集到Driver,形成一个新的数组,.foreach不是一个算子,是集合中的方法,是对数组中的元素进行遍历
            >foreach:对RDD中的元素进行遍历
        1.29)count:获取RDD中元素的个数
        1.30)countByKey:获取RDD中每个key对应的元素个数
        1.31)first:获取RDD中第一个元素
        1.32)take:获取RDD中的前几个元素
        1.33)takeOrdered:获取排序后的RDD中的前几个元素
        1.34)aggregate&fold
            >aggregateByKey:处理kv类型的RDD,并且在进行分区间聚合的时候,初始值不参与运算
            >fold 是aggregate的简化版
        1.35)save相关的算子:
            >saveAsTextFile
            >saveAsObjectFile
            >saveAsSequenceFile(只针对kv类型的RDD)
        
20. 分区影响的是开多少个Executor来执行计算,对我们执行程序没有任何影响,不管是开4个,还是2个,对每个分区执行的操作都是相同的。
21. 计算机的随机数是伪随机,是根据随机算法参数的。
22. sample:随机抽样
    1)创建Random对象,如果没有给初始值(seed),就会默认生成一个以时间为单位的初始值,作为随机算法的初始值
    2)种子就是随机算法的初始值
23. filter过滤RDD中的元素,没有提供缩减分区的参数。比如100W条数据,过滤了50W,那么就没有必要需要之前那么多分区。
    注意:分区不是越多越好,比如100G的数据,给10个分区可以,但是100KB的数据,给10个分区就浪费了。
24. 判断某个算子是否经过Shuffle的两种方法:
    1) 让程序sleep一会,看localhost:4040,是否出现Shuffle;
    2)  看底层源码是否创建ShuffledRDD对象
    3) 一般xxxByKey,排序算子需要经过Shuffle
25. reduceByKey有重载的方法,可以指定分区数。因为是聚合操作,所以可以减少分区数。比如以前100个数据,reduce之后50个,就可以减少分区了。
    1)先分区内聚合,为了合并不同分区的数据,必须落盘(Shuffle);
    2)落盘后拿取相同的key到同一分区,再进行聚合。
26. 对落盘的理解:1)当RDD中的操作不需要依赖其他分区的数据,比如map,那么他就可以一直在内存中执行下去,进入下一个RDD。
    2)但是当要RDD算子要执行分组、排序等操作,只在一个分区中无法完成,必须依赖于所有分区的数据,不能在内存中等,因为对内存压力很大,
    所以需要落盘。落盘就要经历Shuffle的过程,落盘后就可以根据分区器或分区数同一组key进去指定分区(默认HashPartitioner)
27. aggregateByKey:按照key对分区内和分区间的数据进行处理,适合于分区内与分区间之间不同业务逻辑的聚合,且可以赋初始值。
    1)场景举例:比如求出每个分区中同一组key的最大值,再对不同分区聚合求和。
    2)如果分区内和分区间的计算逻辑相同,就可以使用reduceBykey
28. foldByKey是aggregateByKey的简化版,当分区内和分区间的业务逻辑相同时,可以简写成foldByKey,但是还是有初始值的,
    reduceByKey没有初始值。
29. reduce与reduceByKey计算逻辑都是相同的,先聚合分区内,再聚合分区间元素。只不过reduceByKey只针对于KV类型,reduce可针对于单值类型。
30. Spark存在序列化,但是并不是所有的序列化都使用Kryo序列化。java序列化是比较重的,会包含header,继承体系等。
    比如对于一个自定义的user(name,age),java序列化之后是87字节,Kryo序列化之后是10字节。
    1)Kryo序列化效率这么高,但是Spark2.0只是部分序列化使用Kryo。是因为Kryo不能控制不参与序列化的属性,
    比如对name加上transient关键字,java序列化之后是44字节,而Kryo序列化之后还是10字节,与之前没有变化。
    2)目前使用的Spark版本,既有java序列化,也有Kryo序列化。也许在未来Spark像Hadoop一样实现自己的序列化,
    或者Kryo序列化更加完善。
    3)Spark使用Kryo的场景是,RDD在Shuffle数据时,简单数据类型,数组和字符串类型,这些Spark已经实现了。
    4)Serializable接口(特质)是一个标识性接口,说明他是可以序列化的,里面没有任何东西。
    trait Serializable extends scala.Any with java.io.Serializable
31. 在序列化时,在属性前加上transient关键字,用transient修饰的属性是不参与序列化的。
32. 查看RDD的血缘关系以及依赖关系
    1)血缘关系:toDebugString
    2)依赖关系:dependencies
        >窄依赖:父RDD一个分区中的数据,还是交给子RDD的一个分区处理
        >宽依赖:父RDD一个分区中的数据,交给子RDD的多个分区处理
33. Spark的job调度
    1)集群(Standalone | Yarn):一个Spark集群可以同时运行多个Spark应用
    2)应用:
        *我们所编写的完成某些功能的程序
        *一个应用可以并发的运行多个Job
    3)Job:
        *job对应着我们应用中的行动算子:每次执行一个行动算子,都提交一个job
        *一个job由多个stage组成
    4)stage:
        *一个宽依赖做一次阶段的划分
        *阶段个数 = 宽依赖个数 + 1
        *一个stage由多个task组成。
    5)task:每一个阶段最后一个RDD的分区数,就是当前阶段的task个数。
    
34. 关于分区、task和Executor的理解?
    1)不同分区交给不同的Executor执行,这只是简单的理解,其实在分区和Executor之间还有Task,在每一个阶段的最后一个RDD,
    看他的分区个数,每一个分区对应创建一个task,把task放在Executor上执行,其实真正提交给执行器,
    让Executor帮我们执行的是封装的task。任务到底有多少个?就是每个阶段最后一个RDD的分区个数。同时每个阶段的分区数
    可能是不一样的,就是通过每个阶段最后一个RDD的分区数确定task个数。
    2)一个Executor可以运行多个task。比如一个job有2个阶段,阶段0有2个task,阶段1有3个task,那么他会同时
    将5个task交给Executor执行。(2个executor可以在同一个节点,也可以在不同节点上)。在executor上运行task,
    一个executor运行可以运行多个task。注意executor与hadoop的MapTask不一样,executor的效率要更高一些,因为
    hadoop每次执行一个MapTask就会开启一个进程,而spark是在启动程序的时候就把相应的executor启动好,比如现在
    需要4个Executor,这4个Executor就已经启动好了,严格说是ExecutorBackend进程,然后任务是多个,其实就是并行计算,
    他会在Executor上开启线程去处理,就像现在我们处理的Executor任务其实就是ExecutorBackend上的线程,他在这里开启多个
    线程来做并行处理,一个是开进程,一个是开线程,当然是开多线程的效率会更高一些。
    3)在yarn集群模式下,在NM上开启开启一个ExecutorBackend进程,一个ExecutorBackend内可以创建多个Executor对象。
        一个Executor上可以运行多个task,其实task就是一个个对象,就是开启相应的线程运行task对象。通过开线程达到并行。
        
35. 一般数据传输的格式:
    1)json:本质还是文本文件,用[]表示多条json文件,用{}表示一条json数据,属性与值之间用:分割,多个属性之间用,分割。
        [{"name":"zhangsan","age":"18"},{"name":"lisi","age":"20"}]
    2)xml

36. RDD的持久化:
    1)缓存:程序结束,就清除缓存,无论是内存还是磁盘
        1.1)cache:底层调用的就是persist,默认存储在内存中,增加副本没有意义,只是会消耗内存
        1.2)persist:可以通过参数指定存储级别
    2)检查点checkponit:
        2.1)可以当做缓存理解,存储在HDFS上更稳定
        2.2)为了避免容错执行时间过长。
    3) 缓存不会切断血缘,但是检查点会切断血缘
    4)由于checkpoint会重头计算一遍,所以生产上通常选择checkpoint和cache一起使用。
37. 数据的读取和保存:
    1)textFile
    2)sequenceFile
    3)objectFile
    4)Json:本质还是通过textFile读取文本,对读到的内容进行处理
    5)HDFS
    6)MySQL:map--mapPartition   foreach--foreachPartition

38. 在Spark里面,有三大结构:
    1)RDD:弹性分布式数据集
    2)累加器:分布式共享只写变量
    3)广播变量:分布式共享只读变量
39. 累加器:分布式共享只写变量
    1)累加器用来对信息进行聚合,并不仅仅是加法操作
40. RDD是一个抽象的概念,体现是一个抽象类,在其下面有很多实现类,比如hadoopRDD,JdbcRDD。不同的实现其对应的分区规则是不同的,
    比如JDBCRDD的分区规则与makeRDD/textFile创建的RDD的分区规则是不同的。具体取决于getPartitions和compute对分区数据的计算。
41. 为什么stage的个数等于宽依赖的个数+1?因为在Spark源码中,首先为会当前RDD创建一个阶段叫resultStage,然后会根据宽依赖的个数,
    为每一个宽依赖创建一个ShuffleMapStage,所以stage的个数等于宽依赖的个数+1,这个1就是resultStage。
42. 在真正提交任务的时候,会对Stage的粒度进一步划分,每一个Stage划分为最后一个RDD的task数。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值