Spark:基本工作原理与RDD

Spark的基本工作原理

我们从宏观讲解Spark的基本工作原理,帮助你全面了解布局
在这里插入图片描述

1、客户端:
客户端也就是专业们常说的Client端,这里的是表示我们在本地编写Spark程序,然后必须找一个能够连接Spark集群,并提交程序进行运行的机器

2、读取数据:
在准备运行Spark程序的同时,是不是也要有数据来源进行处理的呢,这里我们介绍几种常见的读取数据来源,是Hadoop集群中的HDFS、Hive也有可能是搭建在集群上的HBase;还有MySQL等DB数据库;或者是在程序中我们设置的集合数据。

3、Spark分布式集群
Spark集群是一种分布式计算、是一种迭代式计算、是一种基于内存计算。

分布式计算,这是Spark最基本的特征,计算时候数据会分布存放到各个集群节点,来并行分布式计算。如图的第一个操作map,是对于节点1、2、3上面的数据进行map算子操作,处理后的数据可能会转移到其他节点的内存中,这里假设到了4、5、6节点,处理后的数据有可能多或是变少,这个需要看我们具体的处理方式。第二个操作reduce,是将map处理后的数据再次进行处理。

这也就得到Spark是一种迭代式计算模型,一次计算逻辑中可以分为N个阶段,上一个阶段结果数据成为了下一个阶段的输入数据,这样就不只是想mapreduce计算一样了,只有两个阶段map和reduce,就结束一个job的运行,必须得落地到HDFS。而Spark在各个阶段计算转换中一直保持基于内存迭代式计算,所以Spark相对于MapReduce来说计算模型可以提供更加强大的计算逻辑功能。

4、结果数据输出:
基于Hadoop的HDFS、Hive或是HBase;MySQL等DB数据;或是直接输出返回给客户端。

RDD特征概要总结

RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变、可分区、里面的元素可并行计算的集合。

  1. RDD在抽象上来说是一种元素集合,包含了数据。它是被分区的,分为多个分区,每个分区分布在集群中的不同节点上,从而让RDD中的数据可以被并行操作。(分布式数据集)
  2. RDD通常通过Hadoop上的文件,即HDFS文件或者Hive表,来进行创建;有时也可以通过应用程序中的集合来创建。
  3. RDD最重要的特性就是,提供了容错性,可以自动从节点失败中恢复过来。即如果某个节点上的RDD partition,因为节点故障,导致数据丢了,那么RDD会自动通过自己的数据来源重新计算该partition。这一切对使用者是透明的。
  4. RDD的数据默认情况下存放在内存中的,但是在内存资源不足时,Spark会自动将RDD数据写入磁盘。(弹性)

在这里插入图片描述

Spark的核心编程

  1. 定义初始的RDD,就是说,你要定义第一个RDD是从哪里,读取数据,hdfs、linux本地文件、程序中的集合。
  2. 定义对RDD的计算操作,这个在spark里称之为算子,map、reduce、flatMap、groupByKey,比mapreduce提供的map和reduce强大的太多太多了。
  3. 其实就是循环往复的过程,第一个计算完了以后,数据可能就会到了新的一批节点上,也就是变成一个新的RDD。然后再次反复,针对新的RDD定义计算操作
  4. 最后,就是获得最终的数据,将数据保存起来。

使用Java开发wordcount程序

  1. 创建SparkConf对象,设置Spark应用的配置信息
    使用setMaster()可以设置Spark应用程序要连接的Spark集群的master节点的url
SparkConf conf = new SparkConf()
				.setAppName("WordCountLocal")
				.setMaster("local"); //local代表,在本地运行
  1. 创建JavaSparkContext对象
    在Spark中,SparkContext是Spark所有功能的一个入口,你无论是用java、scala,甚至是python编写都必须要有一个SparkContext,它的主要作用,包括初始化Spark应用程序所需的一些核心组件,包括调度器(DAGSchedule、TaskScheduler),还会去到Spark Master节点上进行注册,等等
JavaSparkContext sc = new JavaSparkContext(conf);
  1. 要针对输入源(hdfs文件、本地文件,等等),创建一个初始的RDD
    输入源中的数据会打散,分配到RDD的每个partition中,从而形成一个初始的分布式的数据集
JavaRDD<String> lines = sc.textFile("hdfs://spark1:9000/spark.txt");
  1. 对初始RDD进行transformation操作,也就是一些计算操作
    通常操作会通过创建function,并配合RDD的map、flatMap等算子来执行
JavaRDD<String> words = lines.flatMap(new FlatMapFunction<String, String>() {

			private static final long serialVersionUID = 5859923875545688649L;

			@Override
			public Iterable<String> call(String line) throws Exception {
				return Arrays.asList(line.split(" "));
			}
		});
  1. 接着,需要将每一个单词,映射为(单词, 1)的这种格式
    mapToPair,其实就是将每个元素,映射为一个(v1,v2)这样的Tuple2类型的元素mapToPair,其实就是将每个元素,映射为一个(v1,v2)这样的Tuple2类型的元素
JavaPairRDD<String, Integer> pairs = words.mapToPair(new PairFunction<String, String, Integer>() {

			private static final long serialVersionUID = 5925817248239252608L;

			@Override
			public Tuple2<String, Integer> call(String t) throws Exception {
				return new Tuple2<String, Integer>(t, 1);
			}
		});
  1. 需要以单词作为key,统计每个单词出现的次数
    使用reduceByKey这个算子,对每个key对应的value,都进行reduce操作,reduce操作,相当于是把第一个值和第二个值进行计算,然后再将结果与第三个值进行计算。
JavaPairRDD<String, Integer> wordCounts = pairs.reduceByKey(new Function2<Integer, Integer, Integer>() {

			private static final long serialVersionUID = 8794397384148863606L;

			@Override
			public Integer call(Integer v1, Integer v2) throws Exception {
				return v1 + v2;
			}
		});
  1. 到这里为止,我们通过几个Spark算子操作,已经统计出了单词的次数
    之前我们使用的flatMap、mapToPair、reduceByKey这种操作,都叫做transformation操作,一个Spark应用中,光是有transformation操作,是不行的,是不会执行的,必须要有一种叫做action。
wordCounts.foreach(new VoidFunction<Tuple2<String, Integer>>() {

			private static final long serialVersionUID = 2448389534049516517L;

			@Override
			public void call(Tuple2<String, Integer> wordCount) throws Exception {
				System.out.println(wordCount._1 + " appeared " + wordCount._2 + " times.");
			}
		});

8.释放资源

sc.close();

9.将文件上传到hdfs上去

hadoop fs -put spark.txt /spark.txt

10.使用spark-submit提交到spark集群进行执行

  • 将SparkConf的setMaster()方法给删掉,默认它自己会去连接
  • pom.xml里配置的maven插件,对工程进行打包
    在这里插入图片描述
  • 编写spark-submit脚本
  /usr/local/spark/bin/spark-submit \
  --class cn.spark.sparktest.core.WordCountCluster \
  --num-executors 3 \
  --driver-memory 100m \
  --executor-memory 100m \
  --executor-cores 3 \
  /usr/local/spark-study-java-0.0.1-SNAPSHOT-jar-with-dependencies \
  • 执行spark-submit脚本,提交spark应用到集群执行

使用Scala开发wordcount程序

import org.apache.spark.SparkConf
import org.apache.spark.SparkContext

object WordCount {

  def main(args: Array[String]): Unit = {
   
    val conf = new SparkConf().setAppName("WordCount")

    val sc = new SparkContext(conf)

    val lines = sc.textFile("hdfs://spark1:9000/spark.txt", 1)

    val words = lines.flatMap(line => line.split(" "))

    val pairs = words.map(word => (word, 1))

    val wordCounts = pairs.reduceByKey(_ + _)

    wordCounts.foreach(wordCount => println(wordCount._1 + wordCount._2))

  }

}

wordcount程序原理深度剖析

在这里插入图片描述

Spark架构原理

  • Client:客户端进程,负责提交作业到Master。
  • Master:主控节点,负责接收Client提交的作业,管理Worker,并命令Worker启动Driver和Executor
  • Worker:进程,slave节点上的守护进程,负责管理本节点的资源,定期向Master汇报心跳,接收Master的命令,启动Driver和Executor。用自己内存,存储RDD的某个或某些partition
  • Driver:进程,一个Spark作业运行时包括一个Driver进程,也是作业的主进程,负责作业的解析、生成Stage并调度Task到Executor上。包括DAGScheduler,TaskScheduler。
  • Executor:进程,即真正执行作业的地方,一个集群一般包含多个Executor,每个Executor接收Driver的命令Launch Task,一个Executor可以执行一到多个Task。
  • Task:线程,对RDD的partition数据执行指定的算子操作,形成新的RDD的partition。

在这里插入图片描述

  1. 启动Spark集群,其实就是通过运行spark-all.sh脚本来启动master节点和worker节点,启动了一个个对应的master进程和worker进程
  2. worker启动之后,向master进程发送注册信息(该过程基于AKKA Actor事件驱动模型);
  3. worker向master注册成功之后,会不断向master发送心跳包,监听master节点是否存活(该过程基于AKKA Actor事件驱动模型)
  4. driver向Spark集群提交作业,通过spark-submit.sh脚本,向master节点申请资源(该过程基于AKKA Actor事件驱动模型)
  5. master收到Driver提交的作业请求之后,向worker节点指派任务,其实就是让其启动对应的executor进程
  6. worker节点收到master节点发来的启动executor进程任务,就启动对应的executor进程,同时向master汇报启动成功,处于可以接收任务的状态
  7. 当Executor进程启动成功后,就像Driver进程反向注册,以此来告诉driver,谁可以接收任务,执行spark作业(该过程基于AKKA Actor事件驱动模型)
  8. driver接收到注册之后,就知道了向谁发送spark作业,这样在spark集群中就有一组独立的Executor进程为该driver服务
  9. SparkContext重要组件运行——DAGScheduler和TaskScheduler,DAGScheduler根据宽依赖将作业划分为若干stage,并为每一个阶段组装一批task组成taskset(task里面就包含了序列化之后的我们编写的spark transformation);然后将taskset交给TaskScheduler,由其将任务分发给对应的Executor
  10. Executor进程接收到driver发送过来的taskset,进行反序列化,然后将这些task封装进一个叫taskrunner的线程中,放到本地线程池中,调度我们的作业的执行
  11. Executor接收到task之后,会启动多个线程来执行task
  12. task就会对RDD的partiton数据执行指定的算子操作,形成新的RDD的partition

猜你喜欢

转载自blog.csdn.net/jiaojiao521765146514/article/details/84848186