1.1、RDD是spark的核心概念,它是一个容错、可以并行执行的分布式数据集
1.2、RDD包含5个特征:
1.3、RDD的特点:
RDD不可变
宽依赖和窄依赖(窄依赖没有shuffle), 窄依赖 <-> 一对一或者多对一,宽依赖 <-> 多对多或者一对多
窄依赖指父RDD的每个分区只被子RDD的一个分区使用
宽依赖指父RDD的每个分区都有可能被多个子RDD分区使用
可以控制存储级别(内存、磁盘等)来进行持久化。应用程序中多次使用同一个RDD,可以将该RDD缓存起来,该RDD只有第一次计算的时候会根据血缘关系得到分区的数据,在后续其他地方用到该RDD的时候,会直接
从缓存处取而不用再根据血缘关系计算,这样就加速后期的重用
两者相同
sc.makeRDD(1 to 10)
sc.parallelize(1 to 10)
sc.textFile(path)
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):
左外连接
控制类算子有三种,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 掉,是一直存在的。
1、分区的目的:
2、分区原则:
3、默认的分区数(并发数)(可在 spark-default.conf 配置):
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、分区器:
5.1、 分区决定了什么:
5.2、HashPartitioner
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
5.4、自定义分区器:
1、RDD的依赖分为两种:窄依赖(Narrow Dependencies)与宽依赖(Wide Dependencies,源码中称为Shuffle Dependencies)
2、作用:
3、窄依赖:
4、宽依赖:
5、窄依赖的优点:
6、Stage的划分
如何划分stage:
7、其他概念:
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:
负责集群资源的获取和调度
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):
笛卡尔积
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算子
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])
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)
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类算子)
istinct(numPartitions: Int)
SparkContext管理
特性:
只读
好处:
减少了数据的传输
一个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
DAGScheduler: 最主要的职责是对用户Job所形成DAG划分成若干个Stage Job => Stage
TaskScheduler : 负责管理task的分配及状态管理 Stage => 若干Task
Scheduler
1.task不能被序列化,将driver上的数据去executor上执行
2.rdd不能嵌套