SparkCore

一、SparkCore

spark架构
SparkCore_第1张图片

二、RDD

1、RDD概念

1.1、RDD是spark的核心概念,它是一个容错、可以并行执行的分布式数据集

1.2、RDD包含5个特征:

  1. 一个分区的列表
  2. 对于每一个分区都有一个计算函数
  3. 存在对其他RDDs的依赖(宽依赖、窄依赖)的列表
  4. 对于key-value的RDD 有一个分区器
  5. 有一个位置优先的函数

2、RDD特点

1.3、RDD的特点:

  1. 分区
  2. 只读
RDD不可变 
  1. 依赖
宽依赖和窄依赖(窄依赖没有shuffle), 窄依赖 <-> 一对一或者多对一,宽依赖 <-> 多对多或者一对多

窄依赖指父RDD的每个分区只被子RDD的一个分区使用
宽依赖指父RDD的每个分区都有可能被多个子RDD分区使用
  1. 持久化(缓存)
可以控制存储级别(内存、磁盘等)来进行持久化。应用程序中多次使用同一个RDD,可以将该RDD缓存起来,该RDD只有第一次计算的时候会根据血缘关系得到分区的数据,在后续其他地方用到该RDD的时候,会直接
从缓存处取而不用再根据血缘关系计算,这样就加速后期的重用

3、创建RDD

两者相同
sc.makeRDD(1 to 10)
sc.parallelize(1 to 10)
sc.textFile(path)
3.1、PairRDD
reduceByKey():
    会产生shuffle分区内合并,会进行map端的combine

groupByKey():
    会产生shuffle分区内不做合并
    
reduceByKey()和groupByKey()二者区别:
    前者效率更高
    reduceByKey用于对每个key对应的多个value进行merge操作,最重要的是它能够在本地先进行merge操作;
    groupByKey也是对每个key进行操作,不会在本地先merge。

keys:
    把Pair RDD中的key返回形成一个新的RDD
    
values:
    把Pair RDD中的value返回形成一个新的RDD

sortByKey():
    返回一个根据键排序的RDD
    
sortByKey(flag : Boolean)
    参数指定按升序还是降序
    
sortBy(f: (T) => K,ascending: Boolean = true,numPartitions: Int = this.partitions.length):
    sortBy()更灵活
    第一个参数传入按什么排序,第二个参数是升序还是降序,第三个参数是并行度
    wordCountsWithReduce.sortBy(_._2, false, 10).collect

mapValues(func):
    作用在value上的,key不会发生变化
    
join(otherPairRDD):
    等值链接,join表示内连接。当有两个KV的dataset(k,v)和(k,w)返回的是(k,(v,w))的dataset,numPartitions是并发的任务数
    
leftOuterJoin(otherPairRDD):
    左外连接

RDD缓存

控制类算子有三种,cache, persist, checkpoint, 以上算子都可以做持久化,持久化的单位是(partition)。这些算子是懒执行的。必须有action类算子触发执行。checkpoint算子不仅仅能将RDD持久化到磁盘,还能切断RDD之间的依赖关系

Spark速度非常快的原因之一,就是在内存中持久化(或缓存)一个数据集。当持久化一个RDD后,每一个节点都将把计算的分片结果保存在内存中,并在对此数据集(或者衍生出的数据集)进行的其他动作(action)中重用。这使得后续的动作变得更加迅速;

rdd.persist(StorageLevel.DISK_ONLY) 与 checkpoint 也有区别。前者虽然可以将 RDD 的 partition 持久化到磁盘,但该 partition 由 blockManager 管理。一旦 driver program 执行结束,也就是 executor 所在进程 CoarseGrainedExecutorBackend stop,blockManager 也会 stop,被 cache 到磁盘上的 RDD 也会被清空(整个 blockManager 使用的 local 文件夹被删除)

cache:
    def cache(): this.type = persist(); 即persist(StorageLevel.MEMORY_ONLY)
    rddn.cache()

persist:
    persist(StorageLevel.MEMORY_ONLY) 只放在内存中一份
    persist(StorageLevel.MEMORY_ONLY_SER) 序列化的方式放内存
    persist(StorageLevel.MEMORY_ONLY_2) 放在两个exector中,各一份
    persist(MEMORY_AND_DISK) 放在内存和磁盘中,是内存放不下才放到磁盘上
    persist(DISK_ONLY_2) 与上述的存储级别一样,但是将每一个分区都复制到集群的两个结点上
    
    程序执行完persist缓存到磁盘中的数据会被清除

checkpoint:
    RDD容错, 建议写到hdfs上, 会斩断依赖,
    
    val cktest = sc.parallelize(1 to 100000)
    sc.setCheckpointDir("/tmp/checkpoint")
    val cktest2 = cktest.map(_*2)
    cktest2.checkpoint


最后,Spark可以自己监测“缓存”空间的使用,并使用LRU算法移除旧的分区数据。也可以通过显式调用RDD unpersist()手动移除数据。而 checkpoint 将 RDD 持久化到 HDFS 或本地文件夹,如果不被手动 remove 掉,是一直存在的。

RDD分区

1、分区的目的:

  • 设置合理的并行度,提高数据处理的性能。

2、分区原则:

  • 尽可能使得分区的个数,等于集群核心数目
  • 尽可能使同一RDD不同分区的记录数保持一致

3、默认的分区数(并发数)(可在 spark-default.conf 配置):

  • spark.default.parallelism

4、如何做分区:
对于textFile方法,默认情况下:
对于HDFS的分区文件(默认128M),每个都会创建一个RDD分区,与core的数量无关(默认情况)
对于本地文件,默认分区个数等于min(defaultParallelism,2)

可以使用下列方式对RDD的分区数进行修改:    
    rdd.textFile("", n)
    rdd.parallelize(arr, n)
    
还可以使用 repartition(有shuffle)、coalesce 对RDD进行重分区:
    val data = sc.parallelize(1 to 10000, 1)
    val rdd2 = data.repartition(4)
    备注:调用data.repartition后,data的分区数并不会改变,而是返回一个新的RDD,其分区数等于repartition后的分区数。

5、分区器:

  • 只有Key-Value类型的RDD才有分区器,非Key-Value类型的RDD分区器的值是None。

5.1、 分区决定了什么:

  • RDD中分区的个数;
  • RDD中每条数据经过Shuffle过程属于哪个分区;
  • reduce的个数;

5.2、HashPartitioner

  • 是最简单也是默认提供的分区器。对于给定的key,计算其hashCode,并除以分区的个数取余,如果余数小于0,则用余数+分区的个数,最后返回的值就是这个key所属的分区ID。该分区方法可以保证key相同的数据出现在同一个分区中。
    val rdd1 = sc.makeRDD(1 to 100000).map((_, 1))
    rdd1.getNumPartitions
    val rdd2 = rdd1.reduceByKey( _+_)
    rdd2.getNumPartitions
    val rdd3 = rdd1.reduceByKey(new HashPartitioner(2), _+_)  //2为指定的分区的个数
    rdd3.getNumPartitions
    val rdd4 = rdd1.map((_, 1)).reduceByKey(_+_, 30)  //30为指定的分区的个数
    rdd4.getNumPartitions
    
    reduceByKey指定分区的个数有两种方式
  • 主动使用分区器:
    val rdd1 = sc.parallelize(1 to 100)
    val rdd2 = rdd1.map(x=>(x,1))
    rdd2.getNumPartitions
    val rdd3 = rdd2.partitionBy(new org.apache.spark.HashPartitioner(10))
    rdd3.partitioner
    rdd3.partitions.size
    
    //对值的操作既不会影响分区器也不会影响分区的个数
    val rdd1=sc.parallelize(1 to 10)
    val rdd2=rdd1.map(x=>(x,1))
    val rdd3=rdd2.partitionBy(new org.apache.spark.HashPartitioner(10))
    rdd3.partitioner
    val rdd4=rdd3.mapValues(x=>x*2)
    rdd4.partitioner
    rdd4.partitions.size
    
    // 如果是对键操作,则子RDD不再继承父RDD的分区器,但是分区数会继承
    val rdd5=rdd3.map(x=>(x,1))   
    rdd5.partitioner   //分区器为None
    rdd5.partitions.size  //分区个数不变
    
    // 可重新定义partitioner
    val rdd6=rdd5.partitionBy(new org.apache.spark.HashPartitioner(10))
    rdd6.partitioner
    rdd6.partitions.size

5.3、RangePartitioner

  • 简单的说就是将一定范围内的数映射到某一个分区内。算法比较复杂。sortByKey会使用RangePartitioner

5.4、自定义分区器:

  • Spark允许用户通过自定义的Partitioner对象,灵活的来控制RDD的分区方式。

RDD依赖

1、RDD的依赖分为两种:窄依赖(Narrow Dependencies)与宽依赖(Wide Dependencies,源码中称为Shuffle Dependencies)

2、作用:

  • 其一用来解决数据容错;
  • 其二用来划分stage

3、窄依赖:

  • 每个父RDD的一个Partition最多被子RDD的一个Partition所使用(1:1 或 n:1)。例如map、filter、union等操作会产生窄依赖;

4、宽依赖:

  • 一个父RDD的Partition会被多个子RDD的Partition所使用,例如groupByKey、reduceByKey、sortByKey等操作会产生宽依赖;

5、窄依赖的优点:

  • 1、宽依赖对应着shuffle操作,需要在运行过程中将同一个父RDD的分区传入到不同的子RDD分区中,中间可能涉及多个节点之间的数据传输;而窄依赖的每个父RDD的分区只会传入到一个子RDD分区中,通常可以在一个节点内完成转换。
  • 2、当RDD分区丢失时(某个节点故障),spark会对数据进行重算。
    对于窄依赖,由于父RDD的一个分区只的父RDD分区即可,所以这个重算对数据的利用率是100%;对应一个子RDD分区,这样只需要重算和子RDD分区对应
    对于宽依赖,重算的父RDD分区对应多个子RDD分区,这样实际上父RDD 中只有一部分的数据是被用于恢复这个丢失的子RDD分区的,另一部分对应子RDD的其它未丢失分区,这就造成了多余的计算;更一般的,宽依赖中子RDD分区通常来自多个父RDD分区,极端情况下,所有的父RDD分区都要进行重新计算。

6、Stage的划分
如何划分stage:

  1. 遇到宽依赖就断开,遇到窄依赖就把当前的RDD加入到Stage中;
  2. 每个Stage里面的Task的数量是由该Stage中最后一个RDD的Partition数量决定的
  3. 最后一个Stage里面的任务的类型是ResultTask,前面所有其他Stage里面的任务类型都是ShuffleMapTask
  4. 代表当前Stage的算子一定是该Stage的最后一个计算步骤

7、其他概念:

  1. Stage = TaskSet
  2. Job: action触发job
  3. Stage(TaskSet):shuffle切分
  4. Task: 处理逻辑相同,数据不同,任务处理的最小单元,driver发送task到executor执行

Spark概念

1、Spark组件

SparkShell
SparkContext:

    SparkContext是编写Spark应用程序用到的第一个类,是Spark的主要入口点,它负责和整个集群的交互
    
    如果把Spark集群当作服务端,那么Driver就是客户端,SparkContext则是客户端的核心,SparkContext是Spark的对外接口,负责向调用者提供Spark的各种功能。
    
    SparkContext用于连接Spark集群、创建RDD、累加器、广播变量

SparkConf:
    SparkConf为Spark配置类,配置以键值对形式存储;配置项包括:master、AppName、Jars、ExecutorEnv等等
    
SparkEnv:
    包含有:serializer、RpcEnv、Block Manager、内存管理等;
    
DAGScheduler:
    高层调度器,将Job按照RDD的依赖关系划分成若干个TaskSet,也称为Stage;之后结合当前缓存情况及数据就近的原则,将Stage交给TaskScheduler;
    
TaskScheduler:
    负责任务调度资源的分配;

SchedulerBackend:
    负责集群资源的获取和调度
    

2、常见Transformation算子

map(func):
    对调用map的RDD数据集中的每个element都使用func,然后返回一个新的RDD,这个返回的数据集是分布式的数据集

fileter(func):
    对调用filter的RDD数据集中的每个元素都使用func,然后返回一个包含是func为true的元素组成的RDD

flatMap:
    与map类似

mapPartitions(func):
    和map很像,但是map是每个element,而mapPartitions是每个partition
    
mapPartitionsIndex(func):
    逐个处理每个partition,有两个参数,第一个参数是分区id,第二个参数是迭代器。使用迭代器it访问每个partition的行,index保存partition的索引

sample(withReplacement,faction,seed):
    抽样,第一个参数控制是不是放回的采样,第二个参数表示百分比(采多大的样本),第三个参数是种子

union:
    返回一个新的dataset,包含源dataset和给定dataset的元素的集合
    
distinct(numPartitions:Int):
    返回一新的RDD,去重,可以无参可以有参,有参参数表示分区的个数

groupByKey(numPartitions:Int):
    参数可写可不写,不写默认的分区个数,也可以参数指定,当然还有一个可以传入一个分区器的重载的函数

reduceByKey(ascending : scala.Boolean, numPartitions : scala.Int):
    用一个给定的reducefunc再作用在groupByKey产生的(K,Seq[V]),比如求和,求平均数
    
sortByKey(ascending : scala.Boolean, numPartitions : scala.Int):
    按照key进行排序,是升序还是降序 ascending是boolean类型,虽然是个transformation算子但是底层采样的时候有collect操作。
    
join(otherKVDataset,numPartitions:Int):
    当有两个KV的dataset(k,v)和(k,w)返回的是(k,(v,w))的dataset,numPartitions是并发的任务数

cogroup(otherKVDataset,numPartitions:Int):
    作用再key-value的RDD上,当有两个KV的dataset(k,v)和(k,w)返回的是(k,Seq[v],Seq[w])的dataset,
    
cartesian(otherDataset):
    笛卡尔积
    

3、Action操作及常见算子

Action触发了Job的执行,application中如果有多个Action,对应多个Job

reduce(func):
    传入的函数是两个参数,输出返回一个值,传入函数必须满足交换率和结合率

collect():
    一般在filter或者足够小的结果的时候,再用collect封装返回一个数组
    
count():
    返回个数
    
first():
    返回的是dataset中的第一个元素
    
take(n):
    返回前n个element
    
takeSample(withRelacement,num,seed):
    抽样返回一个dataset中的num个元素(与sample类似,但第二个参数不是百分比)
    
saveAsTextFile(path):
    把dataset写到一个textFile中,或者hdfs,或者hdfs支持的文件系统中,spark把每条记录都转换为一行记录,然后写到file中
    
countByKey():
    要求的是一个pairRDD,返回的是key对应个数的一个map,作用于一个RDD,countByKey()底层最后有个collect()操作,所以不建议使用此算子
    
    def countByKey(): Map[K, Long] = self.withScope {
        self.mapValues(_ => 1L).reduceByKey(_ + _).collect().toMap
    }
countByValue():
    不要求是pairRDD,底层调用countByKey(),底层最后有个collect()操作,所以不建议使用
    
    def countByValue()(implicit ord: Ordering[T] = null): Map[T, Long] = withScope {
        map(value => (value, null)).countByKey()
    }
    
foreach(func)
    对dataset中的每个元素都使用func,是action算子,map是transformation算子

4、带有Shuffle的算子

  1. ByKey算子
  2. 重分区算子
  3. Join类算子
(1) ByKey算子
groupByKey(numPartitions:Int):
参数可写可不写,不写默认的分区个数,也可以参数指定,当然还有一个可以传入一个分区器的重载的函数

reduceByKey(ascending : scala.Boolean, numPartitions : scala.Int):
    用一个给定的reducefunc再作用在groupByKey产生的(K,Seq[V]),比如求和,求平均数

sortByKey(ascending : scala.Boolean, numPartitions : scala.Int):
    按照key进行排序,是升序还是降序 ascending是boolean类型,虽然是个transformation算子但是底层采样的时候有collect操作。
    
join(otherKVDataset,numPartitions:Int):
    当有两个KV的dataset(k,v)和(k,w)返回的是(k,(v,w))的dataset,numPartitions是并发的任务数

cogroup(otherKVDataset,numPartitions:Int):
    作用再key-value的RDD上,当有两个KV的dataset(k,v)和(k,w)返回的是(k,Seq[v],Seq[w])的dataset

aggregateByKey[U: ClassTag](zeroValue: U, partitioner: Partitioner)(seqOp: (U, V) => U,combOp: (U, U) => U)
  
combineByKey[C](createCombiner: V => C,mergeValue: (C, V) => C,mergeCombiners: (C, C) => C)

sortBy[K](f: (T) => K, ascending: Boolean = true,numPartitions: Int = this.partitions.size)(implicit ord: Ordering[K], ctag: ClassTag[K])
(2) 重分区算子
repartition(numPartitions: Int)

coalesce(numPartitions: Int, shuffle: Boolean = false)

两者的区别:
repartition是coalesce带有shuffle的情况,所以前者一定有shuffle,后者不一定有shuffle

def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T] = withScope {
				coalesce(numPartitions, shuffle = true)
				}

coalesce(numPartitions: Int, shuffle: Boolean = false,
           partitionCoalescer: Option[PartitionCoalescer] = Option.empty)
          (implicit ord: Ordering[T] = null)
(3) join算子
join[W](other: RDD[(K, W)], partitioner: Partitioner)

cogroup[W](other: RDD[(K, W)], partitioner: Partitioner)

leftOuterJoin[W](other: RDD[(K, W)],partitioner: Partitioner)
  
intersection(other: RDD[T])

subtract(other: RDD[T])

subtractByKey[W: ClassTag](other: RDD[(K, W)], p: Partitioner): RDD[(K, V)]

(姑且把后面三个也放到Join类算子)

(4) 其他算子
istinct(numPartitions: Int)

5、共享变量

  • 广播变量
SparkContext管理

特性:
    只读
好处:
    减少了数据的传输
  • 累加器

5、作业调度相关概念

一个application由一个driver和若干个Job构成,一个Job由多个Stage构成,一个Stage由多个没有Shuffle关系的Task组成;

当执行一个Application时,Driver会向集权管理器申请资源,启动Executor,Executor启动后向Driver注册。并向Executor发送应用程序代码和文件,然后在Executor上执行Task,运行结束后,执行结果会返回给Driver,或者写到HDFS或其他数据库中

Application: driver (SparkContext) + job
Job => DAG => Stage => Task

6、SparkContext三大组件

DAGScheduler: 最主要的职责是对用户Job所形成DAG划分成若干个Stage  Job => Stage
TaskScheduler : 负责管理task的分配及状态管理 Stage => 若干Task
Scheduler

7、Spark常见问题

1.task不能被序列化,将driver上的数据去executor上执行
2.rdd不能嵌套

你可能感兴趣的:(大数据,spark,spark,大数据)