@R星校长
Spark 第四天【SparkCore 内容】
主要内容
- Spark Master 启动源码
- Spark Submit 任务提交源码
- Spark Driver 启动源码
- Spark Application 注册并分配资源
- Spark 资源调度结论
- Spark 任务调度
- Spark 二次排序问题
- Spark 分组取 topN 问题
学习目标
第一节 Spark Master 启动
- Spark 资源任务调度对象关系图
- 集群启动过程
Spark 集群启动之后,首先调用 $SPARK_HOME/sbin/start-all.sh,start-all.sh 脚本中调用了 “start-master.sh” 脚本和 “start-slaves.sh” 脚本,在 start-master.sh 脚本中可以看到启动 Master 角色的主类:“org.apache.spark.deploy.master.Master”。在对应的 start-slaves.sh 脚本中又调用了 start-slave.sh 脚本,在 star-slave.sh 脚本中可以看到启动 Worker 角色的主类:“org.apache.spark.deploy.worker.Worker”。
- Master&Worker 启动
Spark 框架的设计思想是每台节点上都会启动对应的 Netty 通信环境,叫做 RpcEnv 通信环境。每个角色启动之前首先向 NettyRpcEnv 环境中注册对应的 Endpoint,然后启动。角色包括:Master, Worker, Driver, Executor 等。下图是启动 start-all 集群后,Master 角色启动过程,Master 角色的启动会调用 “org.apache.spark.deploy.master.Master” 主类,执行流程如下:
第二节 Spark Submit 任务提交
- SparkSubmit 任务提交
Spark submit 提交任务时,调用 $SPARK_HOME/bin/spark-submit spark-submit 脚本中调用了 “org.apache.spark.deploy.SparkSubmit” 类。执行此类时,首先运行 main 方法进行参数设置,然后向 Master 申请启动 Driver。代码流程如下图示: - 启动 DriverWrapper 类
当提交任务之后,客户端向 Master 申请启动 Driver,这里首先会启动一个DriverWrapper 类来对用户提交的 application 进行包装运行,DriverWrapper 类的启动过程如下: - 注册 Driver Endpoint,向 Master 注册 Application
当执行用户的代码时,在 new SparkContext 时,会注册真正的 Driver 角色,这个角色名称为 “CoarseGrainedScheduler” , Driver 角色注册之后,注册 “AppClient” 角色,由当前这个角色向 Master 注册 Application。代码流程如下:
第三节 Spark资源调度源码
-
Spark 资源调度源码过程
Spark 资源调度源码是在 Driver 启动之后注册 Application 完成后开始的。Spark 资源调度主要就是 Spark 集群如何给当前提交的 Spark application 在 Worker 资源节点上划分资源。Spark 资源调度源码在 Master.scala 类中的 schedule() 中进行的。 -
Spark 资源调度源码结论
1) Executor 在集群中分散启动,有利于 task 计算的数据本地化。
2) 默认情况下(提交任务的时候没有设置 --executor-cores 选项),每一个 Worker 为当前的 Application 启动一个 Executor , 这个 Executor 会使用这个 Worker 的所有的 cores 和 1G 内存。
3) 如果想在 Worker 上启动多个 Executor,提交 Application 的时候要加 --executor-cores 这个选项。
4) 默认情况下没有设置 --total-executor-cores , 一个 Application 会使用 Spark 集群中所有的 cores。
5) 启动 Executor 不仅和 core 有关还和内存有关。 -
资源调度源码结论验证
使用 Spark-submit 提交任务演示。也可以使用 spark-shell 来验证。
1) 默认情况每个 worker 为当前的 Application 启动一个 Executor,这个 Executor 使用集群中所有的 cores 和 1G 内存。
./spark-submit
--master spark://node1:7077
--class org.apache.spark.examples.SparkPi
../lib/spark-examples-1.6.0-hadoop2.6.0.jar
10000
2) 在 workr 上启动多个 Executor , 设置 --executor-cores 参数指定每个 executor 使用的 core 数量。
./spark-submit
--master spark://node1:7077
--executor-cores 1
--class org.apache.spark.examples.SparkPi
../lib/spark-examples-1.6.0-hadoop2.6.0.jar
10000
3) 内存不足的情况下启动 core 的情况。Spark 启动是不仅看 core 配置参数,也要看配置的 core 的内存是否够用。
./spark-submit
--master spark://node1:7077
--executor-cores 1
--executor-memory 3g
--class org.apache.spark.examples.SparkPi
../lib/spark-examples-1.6.0-hadoop2.6.0.jar
10000
–total-executor-cores 集群中共使用多少 cores
注意:一个进程不能让集群多个节点共同启动。
./spark-submit
--master spark://node1:7077
--executor-cores 1
--executor-memory 2g
--total-executor-cores 3
--class org.apache.spark.examples.SparkPi
../lib/spark-examples-1.6.0-hadoop2.6.0.jar
10000
第四节 Spark 任务调度源码
Spark 任务调度源码是从 Spark Application 的一个 Action 算子开始的。action 算子开始执行,会调用 RDD 的一系列触发 job 的逻辑。其中也有 stage 的划分过程:
第五节 Spark 二次排序和分组取 topN
- 二次排序
大数据中很多排序场景是需要先根据一列进行排序,如果当前列数据相同,再对其他某列进行排序的场景,这就是二次排序场景。例如:要找出网站活跃的前 10 名用户,活跃用户的评测标准就是用户在当前季度中登录网站的天数最多,如果某些用户在当前季度登录网站的天数相同,那么再比较这些用户的当前登录网站的时长进行排序,找出活跃用户。这就是一个典型的二次排序场景。
解决二次排序问题可以采用封装对象的方式,对象中实现对应的比较方法。
1. SparkConf sparkConf = new SparkConf()
2. .setMaster("local")
3. .setAppName("SecondarySortTest");
4. final JavaSparkContext sc = new JavaSparkContext(sparkConf);
5.
6. JavaRDD<String> secondRDD = sc.textFile("secondSort.txt");
7.
8. JavaPairRDD<SecondSortKey, String> pairSecondRDD = secondRDD.mapToPair(new PairFunction<String, SecondSortKey, String>() {
9.
10. /**
11. *
12. */
13. private static final long serialVersionUID = 1L;
14.
15. @Override
16. public Tuple2<SecondSortKey, String> call(String line) throws Exception {
17. String[] splited = line.split(" ");
18. int first = Integer.valueOf(splited[0]);
19. int second = Integer.valueOf(splited[1]);
20. SecondSortKey secondSortKey = new SecondSortKey(first,second);
21. return new Tuple2<SecondSortKey, String>(secondSortKey,line);
22. }
23. });
24.
25. pairSecondRDD.sortByKey(false).foreach(new
26. VoidFunction<Tuple2<SecondSortKey,String>>() {
27.
28. /**
29. *
30. */
31. private static final long serialVersionUID = 1L;
32.
33. @Override
34. public void call(Tuple2<SecondSortKey, String> tuple) throws Exception {
35. System.out.println(tuple._2);
36. }
37. });
38.
39.
40.
41. public class SecondSortKey implements Serializable,Comparable<SecondSortKey>{
42. /**
43. *
44. */
45. private static final long serialVersionUID = 1L;
46. private int first;
47. private int second;
48. public int getFirst() {
49. return first;
50. }
51. public void setFirst(int first) {
52. this.first = first;
53. }
54. public int getSecond() {
55. return second;
56. }
57. public void setSecond(int second) {
58. this.second = second;
59. }
60. public SecondSortKey(int first, int second) {
61. super();
62. this.first = first;
63. this.second = second;
64. }
65. @Override
66. public int compareTo(SecondSortKey o1) {
67. if(getFirst() - o1.getFirst() ==0 ){
68. return getSecond() - o1.getSecond();
69. }else{
70. return getFirst() - o1.getFirst();
71. }
72. }
73. }
- 分组取 topN
大数据中按照某个 Key 进行分组,找出每个组内数据的 topN 时,这种情况就是分组取 topN 问题。
解决分组取 TopN 问题有两种方式,第一种就是直接分组,对分组内的数据进行排序处理。第二种方式就是直接使用定长数组的方式解决分组取 topN 问题。
1. SparkConf conf = new SparkConf()
2. .setMaster("local")
3. .setAppName("TopOps");
4. JavaSparkContext sc = new JavaSparkContext(conf);
5. JavaRDD<String> linesRDD = sc.textFile("scores.txt");
6.
7. JavaPairRDD<String, Integer> pairRDD = linesRDD.mapToPair(new PairFunction<String, String, Integer>() {
8.
9. /**
10. *
11. */
12. private static final long serialVersionUID = 1L;
13.
14. @Override
15. public Tuple2<String, Integer> call(String str) throws Exception {
16. String[] splited = str.split("\t");
17. String clazzName = splited[0];
18. Integer score = Integer.valueOf(splited[1]);
19. return new Tuple2<String, Integer> (clazzName,score);
20. }
21. });
22.
23. pairRDD.groupByKey().foreach(new
24. VoidFunction<Tuple2<String,Iterable<Integer>>>() {
25.
26. /**
27. *
28. */
29. private static final long serialVersionUID = 1L;
30.
31. @Override
32. public void call(Tuple2<String, Iterable<Integer>> tuple) throws Exception {
33. String clazzName = tuple._1;
34. Iterator<Integer> iterator = tuple._2.iterator();
35.
36. Integer[] top3 = new Integer[3];
37.
38. while (iterator.hasNext()) {
39. Integer score = iterator.next();
40.
41. for (int i = 0; i < top3.length; i++) {
42. if(top3[i] == null){
43. top3[i] = score;
44. break;
45. }else if(score > top3[i]){
46. for (int j = 2; j > i; j--) {
47. top3[j] = top3[j-1];
48. }
49. top3[i] = score;
50. break;
51. }
52. }
53. }
54. System.out.println("class Name:"+clazzName);
55. for(Integer sscore : top3){
56. System.out.println(sscore);
57. }
58. }
59. });
本节作业
- Spark 底层代码设计思想,以 Master 为例介绍?
- Spark 资源调度源码流程?
- Spark 资源调度源码结论?
- 二次排序和分组取 topN 问题代码。