1.什么是Spark?
2.为什么要使用Spark?
因为Hadoop在处理数据的时候有多次的IO和网络操作,Mapreduce都要转成map,shuffle和reduce等核心阶段,而且任务之间是串行执行的
Spark对比Hadoop MR的特点
内存计算比mr快100倍,磁盘计算快mr10倍
使用方便,安装部署简单,支持交互式
支持处理丰富
继承hadoop,能都读取hadoop上的数据,hdfs,hbase等
Spark运行模式
- local本地模式,多线程
- standalone集群模式可以使用zk解决单点故障问题
- on YARN,通过yarn调度资源,spark负责任务调度和计算
- On mesos
- on cloud
常见术语解释
什么是RDD?
RDD就是分布式弹性数据集,可以在定义app的时候指定,通常是加载外部资源数据或者是对象集合
RDD有两种操作,转换和动作
转换就是将原来的Rdd通过某种规则转换成新的RDD,转换是函数规则,例如map和Filter
动作就是求出结果
转换采用的是懒处理,只有当动作的时候,才会真真的计算
如果需要对一个RDD进行重用,可以通过rdd,persist()方法将其放入内存中
血统,也就是RDD之间的关系
RDDD本质上是一个只读的分区记录集合,每一个分区就是一个dataset(类似于mr中的文件 切片)
map会产生窄依赖,groupby产生宽依赖
RDD的特征
- 有一个分片列表,可以将一个RDD分成多个分片,这样才能并行运算
- 函数计算每一个分片
- 对父RDD的依赖可以分为宽依赖和窄依赖(一个是窄,多个依赖就是宽依赖)
- 对KV类型的RDD是根据hash分区的,每一个分片有预先计算位置,移动运算优于移动数据
通过RDD.toDebugString,可以看到当前RDD的血统
基本概念
RDD来自英文Resilient Distributed DataSet,字面翻译也就是弹性可恢复的分布式的数据集,弹性是指这个集合是可大可小可以动态变化的,可恢复是针对处理过程是可恢复的,数据是存储在不同机器上的
RDD实际上是对分区数据的一种抽象和逻辑集合,RDD可以通过读取HDFS上的数据,也可以由内部数据进行初始化
RDD分区,也就是对数据集的划分,分区决定了处理数据的并行度
默认是一个文件切片一个分区(如果文件大小差距比较大,会讲大的文件对应多个分区,具体的逻辑在FileInputFormat类中),
可以通过指定运行的cores参数,来指定分区数量,核心数决定了运行时的线程数量
一个Job对应多个Task,每个Job对应多个stage,如果计算过程没有shuffle,就是一个stage,如果存在shuffle,每次shuffle都会讲task划分为两个阶段(stage),一个stage对应一个task
一个任务如果没有shuffle就是一个task,一个stage,如果有一次shuffle,就是两个stage,也就是两个task,这两个task会形成DAG(有向无环图,从子RDD不断往上递归)也就是执行顺序,对于HDFS文件来说,文件中的一条数据只对应一个分区,但是可以有多个task操作操作这个数据
Task在Dirver端生成之后,会通过序列化来发送到worker结点的excutor中,Executor启动一个线程进行反序列化,执行用户的逻辑,如果存在后续的task,会存在内存中,或者持久化到磁盘上,Task持有父RDD的迭代器,对父RDD的迭代器中进行处理
举个例子
API使用者:司令
RDD:军队
Driver,也就是智囊团
Mater,就是党中央
Worker是各个军队
不管是Worker,和Driver,必须要是党的一部分才能参政议政,所以都会向Master注册自己
当有战斗任务的时候,我们发号施令说明自己像干嘛,需要多少人参加,然后智囊团进行计划,那几个军队参加比较合适和占据优势
然后向Master去申请,Dirver将作战计划形成文档(Task的序列化文档),加密后发送给各个战斗部门所有的战斗的具体都是由智慧团指导的,各个军队之间将各自小的战斗成果汇总成大的胜利阶段一步一步取得胜利,但是这个谋划必须要由将军命令行动才会执行
RDD算子及功能
Transformation
reduceBykey比groupbykey的效率高,因为数据量小,而且可以局部聚合,groupbyKey在shuffle的时候会将kv都写入文件中等待stage2聚合
涉及到shuffle的转换算子详解
def main(args: Array[String]): Unit = {
val goodsMoneyCount: SparkConf = new SparkConf().setAppName("GoodsMoneyCount").setMaster("local[*]")
val sc: SparkContext = new SparkContext(goodsMoneyCount)
val rdd1: RDD[String] = sc.textFile("")
// new ShuffledRDD[K, V, C](self, partitioner)
// .setSerializer(serializer)
// .setAggregator(aggregator)
// .setMapSideCombine(mapSideCombine)
// Aggregator,集合器
// 其中的rdd.reduceBykey,其实就是创建一个新的shufflerdd,并且设定了Aggregator,这里面定义了怎么进行聚合
val wordAndOne: RDD[(String, Int)] = rdd1.map(s=>(s,1))
val shuffleRDD: ShuffledRDD[String, Int, Int] = new ShuffledRDD[String,Int,Int](wordAndOne,new HashPartitioner(rdd1.getNumPartitions))
//mapside
val createCombiner=(a:Int)=>a
val mergeValue=(a:Int,b:Int)=>b + a
//shuffle read之后执行
val mergeCombiners=(arr1:Int,arr2:Int)=>{arr1 +arr2}
//定义三个函数
val reduceByKey: ShuffledRDD[String, Int, Int] = shuffleRDD.setAggregator(new Aggregator[String, Int, Int](createCombiner,mergeValue,mergeCombiners))
reduceByKey.saveAsTextFile("shuffle-out")
sc.stop()
//也就是说,其实reduce和group等只要涉及到shuffle的都是调用了ShuffleRDD这个类,这个类将应用传进来的三个函数
// 其中前两个函数都是在shuffleread的时候调用,也就是写入本地磁盘之前,第三个是在shuffleread之后调用
//前两个是对分区的结果进行聚合操作,最后一个参数是对分区后的结果进行聚合
}
通过创建ShuffleRDD来自定义shuffle方法
自定义shuffledRDD实现
ReduceByKey
def main(args: Array[String]): Unit = {
val sc: SparkContext = new SparkContext(new SparkConf().setAppName("ReduceByKey").setMaster("local[*]"))
val words: RDD[String] = sc.parallelize(
List(
"spark", "hadoop", "hive", "spark",
"spark", "flink", "spark", "hbase",
"kafka", "kafka", "kafka", "kafka",
"hadoop", "flink", "hive", "flink"
), 4)
// val value: RDD[(String, Int)] = words.mapPartitions(f=>{f.map(t=>(t,1))})
// //获得每个分区的迭代器,将每个值加上1之后,获得rdd
// val tuples: Array[(String, Int)] = value.reduceByKey(_+_).collect()//根据key进行聚合操作
val shuffle: ShuffledRDD[String, Int, Int] = new ShuffledRDD[String,Int,Int](words.map(f=>(f,1)),new HashPartitioner(words.partitions.length))
//通过创建ShuffledRDD,来自定义构建Shuffled
//其中[String,Int,Int] ----分别是针对父rddwords.map(f=>(f,1)),的输入的key,输出的value,以及进行shuffle的结果集合输出,第二个参数是分区器
//聚合器 Aggregator
//第一个聚合器是对每一行数据的val进行处理,第二个是对局部进行什么处理,最后一个是对全局结果进行什么处理
val rdd: ShuffledRDD[String, Int, Int] = shuffle.setAggregator(new Aggregator[String, Int, Int](x=>x, (a, b)=>a+b,_+_))
println(rdd.collect().toBuffer)
sc.stop()
}
groupByKey
def main(args: Array[String]): Unit = {
val sc: SparkContext = new SparkContext(new SparkConf().setAppName("ReduceByKey").setMaster("local[*]"))
val words: RDD[String] = sc.parallelize(
List(
"spark", "hadoop", "hive", "spark",
"spark", "flink", "spark", "hbase",
"kafka", "kafka", "kafka", "kafka",
"hadoop", "flink", "hive", "flink"
), 4)
val rdd1=words.map(f=>(f,1))
val shuffle: ShuffledRDD[String, Int, ArrayBuffer[Int]] = new ShuffledRDD[String,Int,ArrayBuffer[Int]](rdd1,new HashPartitioner(rdd1.partitions.length))
val createCombiner = (v: Int) => ArrayBuffer(v) //每个记录怎么处理
val mergeValue = (buf: ArrayBuffer[Int], v: Int) => buf += v //每个分区怎么处理
val mergeCombiners = (c1: ArrayBuffer[Int], c2: ArrayBuffer[Int]) => {c1 ++= c2
} //对各个分区结果怎么处理
shuffle.setAggregator(new Aggregator[String,Int,ArrayBuffer[Int]](createCombiner,mergeValue,mergeCombiners))
//第一个函数是指定了每个数据在map处理完成之后就开始执行,也就是mapPartition之后
//处理完成之后,就开始将相同的key分发到不同的分区,这是在shuffle之前,每个分区将自己分区的结果进行何种计算
//这时候的拿到的已经是分区内每个key及对应的迭代器
//最后一个是task2已经拿到shuffle之后的结果了,这时候的结果是多个分区的结果,对这些分区的数据进行何种处理
println(shuffle.collect().toBuffer)
Thread.sleep(1000000000)
sc.stop()
}
aggregateByKey,每个分区加上一个初始值,然后对相同的key做f1,对不同分区做f2
def main(args: Array[String]): Unit = {
val sc: SparkContext = new SparkContext(new SparkConf().setAppName("ReduceByKey").setMaster("local[*]"))
val wordAndCount = sc.parallelize(List(
("spark", 5), ("hadoop", 2), ("spark", 3), ("hive", 4),
("spark", 9), ("hadoop", 4), ("spark", 1), ("hive", 2)
), 2)
val result = wordAndCount.aggregateByKey(10)(Math.max(_, _), _+_)
//map阶段的局部结果进行max(_, _)运算,然后将总体结果 _+_
//初始值10,每个分区一个初始值
result.saveAsTextFile("aggregate3-out")
sc.stop()
}
Cogroup
def join[W](other: RDD[(K, W)], partitioner: Partitioner): RDD[(K, (V, W))] = self.withScope {
this.cogroup(other, partitioner).flatMapValues( pair =>
for (v <- pair._1.iterator; w <- pair._2.iterator) yield (v, w)
)
}
将两个RDD进行组合,依赖于Key,返回的是同一个key的多个iterator,每个都是一个分区的元素迭代器
join方法也是依赖于这个方法
ACTION
Spark-submit的参数
Spark配置文件
Spark原理及流程
Standalone模式
这里的standalone是通过spark-submit提交任务的,这时候,SparkSubmit和Driver都是是运行在提交任务的客户端的,还有一种是cluster模式,这种模式的的Driver是运行在一个Worker结点上的
- start-all.sh启动之后,会通过本地shell启动,将本地结点的信息向zookeeper注册,并且注册为Master
- 启动slave配置文件的子节点
- 启动app程序后,首先找到Master结点,
- 这时候DiverApplication里的scheduler收到任务会要求Master调度多少资源给他,Master会通知Worker创建Executer,
- Worker创建完毕之后之后会向Driver反向注册(Master告诉了Worker).
注意:
每个Worker可以运行多个ExcuterRunner(每个都是一个单独的任务)
一个Executer对应一个CoarseGrainedExecutorBackend,
一个Executor里面有该worker分得的该任务的task,可以有多个task,单这些task都是同一个任务的,一个Worker两个Executor的Task是不同任务的task,Task只是不同的对象,Excutor就是线程池,每个Task运行由一个独立线程反序列化的Task到线程池中