Spark整理(2)
一,Standalone提交任务
1.1 Standalone-client提交任务
- 提交命令
/spark-submit
--master spark://node01:7077
--class org.apache.spark.examples.SparkPi
../lib/spark-examples-1.6.0-hadoop2.6.0.jar
1000
或者
/spark-submit
--master spark://node01:7077
--deploy-mode client
--class org.apache.spark.examples.SparkPi
../lib/spark-examples-1.6.0-hadoop2.6.0.jar
1000
-
执行原理图解
-
执行流程
1.集群启动后,worker节点会定期向Master汇报资源情况(cpu,内存等)
2.客户端提交程序后,会在客户端启动Driver进程
3.Driver会向Master申请当前程序需要的Application资源
4.资源申请成功后,Driver将task发送到Worker节点上执行
5.Driver将执行结果回收到Driver端。
- 总结
client模式适用于测试调试程序。driver是在客户端启动的,这里的客户端就是指提交程序的节点。
在Driver端可以看到task执行的情况,生产环境不能使用client模式,是因为:
假设要提交100个Application到集群运行,Driver每次都会在提交程序的节点上启动,会导致客户端100次网卡流量暴增的问题。
1.2 Standalone-cluster提交任务
- 提交命令
./spark-submit
--master spark://node1:7077
--deploy-mode cluster
--class org.apache.spark.examples.SparkPi
../lib/spark-examples-1.6.0-hadoop2.6.0.jar
100
- 注意
Standalone-cluster提交方式,应用程序使用的所有的jar文件,必须保证所有的worker节点都要有,因为这种方式,Spark不会自动上传包。
解决方式:
1.将所有的依赖包和文件打到同一个包中,然后放在hdfs上。
2.将所有的依赖包和文件各放一份到worker节点
-
执行原理图解
-
执行流程
1.首先是集群启动后,worker定期向master汇报资源情况
2.cluster模式下提交程序后,首先会去Master申请启动Driver进程
3.Master随机的在一台节点上开启Driver进程
4.Driver向Master申请Application程序运行所需资源
5.Master分配好资源后,Driver将Task发送到worker上执行
6.Driver回收任务执行结果,监控任务的状态
- 总结
Driver进程是在集群某一个Worker上启动的,在客户端是无法查看task的运行情况的。因为集群提交Driver是随机分配在Worker节点上启动的,所以不存在网卡流量激增的问题。
- Driver
1.向Master申请Application程序所需资源
2.分配task到Worker
3.回收task的运行结果
4.监控task的运行情况
二,Yarn模式提交任务
2.1 yarn-client提交任务
- 提交命令
./spark-submit
--master yarn
--class
org.apache.spark.examples.SparkPi ../lib/spark-examples-1.6.0-hadoop2.6.0.jar
100
或者
./spark-submit
--master yarn–client
--class
org.apache.spark.examples.SparkPi ../lib/spark-examples-1.6.0-hadoop2.6.0.jar
100
或者
./spark-submit
--master yarn
--deploy-mode client
--class
org.apache.spark.examples.SparkPi ../lib/spark-examples-1.6.0-hadoop2.6.0.jar
100
-
执行原理图解
-
执行流程
1.在客户端提交程序,会在当前节点启动一个Driver进程
2.Driver向RM申请启动AppMaster
3.RM随机在一个NM节点上启动AppMaster
4.AppMaster启动后,会向RM申请启动一批资源(Container),用于启动Exector
5.RM分配给AppMaster一批资源
6.AppMaster启动Exector
7.Driver发送task到Exector上执行,并监听task的运行情况和回收结果。
- 总结
Yarn-client 模式同样是适用于测试,因为 Driver 运行在本地,Driver会与 yarn 集群中的 Executor 进行大量的通信,会造成客户机网卡流量的大量增加。
- ApplicationMaster
1.为当前的Applicatin向RM申请启动资源
2.给NodeManager发送消息启动Exector
注意:ApplicationMaster 有 launchExecutor 和申请资源的功能,并没有作业调度的功能。
2.2 yarn-cluster提交任务方式
- 提交方式
./spark-submit
--master yarn
--deploy-mode cluster
--class
org.apache.spark.examples.SparkPi ../lib/spark-examples-1.6.0-hadoop2.6.0.jar
100
或者
./spark-submit
--master yarn-cluster
--class
org.apache.spark.examples.SparkPi ../lib/spark-examples-1.6.0-hadoop2.6.0.jar
100
-
执行原理图解
-
执行流程
1.客户端提交程序到集群中,发送请求给RM请求启动AppMaster
2.RM随机在集群节点中分配一台启动AppMaster(相当于Driver)
3.AppMaster启动后,向RM申请执行给Application程序的所需资源
4.RM返回给AppMaster一批NodeManager资源(Container)
5.AppMaster发送消息到NodeManager,启动Exector,Exector启动后会反向注册给Driver
6.Driver分发task到Exector,并监听任务情况,回收任务结果
- 总结
Yarn-Cluster 主要用于生产环境中,因为 Driver 运行在 Yarn 集群中某一台 nodeManager 中,每次提交任务的 Driver 所在的机器都是随机的,不会产生某一台机器网卡流量激增的现象,缺点是任务提交后不能看到日志。只能通过 yarn 查看日志。
- ApplicationMaster的作用
1.为当前的Applicatin向RM申请启动资源
2.给NodeManager发送消息启动Exector
3.任务调度
三,补充算子
3.1 转换算子
- Join LeftOutJoin RightOuterJoin FullOuterJoin
作用在<K,V>格式的RDD上。根据K进行连接,对(K,V)和(K,W)进行join返回(K,(V,W))
Join后的分区数和父RDD分区数多的那一个相同
java代码实现:
package com.shsxt.spark.java;
import com.google.common.base.Optional;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.VoidFunction;
import scala.Tuple2;
import java.util.Arrays;
public class Ja_Join {
public static void main(String args[]){
SparkConf sparkConf = new SparkConf();
sparkConf.setMaster("local").setAppName("join");
JavaSparkContext sc = new JavaSparkContext(sparkConf);
JavaPairRDD<Integer, String> nameRDD = sc.parallelizePairs(Arrays.asList(
new Tuple2<Integer, String>(0, "aa"),
new Tuple2<Integer, String>(1, "a"),
new Tuple2<Integer, String>(2, "b"),
new Tuple2<Integer, String>(3, "c")
));
JavaPairRDD<Integer, Integer> scoreRDD = sc.parallelizePairs(Arrays.asList(
new Tuple2<Integer, Integer>(1, 100),
new Tuple2<Integer, Integer>(2, 200),
new Tuple2<Integer, Integer>(3, 300),
new Tuple2<Integer, Integer>(4, 400)
));
JavaPairRDD<Integer, Tuple2<String, Integer>> joinRDD = nameRDD.join(scoreRDD);
System.out.println(joinRDD.partitions().size() +" ----------- ");
joinRDD.foreach(new VoidFunction<Tuple2<Integer, Tuple2<String, Integer>>>() {
@Override
public void call(Tuple2<Integer, Tuple2<String, Integer>> t) throws Exception {
System.out.println(t);
}
});
JavaPairRDD<Integer, Tuple2<String, Optional<Integer>>> leftJoin = nameRDD.leftOuterJoin(scoreRDD);
leftJoin.foreach(new VoidFunction<Tuple2<Integer, Tuple2<String, Optional<Integer>>>>() {
@Override
public void call(Tuple2<Integer, Tuple2<String, Optional<Integer>>> rssult) throws Exception {
System.out.println(rssult);
}
});
JavaPairRDD<Integer, Tuple2<Optional<String>, Integer>> rightOuterJoin = nameRDD.rightOuterJoin(scoreRDD);
rightOuterJoin.foreach(new VoidFunction<Tuple2<Integer, Tuple2<Optional<String>, Integer>>>() {
@Override
public void call(Tuple2<Integer, Tuple2<Optional<String>, Integer>> result) throws Exception {
System.out.println(result);
}
});
JavaPairRDD<Integer, Tuple2<Optional<String>, Optional<Integer>>> fullOuterJoin = nameRDD.fullOuterJoin(scoreRDD);
fullOuterJoin.foreach(new VoidFunction<Tuple2<Integer, Tuple2<Optional<String>, Optional<Integer>>>>() {
@Override
public void call(Tuple2<Integer, Tuple2<Optional<String>, Optional<Integer>>> result) throws Exception {
System.out.println(result);
}
});
}
}
- union
合并两个数据集,两个数据集的类型要一致
返回新的RDD的分区数是合并RDD分区数的总和
scala代码实现:
package com.shsxt.spark.scala
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Sp_Union {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf()
sparkConf.setMaster("local").setAppName("union")
val sc = new SparkContext(sparkConf)
val RDD1 = sc.parallelize(Array(1,2,3),3)
val RDD2 = sc.parallelize(Array(4,5,6),2)
val result: RDD[Int] = RDD1.union(RDD2)
println(result.partitions.length) //输出 5
result.foreach(println)
}
}
java代码实现:
package com.shsxt.spark.java;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.VoidFunction;
import org.apache.spark.rdd.RDD;
import org.apache.spark.sql.execution.columnar.INT;
import org.codehaus.janino.Java;
import java.util.Arrays;
public class Ja_Union {
public static void main(String args[]){
SparkConf sparkConf = new SparkConf();
sparkConf.setMaster("local").setAppName("union");
JavaSparkContext sparkContext = new JavaSparkContext(sparkConf);
JavaRDD<Integer> rdd = sparkContext.parallelize(Arrays.asList(1, 2, 3),2);
JavaRDD<Integer> rdd1 = sparkContext.parallelize(Arrays.asList(3, 6, 9),3);
JavaRDD<Integer> result = rdd.union(rdd1);
System.out.println(result.partitions().size());
result.foreach(new VoidFunction<Integer>() {
@Override
public void call(Integer t) throws Exception {
System.out.println(t);
}
});
}
}
- intersection
取两个数据集的交集
分区数取两个RDD中分区数较大的一个
scala代码实现:
package com.shsxt.spark.scala
import org.apache.spark.{SparkConf, SparkContext}
object Sp_Intersection {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf()
sparkConf.setMaster("local").setAppName("union")
val sc = new SparkContext(sparkConf)
val RDD1 = sc.parallelize(Array(1,2,3),3)
val RDD2 = sc.parallelize(Array(3,5,6),2)
val result = RDD1.intersection(RDD2)
println(result.partitions.length) //输出 3
result.foreach(println)
}
}
- subtract
取两个数据集的差集
scala代码实现:
package com.shsxt.spark.scala
import org.apache.spark.{SparkConf, SparkContext}
object Sp_Subtract {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf()
sparkConf.setMaster("local").setAppName("union")
val sc = new SparkContext(sparkConf)
val RDD1 = sc.parallelize(Array(1,2,3),3)
val RDD2 = sc.parallelize(Array(3,5,6),2)
val result = RDD2.subtract(RDD1) //前一个RDD的分区数
println(result.partitions.length)
result.foreach(println);
}
}
- mapPartition
与Map类似,遍历的单位是每个partition上的数据。
java代码:
package com.shsxt.spark.java;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.FlatMapFunction;
import java.util.ArrayList;
import java.util.Iterator;
public class Ja_MapPartitions {
public static void main(String args[]){
SparkConf conf = new SparkConf();
conf.setMaster("local").setAppName("mappartition");
JavaSparkContext sc = new JavaSparkContext(conf);
JavaRDD<String> lines = sc.textFile("./wc.txt",3);
JavaRDD<String> mapPartitions = lines.mapPartitions(new FlatMapFunction<Iterator<String>, String>() {
@Override
public Iterable<String> call(Iterator<String> t) throws Exception {
System.out.println("创建数据库连接......");
ArrayList<String> list = new ArrayList<>();
while (t.hasNext()) {
list.add(t.next());
}
System.out.println("批量插入数据........");
System.out.println("关闭数据库连接......");
return list;
}
});
//触发算子 触发执行
mapPartitions.collect();
sc.stop();
}
}
-
distinct(map+reduceByKey+map)
-
cogroup
当调用类型(K,V)和(K,W)的数据上时,返回一个数据集(K,(iterable,iterable))
java代码:
package com.shsxt.spark.java;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.VoidFunction;
import scala.Tuple2;
import java.util.Arrays;
import java.util.List;
public class Ja_Cogroup {
public static void main(String args[]){
SparkConf conf = new SparkConf().setAppName("ReduceOperator")
.setMaster("local");
JavaSparkContext sc = new JavaSparkContext(conf);
List<Tuple2<String,String>> studentsList = Arrays.asList(
new Tuple2<String,String>("1","zhangsan"),
new Tuple2<String,String>("2","lisi"),
new Tuple2<String,String>("2","wangwu"),
new Tuple2<String,String>("3","maliu"));
/**
* 1 zhangsan 100
* 1 zhangsan 100
* 2 lisi 90
* 2 lisi 60
* 3 wagnwu 80
* 3 wangwu 50
*/
List<Tuple2<String,String>> scoreList = Arrays.asList(
new Tuple2<String,String>("1","100"),
new Tuple2<String,String>("2","90"),
new Tuple2<String,String>("3","80"),
new Tuple2<String,String>("1","1000"),
new Tuple2<String,String>("2","60"),
new Tuple2<String,String>("3","50"));
JavaPairRDD<String, String> stuRDD = sc.parallelizePairs(studentsList);
JavaPairRDD<String, String> scoreRDD = sc.parallelizePairs(scoreList);
JavaPairRDD<String, Tuple2<Iterable<String>, Iterable<String>>> cogroupRDD = stuRDD.cogroup(scoreRDD);
cogroupRDD.foreach(new VoidFunction<Tuple2<String, Tuple2<Iterable<String>, Iterable<String>>>>() {
@Override
public void call(Tuple2<String, Tuple2<Iterable<String>, Iterable<String>>> tuple2) throws Exception {
System.out.println(tuple2);
}
});
sc.close();
}
}
- mapPartitionWithIndex
遍历分区数据的同时加上分区号
java代码实现:
package com.shsxt.spark.java;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.Function2;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
public class Ja_MapPartitionWithIndex {
public static void main(String args[]){
SparkConf conf = new SparkConf();
conf.setMaster("local").setAppName("mapPartitionWithIndex");
JavaSparkContext sc = new JavaSparkContext(conf);
List<String> list = Arrays.asList("zhangsan1", "zhangsan2", "lis1", "lisi2");
//这里的第二个参数是设置并行度,也是RDD的分区数,并行度理论上来说设置大小为core的2-3倍
JavaRDD<String> rdd = sc.parallelize(list, 3);
JavaRDD<String> rdd1 = rdd.mapPartitionsWithIndex(new Function2<Integer, Iterator<String>, Iterator<String>>() {
@Override
public Iterator<String> call(Integer index, Iterator<String> t) throws Exception {
ArrayList<String> list = new ArrayList<>();
while (t.hasNext()) {
String s = t.next();
list.add(s);
System.out.println("partition id is "+index +",value is "+s );
}
return list.iterator();
}
}, true);
rdd1.count();
sc.stop();
}
}
3.2 触发算子
- foreachPartition
遍历的是每个partition的数据
java代码:
package com.shsxt.spark.java;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.VoidFunction;
import java.util.ArrayList;
import java.util.Iterator;
public class Ja_ForeachPartitions {
public static void main(String args[]){
SparkConf conf = new SparkConf();
conf.setMaster("local").setAppName("mappartition");
JavaSparkContext sc = new JavaSparkContext(conf);
JavaRDD<String> lines = sc.textFile("./wc.txt",3);
lines.foreachPartition(new VoidFunction<Iterator<String>>() {
@Override
public void call(Iterator<String> t) throws Exception {
ArrayList<String> list = new ArrayList<>();
System.out.println("打开数据库连接");
while (t.hasNext()){
list.add(t.next());
}
System.out.println("批量插入数据");
System.out.println("关闭连接.....");
}
});
sc.close();
}
}
四,相关术语解释
- Master
资源管理的主节点(进程)
- Cluster Manager
在集群上获取资源的外部服务(例如:standalone,Mesos,Yarn)
- Worker Node(standalone)
资源管理的从节点(进程)或者说管理本机资源的进程
- Application
基于Spark的用户程序,包含了Driver程序和运行在集群上的exector程序
- Driver Program
用来连接工作进程(worker)的程序
- Exector
是在一个Worker进程所管理的节点上为某Application启动的一个进程,该进程负责运行任务,并且负责将数据存在内存或者磁盘上。每个应用都有各自独立的的exectors.
- Task
被送到某个Exector上的工作单元
- Job
包含很多任务(task)的并行计算,可以看做和action对应
- Stage
一个Job会被拆分很多组任务,每组任务被称为Stage(就像MapReduce分为MapTask和ReduceTask一样)
五,窄依赖和宽依赖
RDD之间有一系列的依赖关系,依赖关系又分为窄依赖和宽依赖
5.1 窄依赖
- 父RDD和子RDD partition之间的关系是一对一
- 父RDD和子RDD partition之间的关系是多对一
- 不会有shuffle的产生
5.2 宽依赖
- 父RDD和子RDD partition之间的关系是一对多
- 会有shuffle的产生
5.3 窄宽依赖理解图
以WordCount为例
六,Stage
Spark任务会根据RDD之间的依赖关系,形成一个DAG有向无环图,DAG会提交给DAGScheduler,DAGSCHeduler会把DAG划分相互依赖的多个stage,划分stage的依据就是RDD之间的宽窄依赖。遇到宽依赖就划分stage,每个stage包含一个或多个Task任务。然后将这些task以task以taskSet的形式提交给TaskScheduler运行。
-
Stage是由一组并行的task组成
-
切割规则,遇到宽依赖就切割stage
6.1 stage的计算模式
pipeline管道计算模式,pipeline只是一种计算思想,模式。
- 数据一直在管道里面什么时候数据会落地?
1.对RDD进行持久化(cache,persist)
2.shuffle write的时候
- Stage的task并行度是由stage的最后一个RDD的分区数决定的
- 如何改变RDD的分区数(改变task的并行度)?
例如:reduceByKey(XXX,3), GroupByKey(4) 宽依赖一般都可以改变分区数。
6.2 pipeline计算模式的验证
package com.shsxt.spark.scala
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Sp_Pipeline {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
conf.setMaster("local").setAppName("pipeline")
val sparkContext = new SparkContext(conf)
val lines: RDD[String] = sparkContext.textFile("./wc.txt")
val rdd2: RDD[String] = lines.map(x=> {
println(x)
x
})
val rdd3 = rdd2.filter(x=>{
println(x+"===============")
true
})
rdd3.collect()
sparkContext.stop()
}
}
执行结果:
可以看出,数据是一条一条处理
七,Spark资源调度和任务调度
-
图例
-
流程描述
1.启动集群后,Worker节点会向Master节点汇报资源情况,Master掌握节点的资源情况。
2.当Spark提交一个Application后,根据RDD之间的依赖关系将Application形成一个DAG有向无环图。
3.任务提交后,Spark会在Driver端创建两个对象:DAGScheduler和TaskScheduler,DAGScheduler是任务调度的高层调度器,主要作用就是将DAG根据RDD之间的宽窄关系划分为一个个的Stage,然后将Stage以TaskSet的形式提交给TaskScheduler(TaskScheduler是任务调度的底层调度器,这里TaskSet其实就是一个集合,里面封装了一个个的task任务,也就是Stage的并行度task任务)
4.TaskScheduler会遍历TaskSet集合,拿到每个Task后将task发送到计算节点Exector中去执行,Task在Exector线程池中的运行情况会汇报给TaskScheduler.
5.当task执行失败时,则由TaskScheduler负责重试,将task重新发送给Executor去执行(不一定是上一个Exector)。默认重试三次,如果任然失败,那么这个task所在的Stage就失败了。
6.Stage失败了则由DAGScheduler负责重试,将TaskSet重新发送给TaskScheduler,Stage默认重试4次。如果任然失败,则认为Stage所在的Job失败,Job失败了,Application就失败了。
7.TaskScheduler不仅能重试失败的task,还会重试stragging(落后,缓慢)的task(执行速度比其他task慢太多的task)。TaskScheduler会启动一个新的task来和这个运行缓慢的task执行相同的处理逻辑。两个task哪个先执行完成,就以哪个task的执行结果为准。
-
最后提到的就是Spark的推测执行机制。在Spark中推测执行机制默认是关闭的,可以通过spark.speculation属性来配置。
-
图详解
7.1 资源申请方式划分
- 粗粒度资源申请(Spark)
Application执行之前,将所有的资源申请完毕,当资源申请成功后,才会进行任务的调度,当所有的task执行完成后,才会释放这部分资源
优点:
在Application执行之前,所有的资源都申请完毕,每一个task直接使用资源就可以了,不需要task在执行前自己去申请资源,task启动就快了,task执行快了,Stage执行就快了,Stage执行快了,Job执行就快了,Application执行也就快了。
缺点:
指到最后一个task执行完毕才回去释放资源,集群的资源无法充分利用
- 细粒度资源申请(MapReduce)
Application执行前不申请资源,而是直接执行,让job中的每一个task在执行前自己去申请资源。task执行完毕后就返回资源
优点:
集群资源可以充分利用
缺点:
task自己去申请资源,task启动变慢,程序的执行运行变慢