13.sparkCore知识点

一、scala复习

1. scala的简介
	scala是一个面向对象,函数式的编程语言,运行在jvm上,可以调用java,c++,python等的api。追求优雅,简单。
	我们学习的是2.11.8的版本。这周要学习的spark2.2.3版本是基于scala-2.11.8的
2. scala的安装(和安装jdk是一样的)
	- windows平台:
		(1)可以下载xxx.msi或者是xxx.zip包进行安装。
		(2)然后配置环境变量:SCALA_HOME和PATH
	- linux平台
		(1)可以下载xxx.tgz包,进行解压即可,然后配置SCALA_HOME和PATH
	- IDEA与SCALA进行整合
	  注意:IDEA与windows上的SCALA环境进行整合时,IDEA需要安装SCALA插件(接口)
3. scala的基础(重点)
	- 编程规范
	- 类的层次结构
	- 变量的声明语法: val 和 var   官方建议使用val。
		    1) 变量声明时必须初始化
	- 常用的类型:
    	1)四个整型:
    	2)两个浮点型:
    	3)一个布尔类型
    	4)一个字符类型
    	5)字符串类型
    	6)Unit
    	7)Option类型:返回可能有的值(Some(值)),或者是无值None。
    - 运算符:
    - 分支结构:if     与java的if没有任何不同  
    - 循环结构: 
    	1)while   和 do-while   与java的没有任何不同  
    	2)for :相当于java的增强for循环
    		语法:  for(变量名 <- Range|数组|集合|元祖|表达式){
    			
    			   }
    		注意:元祖的情况,必须是两个元素,第一个元素是截止数字,第二个是步长
    		     表达式,比如最普通的字符串。
    - 常用的类型转换:
      1)自动转换也叫隐式转换。 小范围向大范围类型赋值的时候
      2)强制类型转换。大范围向小范围类型赋值的时候。 调用方法,比如toInt,toByte,toShort等
4. scala的方法和函数(重点)
   - 方法的定义
   	   def 方法名(参数类型列表):返回值类型={}
   	   def 方法名(参数类型列表)={}   省略返回值类型
   	   def 方法名(参数类型列表){}	省略等号时,必须添加花括号
   	   
   	   注意:无参数列表的情况,定义期间可以加()也可以不加(),调用期间要和定义时一致
   - 函数的定义
    	1) 匿名内部类的写法:
          val|var 函数名 = new FunctionN(类型列表){

          }
        2) 等号的写法(别写返回值类型,会自己推断) 
   	   		val 函数名 =((参数列表)=>函数体)
	  		val 函数名 =(参数列表)=>函数体
	  		val 函数名 =(_:类型) 运算符 (_:类型) 
	  	3) 冒号的写法	
      		val 函数名: 参数列表(指定形参类型=>返回值类型)= 函数体(_ 运算符 _)
      		val 函数名: 参数列表(指定形参类型=>返回值类型)={参数名称列表=>函数体}
      		val 函数名: 参数列表(指定形参类型=>返回值类型)= 参数名称列表=>函数体 
        4)无参数列表的情况
            定义时,小括号不能省略,
            调用时:
            	带上小括号是执行逻辑,
            	不带小括号不执行逻辑,显示定义结构    	
    - 方法转函数:神奇的下划线
       方法名 _
       方法名(_[,....])
    - 匿名函数:
    	  不带val|var 函数名的定义的函数,就是匿名函数
    	  通常用于作为形参传入到函数体或者是方法体中
    - 函数在scala中是一等公民,就是相当于java中的对象,可以在任何地方使用。
    - 自定义函数的本质其实就是实现23个FunctionN特质
5. 集合框架(重点)
	- 所有的集合类型,都在两个包下,
		1) 一个是scala.collection.immutable, 是不可变的集合
		2)一个是scala.collection.mutable, 是可变的集合
	- 常用的集合类型,数组,列表,Set,Map,Seq,元祖	
	- Array:
    	1)定长数组:Array,  长度不可变,元素可变。 可以变相的添加删除元素,只不过返回新Array对象
    	2)变长数组: ArrayBuffer,长度可变,原来的元素可变, 返回的都是自己
    - List:有序
    	1) 不可变List: 长度不变,元素不可变。可以变相的做增减元素的操作,返回的是新对象
    	2) 可变List:长度可变,元素可变	返回的都是自己
    - Set: 无序
    	1)不可变Set :  有序,不重复, 长度不可变,元素不可变。可以变相的做增减元素的操作,返回的是新对象
    	2)可变Set:   无序,不重复,长度可变,原来的元素不可变,可以新添加或删除元素 ,返回自己
    - tuple: 可以存不同类型的元素,最多存22个元素
        1)访问元祖元素时:_N。
        2)如何定义
           val a = (元素1[,.......])
           val b = new TupleN(元素1[,.......])
6. 类的体系(重点)
	- class: 
		 1)语法:  
		 class className(主构造器参数列表){
		   	变量的声明和初始化
		    this(辅助构造器参数列表) //可以有N个
		    普通方法
		    函数
		 }
	- 抽象类:
	 	abstract class className(主构造器参数列表){
		     普通字段
		     抽象字段
		     普通方法
		     抽象方法
		     辅助构造器
		 }
	- trait
		traint className[泛型]{
		     普通字段
		     抽象字段
		     普通方法
		     抽象方法
		     辅助构造器
		 }
	- object
		- 特殊的类,只有一个对象,调用时使用名称.调用
		- 与在同一个源文件的其他class名相同时,就是伴生对象,可以提供apply方法,方便创建伴生类的实例
	- 样例类
		使用case 修饰的class,就是样例类。 系统会默认提供一个伴生对象,同时提供相应参数的apply方法,
		unapply方法,还有toString,hashCode,equals等
		通常样例类:用于模式匹配。是unapply方法在起作用
7. 模式匹配(重点)
	-,在scala中没有java的switch-case,而是一个功能更加强大的 模式匹配。
		语法:
		val a = 变量名 match {
			case 值 =>
			case 数组对象
			case 集合对象
			case 元祖对象
			case 类型
		}
	- 偏函数:
    	没有match关键字的一堆case语句所在的函数就是偏函数。
    	怎么调用偏函数进行case匹配
    	.apply()方法
8. 高阶函数(闭包函数和柯里化函数)(重点理解)
	- 什么是高阶函数:参数是一个方法或者是一个函数。
	- 闭包函数:函数体内使用了形参列表中没有的变量,而是外部的表
	- 柯里化函数:多参数列表的函数就是柯里化函数。底层原理转成多个普通函数进行计算。
	- 一般情况下多参数列表用于将形参分类,比如第一个列表里是普通形参,第二个列表里是一个函数
9. 隐式转换和隐式参数(重点理解,主要用来看源码)
	- 方便程序员的调用。
	- 隐式函数:
		 使用implicit修饰的函数就是隐式函数,可以进行类型转换
		 当开发人员在调用一个对象的方法或者做一些语法看似不正确的逻辑时。编译器此时不会立马报错,而是在此
		 作用域里查询是否有隐式函数可以帮助实现此逻辑。不需要程序员自己调用
	- 隐式类:通常用于做类型转换。使用implicit修饰的class
	- 隐式参数: 参数列表的形参用于implicit修饰的, 主构造器或是方法的形参列表中隐式参数只能在最后面
10.scala的泛型: 用于限定类名,方法的形参是用于限定类型的值或实例的(重点理解,看懂源码)
	- 协变: [+A]   如果A是B的父类,那么List[A]就是List[B]的父类
	- 逆变   [-A]   如果A是B的父类,那么List[A]就是List[B]的子类
	- 上界  [A <: B]  传入的真正的类型只能是B以下的子类型,A以上的父类型
	- 下界  [A >: B]  传入的真正的类型只能是A以下的子类型,B以上的父类型
11.scala的通信机制(了解)
    - scala的多线程机制是Actor
    - scala低版本的通信机制是AKKA,AKKA是基于Actor多线程
    - scala高版本的通信机制是Netty,Netty比AKKA在分布式集群中的通信性能更好。

二、Spark的简介

2.1 spark的简介

1. spark 是一个快速的,通用的,计算大数据的一个分析处理框架
2. spark 使用scala编写
3. spark 采用了先进的DAG执行引擎, 支持循环数据流和内存计算,所以速度快
4. spark 被设计用来做批处理、迭代运算、交互式查询、流处理、机器学习等
5. 可以运行Scala、Java、Python、R等开发的分布式应用程序
6. 集成了多种数据源,并且可以通过local、Yarn、Mesos、Standalone(Spark提供的部署方式)等各种模式运行。

2.2 spark与hadoop的比较

1、spark把运算的中间数据存放在内存,迭代计算效率更高;mapreduce的中间结果需要落地,需要保存到磁盘,这样必然会有磁盘io操做,影响性能。

2、spark容错性高,它通过弹性分布式数据集RDD来实现高效容错,RDD是一组分布式的存储在节点内存中的只读性质的数据集,这些集合是弹性的,某一部分丢失或者出错,可以通过整个数据集的计算流程的血缘关系来实现重建;mapreduce的话容错可能只能重新计算了,成本较高。

3、spark更加通用,spark提供了transformation和action这两大类的多个功能api,另外还有流式处理sparkstreaming模块、图计算GraphX等等;mapreduce只提供了map和reduce两种操作,流计算以及其他模块的支持比较缺乏。

4、spark框架和生态更为复杂,首先有RDD、血缘lineage、执行时的有向无环图DAG、stage划分等等,很多时候spark作业都需要根据不同业务场景的需要进行调优已达到性能要求;mapreduce框架及其生态相对较为简单,对性能的要求也相对较弱,但是运行较为稳定,适合长期后台运行。

2.3 spark的组件

MR只有两个Map和Reduce,而相对的spark的组件比较复杂,

1. spark core
	实现了 Spark 的基本功能,包含任务调度、内存管理、错误恢复、与存储系统交互等模块。
	Spark Core 中还包含了对弹性分布式数据集(resilient distributed dataset,简称RDD)的API定义。 
2. spark sql
	是 Spark 用来操作结构化数据的程序包。通过 Spark SQL,我们可以使用 SQL 或者 Apache Hive版本
	的 SQL方言(HQL)来查询数据。Spark SQL 支持多种数据源,比 如 Hive 表、Parquet 以及 JSON 等。 
3. spark streaming
	是 Spark 提供的对实时数据进行流式计算的组件。提供了用来操作数据流的 API,并且与 Spark Core 
	中的 RDD API 高度对应。
4. spark MLlib
	提供常见的机器学习(ML)功能的程序库。包括分类、回归、聚类、协同过滤等,还提供了模型评估、数据 导入等额外的支持功能。
5. spark GRAPHX
	GraphX在Spark基础上提供了一站式的数据解决方案,可以高效地完成图计算的完整流水作业。GraphX是用于图计
	算和并行图计算的新的(alpha)Spark API。通过引入弹性分布式属性图(Resilient Distributed 
	Property 	Graph),一种顶点和边都带有属性的有向多重图,扩展了Spark RDD。

2.4 spark的特点(参考官网)

1. 快速
	基于内存的数据流处理,速度快
2. 易用
	可以使用各种编程语言,比如java,scala,c++,python,r等写的应用程序可以运行在spark上。
3. 通用
	提供了统一的解决方案(计算模型)
        - 批处理、
        - 交互式查询(Spark SQL)、
        - 实时流处理(Spark Streaming)、
        - 机器学习(Spark MLlib)
        - 图计算(GraphX)
4. 兼容
	spark可以非常方便的与hadoop的yarn进行整合使用。

三、Spark的配置

3.1 Spark的部署方式

1)介绍

1. Amazon EC2: 
2. Standalone Deploy Mode: 此模式指的是spark使用内置的资源调度工具。部署的节点可以是单机的,也可以使集群的  : 学习期间使用。
3. Mesos: spark可以使用apache mesos作为调度工具
4. YARN:  spark可以使用hadoop的 yarn作为调度工具
5. Kubernetes (experimental): 使用k8s作为spark的调度工具,大企业中的用的比较多
6. local:指的就是解压,配置完环境变量后,做spark程序的测试。单节点

2)准备工作

1. 三台虚拟机,qianfeng01,qianfeng02,qianfeng03
2. 布局 
	master:qianfeng01
	worker:qianfeng02,qianfeng03
3. 免密登录,时间同步,安装好JDK1.8。
4. 下载spark-2.2.3.tar.gz
	历史版本的地址:https://archive.apache.org/dist/spark/

3.2 standlone模式的搭建

3.2.1 说明

1. standlone模式可以是单节点,也可以是集群的
2. 准备条件:免密登录,时间同步,关闭防火墙,安装好JDK1.8。

3.2.2 搭建步骤

1)上传,解压,更名

[root@qianfeng01 ~]# tar -zxvf spark-2.2.3-bin-hadoop2.7.tgz -C /usr/local/ && cd /usr/local
[root@qianfeng01 local]# mv spark-2.2.3-bin-hadoop2.7/ spark

2)配置环境

[root@qianfeng01 local]# vim /etc/profile
.........省略.........
# spark  environment
export SPARK_HOME=/usr/local/spark
export PATH=$SPARK_HOME/sbin:$SPARK_HOME/bin:$PATH

[root@qianfeng01 local]# source /etc/profile


注意:因为spark和hadoop的一些脚本重名,比如start-all.sh  stop-all.sh。因此要注意你的PATH环境变量的值配置的顺序。

因为我的spark的环境变量写在了hadoop的环境变量的前面,因此默认使用的是spark的脚本。如果想要启动hadoop。可以写绝对路径或者是相对路径
/usr/local/hadoop/sbin/start-all.sh
/usr/local/hadoop/sbin/stop-all.sh

3)配置spark的环境脚本

[root@qianfeng01 spark]# cd conf
[root@qianfeng01 conf]# cp spark-env.sh.template spark-env.sh
在最底下添加如下内容
export JAVA_HOME=/usr/local/jdk   			<== java环境
export SPARK_MASTER_HOST=qianfeng01			<== master的主机名或者是IP地址,后缀是什么就写什么
export SPARK_MASTER_PORT=7077

4)修改slaves文件

[root@qianfeng01 conf]# cp slaves.template slaves
[root@qianfeng01 conf]# vim slaves
删除localhost,添加从节点的主机名,如下
qianfeng02
qianfeng03

5)分发到其他两个节点

[root@qianfeng01 conf]# scp -r /usr/local/spark qianfeng02:/usr/local
[root@qianfeng01 conf]# scp -r /usr/local/spark qianfeng03:/usr/local

别忘记/etc/profile
[root@qianfeng01 conf]# scp /etc/profile qianfeng02:/etc/
[root@qianfeng01 conf]# scp /etc/profile qianfeng03:/etc/

6)启动

在master节点上,运行spark的sbin目录下的start-all.sh

[root@qianfeng01 spark]# start-all.sh    <==配置了环境变量的写法。注意和hadoop的重名问题
[root@qianfeng01 spark]# ./sbin/start-all.sh

7)webui

启动后,会启动一个webui的端口8080。 注意:7077是worker和master进程通信的端口

http://qianfeng01:8080

3.2.3 standlone的集群模型

1. master进程	:   管理整个集群,分配资源
2. worker进程: 真正执行spark程序的节点    会与master进行心跳反馈。 
3. driver:     spark程序的启动的位置,也可以称之client。 位置可以在集群外,也可以在master上
               driver内部包含了sparkcontext组件。
4. executor组件:位于worker节点。作用是执行spark的task.               

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ilp6b0xs-1615727554189)(./ClassNotes.assets/20190228095232981.jpg)]

3.2.4 配置Job History Server

因为spark程序在运行时,driver会提供一个webui端口来记录实时的运行信息。但是一旦程序完成,端口就会被关闭。所以造成查看不了历史记录。我们可以配置Job History Server来解决这个问题。

1)在hdfs上创建一个永久的目录

[root@qianfeng01 logs]# start-dfs.sh
	创建sparkJobHistory目录
[root@qianfeng01 logs]# hdfs dfs -mkdir /sparkJobHistory

2)配置spark-default.conf

[root@qianfeng01 conf]# cp spark-defaults.conf.template spark-defaults.conf
[root@qianfeng01 conf]# vim spark-defaults.conf
在文件的末尾添加
spark.eventLog.enabled      true    开启
spark.eventLog.dir          hdfs://qianfeng01:8020/sparkJobHistory  存储路径
spark.eventLog.compress     true    开启压缩

参数描述:

spark.eventLog.dir:Application在运行过程中所有的信息均记录在该属性指定的路径下
spark.eventLog.compress 这个参数设置history-server产生的日志文件是否使用压缩,true为使用,false为不使用。这个参数务可以成压缩哦,不然日志文件岁时间积累会过

3)修改spark-env.sh文件,添加如下配置

export SPARK_HISTORY_OPTS="-Dspark.history.ui.port=4000 -Dspark.history.retainedApplications=10 -Dspark.history.fs.logDirectory=hdfs://qianfeng01:8020/sparkJobHistory"

解析如下:

spark.history.ui.port=4000  调整WEBUI访问的端口号为4000
spark.history.fs.logDirectory=hdfs://hadoop01:8020/directory  配置了该属性后,在start-history-server.sh时就无需再显式的指定路径,Spark History Server页面只展示该指定路径下的信息
spark.history.retainedApplications=10   指定保存Application历史记录的个数,如果超过这个值,旧的应用程序信息将被删除,这个是内存中的应用数,而不是页面上显示的应用数。

4). 配置完成后分发文件到相应节点

scp -r ./spark-env.sh ./spark-defaults.conf root@qianfeng02:$PWD
scp -r ./spark-env.sh ./spark-defaults.conf root@qianfeng03:$PWD

5)启动的时候是

注意:最好不要是用IE内核的浏览器不然效果是显示不出来的

 start-history-server.sh 
 
 然后可以访问webui
 http://192.168.10.101:4000/

6)历史服务器组件的作用

作用是用于保存spark程序产生的日志信息。可以供开发人员查看。这个组件与spark程序的运行模式无关。那种模式都可以使用这个历史服务器组件

2.2.5 Spark高可用(选做)

1.那就是Master节点存在单点故障,要解决此问题,就要借助zookeeper,并且启动至少两个Master节点来实现高可靠,
2. 配置方式比较简单:
Spark集群规划:hadoop01,hadoop03是Master;hadoop02,hadoop03是Worker
3. 安装配置zk集群,并启动zk集群
4. 停止spark所有服务,修改配置文件spark-env.sh,在该配置文件中删掉SPARK_MASTER_IP(HOST)并添加如下配置
export SPARK_DAEMON_JAVA_OPTS="-Dspark.deploy.recoveryMode=ZOOKEEPER -Dspark.deploy.zookeeper.url=qianfeng01,qianfeng02,qianfeng03 -Dspark.deploy.zookeeper.dir=/spark"

5. 分发到qianfeng02,qianfeng03上
6. 启动过程

	- 先启动zookeeper集群
	- 在qianfeng01上执行sbin/start-all.sh脚本,
	- 然后在hadoop03上执行sbin/start-master.sh启动第二个Master

ps:若使用spark-shell启动集群需要添加配置
spark-shell --master spark://qianfeng01:7077,qianfeng03:7077

3.3 YARN模式的搭建

3.3.1 配置

1)修改hadoop的yarn-site.xml

这两项判断是否启动一个线程检查每个任务正使用的物理内存量/虚拟内存量,如果任务超出分配值,则直接将其杀掉,默认是true 如果不配置这两个选项,在spark-on-yarn的client模式下,可能会报错,导致程序被终止。

在文件中增加以下两个属性:

<property>
    <name>yarn.nodemanager.pmem-check-enabled</name>
    <value>false</value>
</property>
<property>
    <name>yarn.nodemanager.vmem-check-enabled</name>
    <value>false</value>
</property>

别忘记同步

2)修改spark-env.sh

添加:

export HADOOP_CONF_DIR=/usr/local/hadoop/etc/hadoop
export YARN_CONF_DIR=/usr/local/hadoop/etc/hadoop

别忘记同步

3.3.2 运行模式之yarn client

使用yarn client模式提交一个计算pi值的程序,运算100次。

spark-submit --class org.apache.spark.examples.SparkPi \
    --master yarn \
    --deploy-mode client \
    --driver-memory 2g \
    --executor-memory 2g \
    --executor-cores 2 \
    /usr/local/spark/examples/jars/spark-examples_2.11-2.2.3.jar  \
    100 

执行原理:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xbODc7Es-1615727554194)(…/%25E6%2596%2587%25E6%25A1%25A3/sparkcore.assets/400827-20171206174933253-682120820.png)]

- Spark Yarn Client向YARN的ResourceManager申请启动Application Master。同时在SparkContext初始化中将创建DAGScheduler和TASKScheduler等,由于我们选择的是Yarn-Client模式,程序会选择YarnClientClusterScheduler和YarnClientSchedulerBackend

- ResourceManager收到请求后,在集群中选择一个NodeManager,为该应用程序分配第一个Container,要求它在这个Container中启动应用程序的ApplicationMaster,与YARN-Cluster区别的是在该ApplicationMaster不运行SparkContext,只与SparkContext进行联系进行资源的分派

- Client中的SparkContext初始化完毕后,与ApplicationMaster建立通讯,向ResourceManager注册,根据任务信息向ResourceManager申请资源(Container)

- 一旦ApplicationMaster申请到资源(也就是Container)后,便与对应的NodeManager通信,要求它在获得的Container中启动CoarseGrainedExecutorBackend,CoarseGrainedExecutorBackend启动后会向Client中的SparkContext注册并申请Task

- client中的SparkContext分配Task给CoarseGrainedExecutorBackend执行,CoarseGrainedExecutorBackend运行Task并向Driver汇报运行的状态和进度,以让Client随时掌握各个任务的运行状态,从而可以在任务失败时重新启动任务

3.3.3 运行模式之yarn cluster

使用yarn cluster模式提交一个计算pi值的程序,运算100次。

spark-submit --class org.apache.spark.examples.SparkPi \
    --master yarn \
    --deploy-mode cluster \
    --driver-memory 2g \
    --executor-memory 2g \
    --executor-cores 2 \
    /usr/local/spark/examples/jars/spark-examples_2.11-2.2.3.jar  \
    100 

运行原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-plEFTSzv-1615727554196)(./ClassNotes.assets/400827-20171206175225316-227997670.png)]

1. Spark Yarn Client向YARN中的resourcemanager提交应用程序申请,申请要使用的applicationmaster以及Executor。

2. ResourceManager收到请求后,在集群中选择一个NodeManager,为该应用程序分配第一个Container,要求它在这个Container中启动这个应用程序的ApplicationMaster,其中ApplicationMaster会进行SparkContext等的初始化

3. ApplicationMaster向ResourceManager注册信息,这样用户可以直接通过ResourceManage查看应用程序的运行状态,然后它将采用轮询的方式通过RPC协议为各个任务申请资源(container),并监控它们的运行状态直到运行结束

4 一旦ApplicationMaster申请到资源(也就是Container)后,便与对应的NodeManager通信,要求它在获得的Container中启动CoarseGrainedExecutorBackend(Executor对象的创建及维护是由CoarseGrainedExecutorBackend负责的)

 CoarseGrainedExecutorBackend启动后会向ApplicationMaster中的SparkContext注册并申请要运行的Task。

这一点和Standalone模式一样,只不过SparkContext在Spark Application中初始化时,使用CoarseGrainedSchedulerBackend配合YarnClusterScheduler进行任务的调度,其中YarnClusterScheduler只是对TaskSchedulerImpl的一个简单包装,增加了对Executor的等待逻辑等

5.  ApplicationMaster中的SparkContext分配Task给CoarseGrainedExecutorBackend执行,CoarseGrainedExecutorBackend运行Task并向ApplicationMaster汇报运行的状态和进度,以让ApplicationMaster随时掌握各个任务的运行状态,从而可以在任务失败时重新启动任务

6. 应用程序运行完成后,ApplicationMaster向ResourceManager申请注销并关闭自己

3.3.4 yarn的client和cluster的区别

1. driver的位置不同
	- client模式 driver在client上运行的,比如在qianfeng04上提交,driver就在qianfeng04上。
	- cluster模式 driver在applicationmaster里.
2. 是否可以关闭客户端
	- client模式,不可以关闭
	- cluster模式,可以关闭
3. 因此cluster适用企业环境	
      client模式是应用作业调试

注意: 在运行过程中的汇报,都是汇报给driver。

下表是Spark Standalone与Spark On Yarn模式下的比较

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VqjfitKL-1615727554199)(…/%25E6%2596%2587%25E6%25A1%25A3/sparkcore.assets/20191113161636.png)]

四、作业提交

4.1 spark-submit指令简介

想要提交一个spark程序在相应的部署模式(standalone、yarn、mesos、k8s)上来运行,需要使用spark-submit指令

常用的选项如下:

Usage: spark-submit [options] <app jar | python file> [app arguments]
Usage: spark-submit --kill [submission ID] --master [spark://...]
Usage: spark-submit --status [submission ID] --master [spark://...]
Usage: spark-submit run-example [options] example-class [example args]

通用的选项:
	--master MASTER_URL
	--deploy-mode DEPLOY_MODE         《=client|cluster    client是默认的
	--name NAME
	--jars JARS
	--driver-memory MEM 
	--executor-memory MEM
Spark standalone with cluster deploy mode only:
  --driver-cores NUM          Cores for driver (Default: 1).

 Spark standalone or Mesos with cluster deploy mode only:
  --supervise                 If given, restarts the driver on failure.
  --kill SUBMISSION_ID        If given, kills the driver specified.
  --status SUBMISSION_ID      If given, requests the status of the driver specified.

 Spark standalone and Mesos only:
  --total-executor-cores NUM  Total cores for all executors.

 Spark standalone and YARN only:
  --executor-cores NUM        Number of cores per executor. (Default: 1 in YARN mode,
                              or all available cores on the worker in standalone mode)

 YARN-only:
  --driver-cores NUM          Number of cores used by the driver, only in cluster mode
                              (Default: 1).
  --queue QUEUE_NAME          The YARN queue to submit to (Default: "default").
  --num-executors NUM         Number of executors to launch (Default: 2).
                              If dynamic allocation is enabled, the initial number of
                              executors will be at least NUM.
  --archives ARCHIVES         Comma separated list of archives to be extracted into the
                              working directory of each executor.
  --principal PRINCIPAL       Principal to be used to login to KDC, while running on
                              secure HDFS.
  --keytab KEYTAB             The full path to the file that contains the keytab for the
                              principal specified above. This keytab will be copied to
                              the node running the Application Master via the Secure
                              Distributed Cache, for renewing the login tickets and the
                              delegation tokens periodically	

4.2 standalone运行模式的作业提交

作业提交也是两种:

就是client和cluster这两种模式

client模式:  driver运行在提交作业时的客户端的内存中启动,  因此提交后客户端不能关闭,适用于测试
cluster模式: driver运作在某一个worker的内存中,因此提交作业后客户端可以关闭,适用于生产环境

4.2.1 client模式运行一个spark作业

提交Spark提供的利用蒙特·卡罗算法求π的例子,其中100这个参数是计算因子

spark-submit  \
--class org.apache.spark.examples.SparkPi  \
--master spark://qianfeng01:7077  \
--deploy-mode client \
--driver-memory 2G \
--driver-cores 2 \
--executor-memory 1G \
--executor-cores 2 \
/usr/local/spark/examples/jars/spark-examples_2.11-2.2.3.jar 100 

运行流程简介(重点):面试官问道的几率非常大,加上细节4.3小章节

1. client进行提交作业时,会在本地启动一个driver进程,driver会进行初始化操作,比如创建sparkcontext,DAGScheduler,TaskScheduler.
2. driver会向master进行注册,申请启动一个spark application
3. master收到请求后,会分配资源,在相应的worker上来启动这些资源,运行executor进程。
4. executor会向driver进行反向注册。
5. driver会计算task,然后分配给相应的executor来真正的去执行。
6. executor在执行过程中会与driver通信,汇报任务的执行状态和信息
7. driver收到所有的executor执行完毕的信息后,向master申请结束任务,注销资源。

4.2.2 cluster模式运行一个spark作业

spark-submit  \
--class org.apache.spark.examples.SparkPi  \
--master spark://qianfeng01:7077  \
--deploy-mode cluster \
--driver-memory 2G \
--driver-cores 2 \
--executor-memory 1G \
--executor-cores 2 \
/usr/local/spark/examples/jars/spark-examples_2.11-2.2.3.jar 100 
Warning: Master endpoint spark://qianfeng01:7077 was not a REST server. Falling back to 

注意:cluster模式需要master是一个rest server才可以

运行流程简介:

1. client进行提交作业时,会请求master,让其在worker上分配一份资源,来运行driver进程
2. driver启动后会初始化DAGScheduler和TaskScheduler。然后会向master进行注册,申请启动一个spark application
3. master收到请求后,会分配资源,在相应的worker上来启动这些资源,运行executor进程。
4. executor会向driver进行反向注册。
5. driver会计算task,然后分配给相应的executor来真正的去执行。
6. executor在执行过程中会与driver通信,汇报任务的执行状态和信息
7. driver收到所有的executor执行完毕的信息后,向master申请结束任务,注销资源。

4.3 运行流程细节问题

下面是一个spark小程序:
sc.textFile(“xx").flatMap(_.split("")).map((_,1)).reduceByKey(_+_).saveAsTextFile(“xx")
1. 不管什么模式,都要启动一个Driver进程,无非就是Driver在什么位置的区别
2. driver进程启动后,会初始化两个组件,一个DAGScheduler,一个TaskScheduler
3. DAGScheduler会根据job划分N个Stage. 每一个Stage都会有一个TaskSet, 然后将TaskSet发送给TaskScheduler
4. TaskScheduler 会向master申请资源,来运行executor,并将taskSet分配给execuer。

5. executor在收到每一个task时,都会将task添加到一个TaskRunner,然后利用自己维护的一个线程池中的某一个空闲线程来执行task。

6. task分两类:  maptask和reduceTask。  
            reduceTask底层会触发spark的shuffle
            maptask就是数据映射的。

4.4 名词解释

4.4.1 Standalone模式下存在的角色。

Client:客户端进程,负责提交作业到Master。

Master:Standalone模式中主控节点,负责接收Client提交的作业,管理Worker,并命令Worker启动Driver(cluster模式下)和Executor。

Worker:Standalone模式中slave节点上的守护进程,负责管理本节点的资源,定期向Master汇报心跳,接收Master的命令,启动Driver(cluster模式)和Executor。

Driver: 一个Spark作业运行时包括一个Driver进程,也是作业的主进程,负责作业的解析、生成Stage并调度Task到Executor上。包括DAGScheduler,TaskScheduler。

Executor:即真正执行作业的地方,一个集群一般包含多个Executor,每个Executor接收Driver的命令Launch Task,一个Executor可以执行一到多个Task。

4.4.2 作业相关的名词解释(重点)

Stage:一个Spark作业一般包含一到多个Stage。

Task:一个Stage包含一到多个Task,通过多个Task实现并行运行的功能。

DAGScheduler: 实现将Spark作业分解成一到多个Stage,每个Stage根据RDD的Partition(Slice)个数决定Task的个数,然后生成相应的Task set放到TaskScheduler中。

TaskScheduler:实现Task分配到Executor上执行

在web UI界面[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G4VP1tKK-1615727554201)(…/…/day01/%25E6%2596%2587%25E6%25A1%25A3/sparkcore.assets/%25E5%259B%25BE%25E7%2589%258710.png)]

在提交任务时可以指定一个spark程序可以使用的最大资源,比如cpu的核数,内存的大小。实际上用的资源要看真实数据情况,如果真实使用情况大于指定的资源,会直接失败。

五、SparkShell

5.1 说明

1. sparkshell是spark提供的一个交互式界面。可以方便开发人员进行交互式编程。在该命令行下可以使用scala来编写spark程序。通常用于测试spark程序。

2. spark-shell启动有两种模式,local模式和cluster模式

5.2 启动方法

5.2.1 local模式启动

直接使用spark-shell
也可以这样:
spark-shell --master local[cores-number]

注意:启动成功后,
1. 会提供一个webui端口
2. 提供一个变量名叫sc的sparkContext对象,
3. 提示master是local模式打印master的信息
4. 开启了一个spark会话

5.2.2 cluster模式

spark-shell --master spark://qianfeng01:7077

也可以指定资源信息,比如使用 --driver-cores  --executor-memory等

注意:提示的信息除了和local模式一样的外,master提示的是url路径

5.3 退出的说明

不要使用ctrl+c 退出,而是使用:q   或是:quit

若使用了ctrl+c退出 使用命令查看监听端口 netstat - apn | grep 4040 在使用kill -9 端口号 杀死即可   

5.4 注意事项

启动spark-shell时,只要不关闭,就会有一个任务一直在运行,可以查看历史服务器

六、RDD的介绍

6.1 RDD的概念

1. 弹性分布式数据集(RDD),这是Spark中的基本抽象。表示可并行操作的不可变的、分区的元素集合
2. 该类是一个抽象类,包含所有RDDs上可用的基本操作,如“map”、“filter”和“persist”。
3. 此外,org.apache.spark.rdd.PairRDDFunctions 包含仅在键值对的RDDs上可用的操作,如“groupByKey”和“join”
4. DoubleRDDFunctions包含仅在双精度RDDs上可用的操作;
5. 在 Spark 中,对数据的所有操作不外乎创建 RDD、转化已有RDD 以及调用 RDD 操作进行求值。每个 RDD 都被分为多个分区,这些分区运行在集群中的不同节点上。
6. RDD 可以包含 Python、Java、Scala 中任意类型的对象, 甚至可以包含用户自定义的对象。
7. RDD具有数据流模型的特点:自动容错、位置感知性调度和可伸缩性。
8.RDD允许用户在执行多个查询时显式地将工作集缓存在内存中,后续的查询能够重用工作集,这极大地提升了查询速度。

概念中的三大特征:

1. 不可变的: RDD一旦被创建,指向的数据是只读的。如果想要更改数据,一定需要调用算子会产生一个新的RDD
2. 分区: 一个RDD中有多个分区,分区可以根据业务需求来指定。
3. 并行的: 因为有多个分区,就可以并行计算。

强调:RDD不会存储真正的数据,而是通过分区里的索引信息指向真正的数据。

6.2 RDD的结构

每个 RDD 里都会包括分区信息、依赖关系等等的信息,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hxw0Wxdv-1615727554203)(ClassNotes.assets/wps5.jpg)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QaZ1mWeq-1615727554203)(ClassNotes.assets/wps6.png)]

1)分区(Partitions)

1. 一个RDD中可能包含多个分区。好比RDD是一个数组,分区是一个元素。
2. 分区不会真正的存储数据。而是存储数据的索引index.指向一个存储在内存或者硬盘中的数据块 (Block)

注意:RDD对应的数据尽可能的使用内存,只有内存不够用的时候才会存在磁盘上
  1. SparkContext
SparkContext 是所有 Spark 功能的入口,代表了与 Spark 节点的连接,可以用来创建 RDD 对象以及在节点中的广播变量等等。一个线程只有一个 SparkContext。

3)SparkConf

SparkConf 是一些配置信息。

4)Partitioner

Partitioner 决定了 RDD 的分区方式,目前两种主流的分区方式:Hash partioner 和 Range partitioner。Hash 就是对数据的 Key 进行散列分布,Rang 是按照 Key 的排序进行的分区。也可以自定义 Partitioner。

5)Dependencies

Dependencies 也就是依赖关系,记录了该 RDD 的计算过程,也就是说这个 RDD 是通过哪个 RDD 经过怎么样的转化操作得到的。

这里有个概念,根据每个 RDD 的分区计算后生成的新的 RDD 的分区的对应关系,可以分成窄依赖和宽依赖。

- 窄依赖就是父 RDD 的分区可以一一对应到子 RDD 的分区,
- 宽依赖是说父 RDD 的每个分区可以被多个子 RDD 分区使用。

如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w918KUGz-1615727554204)(ClassNotes.assets/wps2.png)]

由于窄依赖的特性,窄依赖允许子 RDD 的每个分区可以被并行处理产生,而且支持在同一个节点上链式执行多条指令,无需等待其它父 RDD 的分区操作。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oLZ0aeiH-1615727554205)(ClassNotes.assets/wps3.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-28e1CDwi-1615727554206)(ClassNotes.assets/image-20201117152810983.png)]

Spark 区分宽窄依赖的原因主要有两点:

1. 窄依赖支持在同一节点上进行链式操作,比如在执行了 map 后,紧接着执行 filter 操作。相反,宽依赖需要所有父分区都是可用的。

2. 从失败恢复的角度考虑,窄依赖失败恢复更有效,因为只要重新计算丢失的父分区即可,而宽依赖涉及到 RDD 的各级多个父分区。

6)Checkpoint

检查点机制,在计算过程中有一些比较耗时的 RDD,我们可以将它缓存到硬盘或者 HDFS 中,标记这个 RDD 有被检查点处理过,并且清空它的所有依赖关系。同时,给它新建一个依赖于 CheckpointRDD 的依赖关系,CheckpintRDD 可以用来从 硬盘中读取 RDD 和生成新的分区信息。

这么做之后,当某个 RDD 需要错误恢复时,回溯到该 RDD,发现它被检查点记录过,就可以直接去硬盘读取该 RDD,无需重新计算。

7)Preferred Location

针对每一个分片,都会选择一个最优的位置来计算,数据不动,代码动。

8)Storage Level

用来记录 RDD 持久化时存储的级别,常用的有:

1. MEMORY_ONLY:只存在缓存中,如果内存不够,则不缓存剩余的部分。这是 RDD 默认的存储级别。

2. MEMORY_AND_DISK:缓存在内存中,不够则缓存至磁盘。

3. DISK_ONLY:只存硬盘。

4. MEMORY_ONLY_2 和 MEMORY_AND_DISK_2等:与上面的级别和功能相同,只不过每个分区在集群两个节点上建立副本。

9)Iterator

迭代函数和计算函数(compute)是用来表示 RDD 怎样通过父 RDD 计算得到的。

迭代函数首先会判断缓存中是否有想要计算的 RDD,如果有就直接读取,如果没有就查找想要计算的 RDD 是否被检查点处理过。如果有,就直接读取,如果没有,就调用计算函数向上递归,查找父 RDD 进行计算。

6.3 RDD的弹性

1) 自动进行内存和磁盘数据存储的切换
   Spark优先把数据放到内存中,如果内存放不下,就会放到磁盘里面,程序进行自动的存储切换

2) 基于血统的高效容错机制
    在RDD进行转换和动作的时候,会形成RDD的Lineage依赖链,当某一个RDD失效的时候,可以通过重新计算上游的RDD来重新生成丢失的RDD数据。

3) Task如果失败会自动进行特定次数的重试
    RDD的计算任务如果运行失败,会自动进行任务的重新计算,默认次数是4次。

4) Stage如果失败会自动进行特定次数的重试
    如果Job的某个Stage阶段计算失败,框架也会自动进行任务的重新计算,默认次数也是4次。

5) Checkpoint和Persist可主动或被动触发
    RDD可以通过Persist持久化将RDD缓存到内存或者磁盘,当再次用到该RDD时直接读取就行。也可以将RDD进行检查点,检查点会将数据存储在HDFS中,该RDD的所有父RDD依赖都会被移除。

6) 数据调度弹性
    Spark把这个JOB执行模型抽象为通用的有向无环图DAG,可以将多Stage的任务串联或并行执行,调度引擎自动处理Stage的失败以及Task的失败。

7) 数据分片的高度弹性
    可以根据业务的特征,动态调整数据分片的个数,提升整体的应用执行效率。

七、RDD的编程

7.1 RDD的创建方式

通常来说有三种:

1. 通过读取外部文件(本地文件,hdfs文件)来获取一个RDD
2. 调用makeRDD()或者parallelize()从集合中创建
3. 其他RDD调用算子转换而来。

7.2 RDD算子的分类

7.2.1 RDD的算子分两大类

RDD支持两种操作:转化操作和行动操作。分别对应的是Transformation算子和Action算子。

- RDD 的转化操作是返回一个新的RDD的操作,比如 map()和 filter(),而行动操作则是向驱动器程序返回结果或把结果写入外部系统的操作。比如 count() 和 first()。 

- Spark采用惰性计算模式,RDD只有第一次在一个行动操作中用到时,才会真正计算。Spark可以优化整个计算过程。默认情况下,Spark 的 RDD 会在你每次对它们进行行动操作时重新计算。如果想在多个行动操作中重用同一个 RDD,可以使用 RDD.persist() 让 Spark 把这个 RDD 缓存下来。

1)常用Transformation算子

RDD中的所有转换都是延迟加载的,也就是说,它们并不会直接计算结果。相反的,它们只是记住这些应用到基础数据集(例如一个文件)上的转换动作。只有当发生一个要求返回结果给Driver的动作时,这些转换才会真正运行。这种设计让Spark更加有效率地运行。

转换含义
map(func)返回一个新的RDD,该RDD由每一个输入元素经过func函数转换后组成
filter(func)返回一个新的RDD,该RDD由经过func函数计算后返回值为true的输入元素组成
flatMap(func)类似于map,但是每一个输入元素可以被映射为0或多个输出元素(所以func应该返回一个序列,而不是单一元素)
mapPartitions(func)类似于map,但独立地在RDD的每一个分片上运行,因此在类型为T的RDD上运行时,func的函数类型必须是Iterator[T] => Iterator[U]
mapPartitionsWithIndex(func)类似于mapPartitions,但func带有一个整数参数表示分片的索引值,因此在类型为T的RDD上运行时,func的函数类型必须是(Int, Iterator[T]) => Iterator[U]
sample(withReplacement, fraction, seed)根据fraction指定的比例对数据进行采样,可以选择是否使用随机数进行替换,seed用于指定随机数生成器种子
union(otherDataset)对源RDD和参数RDD求并集后返回一个新的RDD
intersection(otherDataset)对源RDD和参数RDD求交集后返回一个新的RDD
distinct([numTasks]))对源RDD进行去重后返回一个新的RDD
groupByKey([numTasks])在一个(K,V)的RDD上调用,返回一个(K, Iterator[V])的RDD
reduceByKey(func, [numTasks])在一个(K,V)的RDD上调用,返回一个(K,V)的RDD,使用指定的reduce函数,将相同key的值聚合到一起,与groupByKey类似,reduce任务的个数可以通过第二个可选的参数来设置
aggregateByKey(zeroValue)(seqOp, combOp, [numTasks])相同的Key值进行聚合操作,在聚合过程中同样使用了一个中立的初始值zeroValue:中立值,定义返回value的类型,并参与运算seqOp:用来在同一个partition中合并值combOp:用来在不同partiton中合并值
sortByKey([ascending], [numTasks])在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD
sortBy(func,[ascending], [numTasks])与sortByKey类似,但是更灵活
join(otherDataset, [numTasks])在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD
cogroup(otherDataset, [numTasks])在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable,Iterable))类型的RDD
cartesian(otherDataset)笛卡尔积
pipe(command, [envVars])将一些shell命令用于Spark中生成新的RDD
coalesce(numPartitions**)**重新分区
repartition(numPartitions)重新分区
repartitionAndSortWithinPartitions(partitioner)重新分区和排序

2) Action算子

在RDD上运行计算,并返回结果给Driver或写入文件系统

动作含义
reduce(func)通过func函数聚集RDD中的所有元素,这个功能必须是可交换且可并联的
collect()在驱动程序中,以数组的形式返回数据集的所有元素
count()返回RDD的元素个数
first()返回RDD的第一个元素(类似于take(1))
take(n)返回一个由数据集的前n个元素组成的数组
takeSample(withReplacement,num, [seed])返回一个数组,该数组由从数据集中随机采样的num个元素组成,可以选择是否用随机数替换不足的部分,seed用于指定随机数生成器种子
takeOrdered(n, [ordering])takeOrdered和top类似,只不过以和top相反的顺序返回元素
saveAsTextFile(path)将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本
saveAsSequenceFile(path)将数据集中的元素以Hadoop sequencefile的格式保存到指定的目录下,可以使HDFS或者其他Hadoop支持的文件系统。
saveAsObjectFile(path)
countByKey()针对(K,V)类型的RDD,返回一个(K,Int)的map,表示每一个key对应的元素个数。
foreach(func)在数据集的每一个元素上,运行函数func进行更新。

7.3 算子的练习

7.3.1 简单转换算子练习

map(func)
filter(func)
flatMap(func)
union(func)
intersecter(func)
join
cartesian
zip
sample
package com.qf.spark.day03

import com.qf.spark.day02._02SimpleSuanzi.sc
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD

/**
 * 简单转换算子的练习
 * map(func)
 * filter(func)
 * flatMap(func)
 * union(func):求两个RDD的并集
 * intersecter(func):求两个RDD的交集
 * subtract(RDD): 求差集
 * join:做关联操作时,都应该是键值对的RDD
 * cartesian:笛卡尔积算子, 两个任何类型的RDD都可以做笛卡尔积做操作
 * zip: 两个RDD做拉链,长度要统一
 * zipWithIndex:RDD与下标做拉链
 * sample
 */
object _01SimpleOp {
    def main(args: Array[String]): Unit = {
        testSample
    }
    def testSample={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        val value: RDD[Int] = sc.parallelize(Array(1, 2, 3,4,5,6,7,8,9))

        /**
         * 功能:随机从RDD中按照比例取样
         * withReplacement: Boolean  取一个后,是否要放回,false表示不放回,true表示放回
         * fraction: Double,    取样的比例,百分比
         * seed: Long = Utils.random.nextLong      随机种子
         */
        val value1: RDD[Int] = value.sample(true, 0.9,0)
        value1.foreach(println)
    }


    def testZip={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        val value: RDD[Int] = sc.parallelize(Array(1, 2, 3))
        val value1: RDD[String] = sc.parallelize(Array("a", "b", "c"))

        value.zip(value1).foreach(println)
        value.zipWithIndex().foreach(println)
    }
    def testCartesian={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)

        val rdd1: RDD[(Int, String)] = sc.parallelize(List((1001, "zhangsan"), 
                                                           (1002, "lisi"), 
                                                           (1003, "wangwu"), 
                                                           (1004, "zhaoliu"), 
                                                           (1005, "superman")))
        val rdd2: RDD[(Int, String)] = sc.parallelize(List((1001, "长春"), 
                                                           (1002, "北京"), 
                                                           (1003, "上海"), 
                                                           (1006, "深圳")))
        rdd1.cartesian(rdd2).foreach(println)

        val value: RDD[Int] = sc.parallelize(Array(1, 2, 3))
        val value1: RDD[String] = sc.parallelize(Array("a", "b", "c"))
        value.cartesian(value1).foreach(println)

    }
    def testJoin={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)

        val rdd1: RDD[(Int, String)] = sc.parallelize(List((1001, "zhangsan"), 
                                                           (1002, "lisi"), 
                                                           (1003, "wangwu"), 
                                                           (1004, "zhaoliu"),
                                                           (1005, "superman")))
        val rdd2: RDD[(Int, String)] = sc.parallelize(List((1001, "长春"), 
                                                           (1002, "北京"), 
                                                           (1003, "上海"), 
                                                           (1006, "深圳")))

        //做内关联操作
        val value: RDD[(Int, (String, String))] = rdd1.join(rdd2)
        value.foreach(println)
        //左外连接
        rdd1.leftOuterJoin(rdd2).foreach(println)
        //右外连接
        rdd1.rightOuterJoin(rdd2).foreach(println)
        //全外连接
        rdd1.fullOuterJoin(rdd2).foreach(println)
    }
    def testsubtract={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)

        val rdd1: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 5,6),3)
        val rdd2: RDD[Int] = sc.parallelize(Array(4, 5, 6, 7, 8),3)
        //调用union求并集
        val rs: RDD[Int] = rdd1.subtract(rdd2)
        rs.foreach(println)

    }
    def testIntersection={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)

        val rdd1: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 5,6),3)
        val rdd2: RDD[Int] = sc.parallelize(Array(4, 5, 6, 7, 8),3)
        //调用union求并集
        val rs: RDD[Int] = rdd1.intersection(rdd2)
        rs.foreach(println)

    }
    def testUnion={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)

        val rdd1: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 5),3)
        val rdd2: RDD[Int] = sc.parallelize(Array(4, 5, 6, 7, 8),3)
        //调用union求并集
        val rs: RDD[Int] = rdd1.union(rdd2)
        rs.foreach(println)

    }
    def testFlatMap={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)

        val rdd1 = sc.parallelize(
            List("you are the best","you can do it","welcome to china"),2)
        //flatMap:遍历集合元素,然后进行计算并将
        //flatMap要处理的是可以返回TravesableOnce可重复遍历的迭代器对象
        // _.split(",") 调用的是scala的函数,而不是spark的算子。
        rdd1.flatMap(_.split(" ")).foreach(println)
    }
    def testFilter={
        val conf = new SparkConf().setAppName("test").setMaster("local[2]")
        val sc = new SparkContext(conf)
        val rdd1: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 5, 6))
        //filter:需要传入一个返回boolean的函数。使用filter算子返回所有的偶数
        //需要注意的是:如果是多分区,每一个分区都会执行过滤函数,但是在汇总时,可能
        //无法保证数据的顺序。
        rdd1.filter(_%2==0).foreach(println)
    }
    def testMap={
        /**
         * 默认分区的情况。
         * 本地(local)模式下:如果不指定核数,会默认使用1,除非指定local[num],这样就会使用指定的num
         * 集群(cluster)模式下:默认是所有worker的总核数
         */
        val conf = new SparkConf().setAppName("test").setMaster("local[3]")
        val sc = new SparkContext(conf)
        val value: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 5, 6))
        //map算子:遍历RDD中的每一个元素,可以进行计算,返回一个新的RDD对象
        val rdd1: RDD[Int] = value.map(x => x * 2)
        rdd1.foreach(println)
    }
}

7.3.2 行动算子练习

collect
count
first
take
saveAsTextFile
foreach
countByKey
reduce
takeSample
takeOrdered
package com.qf.spark.day03

import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD

/**
 * 行动算子的练习
 * collect  :	先局部搜集,然后再汇总 :搜集所有分区的数据,返回到Driver处
 * count: 		计算所有分区的元素个数,先计算每一个分区的元素个数,然后再将多个分区的个数
 *         		进行累加  ----》就是先局部运算,在汇总
 * first: 		获取0分区的第一个元素
 * take:		取所有分区中的前n个元素,分区是按照顺序排列的
 * saveAsTextFile:将结果保存到外部设备中
 * foreach:		遍历
 * countByKey: 	RDD必须是键值对形式,然后统计Key的重复次数
 * countByValue: 可以计算RDD中的元素的重复次数
 * reduce:  	将所有的分区进行规约。
 * takeSample:	取样算子
 * takeOrdered: 所有的分区进行全排序(升序)然后取前N个
 */
object _02ActionOp {
    def main(args: Array[String]): Unit = {
        testTakeOrdered
    }
    def testTakeOrdered={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        val value: RDD[Int] = sc.parallelize(Array(6,1,2,6,7,3,4,1),2)
        println(value.takeOrdered(4).mkString(","))
    }

    def testTakeSample={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        val value: RDD[Int] = sc.parallelize(Array(1, 2, 3,4,5,6,7,8,9,10),2)
        /**
         * 功能就是随机取样
         * withReplacement: Boolean,  表示是否放回, true表示放回 false表示不放回
         * num: Int, : 指定取样的个数
         * seed: Long = Utils.random.nextLong  :随机种子,如果指定种子,每次取出的都一样
         */
        val ints: Array[Int] = value.takeSample(true, 4)
        println(ints.mkString(","))
    }
    def testReduce={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        val value: RDD[Int] = sc.parallelize(Array(1, 2, 3,4,5,6,7,8,9,10),2)
        val i: Int = value.reduce(_+_)
        println(i)
    }

    def testCountBykey={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        val rdd1: RDD[(Int, String)] = sc.parallelize(List((1001, "zhangsan"),
            (1002, "lisi"), (1003, "wangwu"),
            (1004, "zhaoliu"), (1005, "superman"),(1004, "hello"), (1004, "hello")))
        println(rdd1.countByKey())
    }
    def testCountValue={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        val value: RDD[Int] = sc.parallelize(Array(6, 2, 3,4,5,1,7,8,9,10,6),2)
        println(value.countByValue())
    }

    def testTake={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        val value: RDD[Int] = sc.parallelize(Array(6, 2, 3,4,5,1,7,8,9,10),2)
        println(value.take(6).mkString(","))
    }
    def testFirst={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        val value: RDD[Int] = sc.parallelize(Array(6, 2, 3,4,5,1,7,8,9,10),2)
        println(value.first())
    }
    def testCount={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        val value: RDD[Int] = sc.parallelize(Array(1, 2, 3,4,5,6,7,8,9,10),2)
        println(value.count())
    }

    def testCollect={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        val value: RDD[Int] = sc.parallelize(Array(1, 2, 3,4,5,6,7,8,9,10),2)
        val ints: Array[Int] = value.map(_ * 2).collect()
        for (elem <- ints) {
            println(elem)
        }

    }
}

7.3.3 高阶转换算子练习

mapPartitions
mapValues()
glom()
mapPartitionsWithIndex
cogroup

sortBy
sortByKey

combineByKey
groupByKey()
groupBy()

reduceByKey()
distinct(funct)

aggregate
aggregateByKey


coalesce
repartition
package com.qf.spark.day03

import com.qf.spark.day02._02SimpleSuanzi.sc
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD

/**
 * 高阶转换算子的练习
 *
 * mapValues():  遍历RDD中的每一个元素的value的值,并返回一个新的value.
 * mapPartitions: 针对于每一个分区进行遍历,形参是一个迭代器,指向的就是每一个分区,返回值是一个新的迭代器
 * glom() : 将RDD中的每一个分区的数据返回一个RDD数组对象
 * mapPartitionsWithIndex: 带上分区号然后遍历每一个分区内的所有元素
 * cogroup:  只能作用在KV形式的RDD上,和另一个KV形式的RDD,按照Key来分组。底层逻辑:先RDD内部按照Key分组,然后再与另一个RDD进行按照key分组。
 *
 * sortBy : 对RDD的元素进行全局排序。:底层调用了soutBykey.
 * sortByKey: 只能作用在KV形式的RDD上,对key进行排序,可以指定排序规则,也可以指定分区数量. sortBykey底层会创建ShuffleRDD(宽依赖)
 *
 * combineByKey
 * groupByKey()
 * groupBy()
 *
 * reduceByKey()
 * distinct(funct)
 *
 * aggregate
 * aggregateByKey
 *
 * coalesce
 * repartition
 */
object _03SuperTransformationOp {
    def main(args: Array[String]): Unit = {
        testCoalesce
    }
    /**
     * 重新分区:repartition
     * 底层调用的是coalesce(numPartitios,true)
     * true表示使用shuffle进行重新洗牌
     */
    def testrepartition={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        val rdd1 = sc.parallelize(Array("1","2","3","4","1","2","3"),3)
        val str: String = rdd1.aggregate("|")(_ + _, _ + _)
        println("未重新分配区间前:"+str)
        //使用coalesce重新分区
        val newRDD: RDD[String] = rdd1.repartition(2)
        newRDD.mapPartitionsWithIndex(
            (index:Int,iter:Iterator[String])=>iter.map(index+":"+_)).foreach(println)
        val str1: String = newRDD.aggregate("|")(_ + _, _ + _)
        println("重新分配区间后:"+str1)
    }

    /**
     * 将RDD的原有分区进行重新分区,生成一个新的RDD,
     * 注意事项:在shuffle=false是,新设置的分区数量大于原有的分区数量时,是不生效的。
     *          为true时,可以设置大于原有的分区数
     */
    def testCoalesce={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        val rdd1 = sc.parallelize(Array("1","2","3","4","1","2","3"),3)
        val str: String = rdd1.aggregate("|")(_ + _, _ + _)
        println("未重新分配区间前:"+str)
        //使用coalesce重新分区
        val newRDD: RDD[String] = rdd1.coalesce(4,true)
        newRDD.mapPartitionsWithIndex(
            (index:Int,iter:Iterator[String])=>iter.map(index+":"+_)).foreach(println)
        val str1: String = newRDD.aggregate("|")(_ + _, _ + _)
        println("重新分配区间后:"+str1)
    }

    /**
     * AggregateByKey:根据key进行聚合,将相同的key的所有的value进行累加
     * 底层逻辑:
     *     先每个分区类进行按照key进行分开累加。
     *         先累加分区内的第一个key的value值到累加器(初始值),然后看第二key
     *         如果第二个key相同,就会再次累加到这个累加器上,如果不同,就重新使用一个
     *         新的累加器,将对应的value累加上,直到分区内累加完毕
     *      然后再和别的分区里相同的key的结果进行累加
     */
    def testAggregateByKey={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        val rdd1 = sc.parallelize(Array(("a",1),("b",2),("a",3),("c",4),("a",4)),2)
        val str: RDD[(String,Int)]= rdd1.aggregateByKey(2)(_+_,_+_)
        str.foreach(println)
    }


    /**
     * 带有一个初始值参数,聚合所有分区的元素。
     * 逻辑:每一个分区的元素都要单独累加到初始值上,然后初始值在和每一个分
     * 区的结果进行按照分区号顺序进行累加
     */
    def testAggregate={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        val rdd1 = sc.parallelize(Array("1","2","3","4","1","2","3"),2)
        val str: String = rdd1.aggregate("|")(_ + _, _ + _)
        println(str)
        val rdd2 = sc.parallelize(Array(1,2,3,4,5),2)
        val rs: Int = rdd2.aggregate(5)(_ + _, _ + _)
        println(rs)
    }


    /**
     * 底层调用了reduceByKey
     */
    def testDistinct={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        val rdd2 = sc.parallelize(Array("1","2","3","4","1","2","3"),2)
        //查看如何分区的
        // rdd2.mapPartitionsWithIndex(myfunc).foreach(println)
        /**
         * distinct的底层流程
         *
         * 1      1 null
         * 2      2 null
         * 3      3 null                   1 null            1 null
         *                                 1 null            3 null        1  3
         *                                 3 null
         *                                 3 null
         *
         * 4      4 null
         * 1      1 null                 2 null            2 null        2  4
         * 2      2 null                 2 null            4 null
         * 3      3 null                 4 null
         *
         */
        rdd2.distinct(2).foreach(println)
    }

    /**
     * 作用:此算子作用在KV形式的RDD上,按照相同的key,将所有的value做汇总操作。
     * 功能和groupBykey相同
     */
    def testReduceByKey={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        val rdd1: RDD[(Int, String)] = sc.parallelize(List((1,"a"),(2,"b"),(1,"c"),(3,"e"),(4,"f"),(1,"m")),2)
        val value: RDD[(Int, String)] = rdd1.reduceByKey(_ + _)
        value.foreach(println)
    }

    /**
     * groupBy: 将相同的元素进行组合到一起,返回的KV形式,K是这个元素,V是这个相同元素的所有的组合
     * 底层调用了groupByKey
     */
    def  testGroupBy={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        val rdd1: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 0, 1, 7, 8, 9), 2)
        val value: RDD[(Int, Iterable[Int])] = rdd1.groupBy(x => x)
        val tuples: Array[(Int, Iterable[Int])] = value.collect()
        for(elem<-tuples){
            println(elem._1+"的个数:"+elem._2.toList.length)
        }

    }

    def testGroupByKey={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        val rdd1: RDD[(Int, String)] = sc.parallelize(List((1,"a"),
                                                           (2,"b"),(1,"c"),
                                                           (3,"e"),(4,"f"),(1,"m")),2)
        /**
         * groupByKey
         * 功能是: 按照KV对的K进分组,相同的K的value组合到一起,形成迭代器。
         * 底层也会创建ShuffleRDD
         */
        val value: RDD[(Int, Iterable[String])] = rdd1.groupByKey(1)
        value.foreach(println)
    }
    /**
     * combineByKey: 底层也创建了ShuffleRDD,涉及到宽依赖
     */
    def testCombineByKey={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        val rdd1: RDD[(Int, String)] = sc.parallelize(List((1,"a"),(2,"b"),(1,"c"),
                                                           (3,"e"),(4,"f"),(1,"m")),2)
        /**
         * 功能:通过key来合并value。
         * 第一个参数是一个函数:指的是value的初始值。 也就是累加器
         * 第二个参数也是一个函数:作用于分区内,将相同key的每一个元素累加到累加器上。
         * 第三个参数也是一个函数:作用于分区间,将不同分区的结果累加到相同的key的累加器上
         */
        val value: RDD[(Int, String)] = rdd1.combineByKey(
            x => x, (a: String, b: String) => a + b, (a: String, b: String) => a + b)
        value.foreach(println)
    }


    def testSortByKey={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        val rdd1: RDD[(Int, String)] = sc.parallelize(
            List((1003, "zhangsan"), (1002, "lisi"),(1004,"zhangmazi")),2)
        rdd1.sortByKey(true,1).foreach(println)
    }
    /**
     *sortBy : 对RDD的元素进行全局排序。
     */
    def testSortBy={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        val rdd1: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 0, 1, 7, 8, 9), 2)
        rdd1.sortBy(x=>x,false).foreach(println)
    }

    /**
     * cogroup:  只能作用在KV形式的RDD上,和另一个KV形式的RDD,按照Key来分组。
     * 底层逻辑:先RDD内部按照Key分组,然后再与另一个RDD进行按照key分组。
     */
    def testCogroup={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        //创建一个一组kv形式的员工基本信息,如下
        val rdd1: RDD[(Int, String)] = sc.parallelize(
            List((1001, "zhangsan"), (1002, "lisi"),(1004,"zhangmazi")),2)
        //创建一个一组kv形式的员工地址信息
        val rdd2: RDD[(Int, String)] = sc.parallelize(
            List((1001, "北京"), (1002, "上海"),(1004,"深圳"),(1001,"南京"),(1005,"黑河")),2)
        val value: RDD[(Int, (Iterable[String], Iterable[String]))] = rdd1.cogroup(rdd2)
        value.foreach(println)
    }

    /**
     * mapPartitionsWithIndex: 带上分区号然后遍历每一个分区内的所有元素
     */
    def testMapPartitionsWithIndex ={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        val rdd1: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 0, 1, 7, 8, 9), 2)

        /**
         * 形参是一个函数: f: (Int, Iterator[T]) => Iterator[U],
         * 函数的第一个形参指的是分区号,分号从0开始
         * 函数的第二个形参指的是对应分区的所有数据,是一个迭代器
         * 返回值是一个新的迭代器
         */
        //定义函数 f1
        val f1 = (index: Int, iter: Iterator[Int]) => iter.map(index + ":" + _)
        //将函数f1传入算子中
        val value: RDD[String] = rdd1.mapPartitionsWithIndex(f1)
        value.foreach(println)
    }


    /**
     * glom() : 将RDD中的每一个分区的数据返回一个RDD数组对象
     */
    def testGlom={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        val rdd1: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 0, 1, 7, 8, 9), 2)
        val value: RDD[Array[Int]] = rdd1.glom()
        /*value.foreach(x=>x.foreach(println))*/
        for (elem <- value.collect()) {
            println(elem.mkString(","))
        }
    }

    /**
     * mapPartitions: 针对于每一个分区进行遍历,形参是一个迭代器,指向的就是
     * 每一个分区,返回值是一个新的迭代器
     */
    def testMapPartitions={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        val rdd1: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 0, 1, 7, 8, 9), 2)
        val rs: RDD[Int] = rdd1.mapPartitions(iter=>iter.filter(_>2))
        rs.foreach(println)
       /* val rs: RDD[String] = rdd1.mapPartitions(iter => {
            val names = Array("a","b")
            names.toIterator
        })
        rs.foreach(println)*/
    }

    /**
     * mapValues():  遍历RDD中的每一个元素的value的值,并返回一个新的value.
     */
    def testMapValues={
        val conf = new SparkConf().setAppName("test").setMaster("local")
        val sc = new SparkContext(conf)
        val rdd1: RDD[(Int, String)] = sc.parallelize(
            List((1001, "zhangsan"), (1002, "lisi"),(1004,"zhangmazi")),2)
        //计算每个人姓名的长度
        val value: RDD[(Int, Int)] = rdd1.mapValues(_.length)
        value.foreach(println)
        //判断每个人的是否大于5长度
        val rs: RDD[(Int, (String, Boolean))] = rdd1.mapValues(x => {
            (x, x.length > 5)
        })
        rs.foreach(println)
    }
}

7.4 算子的比较

1)map、mapPartitions、flatMap、mapPartitionsWithIndex

从效率上讲:mapPartitions的效率明显高于map.
	
	在调用map算子时,针对每一条记录都会执行一次,如果一个RDD中有10分区,每一个分区有1000条记录,那么就会执行1w次
	在调用mapPartitions算子,针对每一个分区执行一次(因此参数是迭代器,每次接受一个分区)。如果一个RDD中有10分区,此算子执行10次。

从要处理的数据量讲:
    map在处理rdd中的数据时,如果处理了1000条,还剩下9000条件,但是此时,内存不够用了,可以将rdd中已经被处理的1000条记录进行内存释放。这样就可以继续使用内存了。
    mapPartitions在处理RDD中的分区数据时,只有当此分区中的所有的记录处理完后,此分区中的数据才可以释放,因此当没有处理完时,内存不够用了,将造成OOM(内存溢出)。
    
    
3. map和flatMap
   map处理的记录是RDD中的元素,元素可以是任何类型的
   flatMap处理的记录是一个迭代器对象
4. mapPartitions和mapPartitionsWithIndex
   mapPartitions不能获取自己的分区索引
   mapPartitionsWithIndex可以获取自己的分区索引

2)reduceByKey、groupByKey、aggregateByKey

1. reduceByKey和groupByKey比较
   功能上:reduceByKey是按照key分组,然后组内进行规约运算
          groupByKey是按照key进行分组。
   参数上:
   		reduceByKey(func: (V, V) => V, numPartitions: Int):可以自定义函数和指定分区数量
   		groupByKey(numPartitions: Int): 只能指定分区数量
2. reduceByKey和aggregateByKey比较
   功能上:都是按照key分组,组内进行规约(聚合)运算
   参数上:
   reduceByKey(func: (V, V) => V, numPartitions: Int):可以自定义函数和指定分区数量
   aggregateByKey[U: ClassTag]
   (zeroValue: U, numPartitions: Int)(seqOp: (U, V) => U,combOp: (U, U) => U)
   :带有一个初始值的形参,有一个分区内的函数形参,还有一个分区间的规范函数形参。

3)coalesce, repartition

功能上:都是用于重新分区

参数上:
	coalesce ( numPartitions : Int , shuffle : Boolean = false ): 可以指定是否使用shuffle形式
	注意:使用shuffle指的是将原RDD的所有分区的数据重新打乱分区
	     不使用shuffle时,指定的分区数量不能大于原来的RDD的分区数量

	repartition ( numPartitions : Int ): RDD [T]:底层调用的使用coalesce(numPartitions,true)
	无法指定不使用shuffle。

7.5 textFile算子的源码

1. textFile算子的参数:
	textFile(path: String, minPartitions: Int = defaultMinPartitions):
	第一个参数:表示要加载(读取)的文件路径
	第二个参数:表示分区数量,可以不赋值,当指定值时,分区数量一定是指定时的数量。
	          不赋值时,会使用默认值,  
	             def defaultMinPartitions: Int = math.min(defaultParallelism, 2)
	             机器的线程数量和2进行比较取最小值。
	             local模式不指定线程数量,线程数量就是1.
	             cluster模式不指定线程数量时,默认使用的是cpu的核数。如果所有worker的核数
	             加起来大于2,则会使用2
	          
2.底层源码使用了HadoopFile算子去读取hdfs上的文件,封装成pair形式的HadoopRDD,然后取出pair._2的值 
3.分区数量会赋值给底层的getSplit(jobConf,minPartitions)方法.
  因此:分区数量由开发人员决定
4.每个分区对应的真正的原始数据,是由getSplit()方法里的逻辑
 	if (isSplitable(fs, path)) {
          long blockSize = file.getBlockSize();
          long splitSize = computeSplitSize(goalSize, minSize, blockSize);

          long bytesRemaining = length;
          while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
            String[][] splitHosts = getSplitHostsAndCachedHosts(blkLocations,
                length-bytesRemaining, splitSize, clusterMap);
            splits.add(makeSplit(path, length-bytesRemaining, splitSize,
                splitHosts[0], splitHosts[1]));
            bytesRemaining -= splitSize;
          }

          if (bytesRemaining != 0) {
            String[][] splitHosts = getSplitHostsAndCachedHosts(blkLocations, length
                - bytesRemaining, bytesRemaining, clusterMap);
            splits.add(makeSplit(path, length - bytesRemaining, bytesRemaining,
                splitHosts[0], splitHosts[1]));
          }
        }
   来决定的

7.6 基站统计信息案例

7.7 spark的序列化

作业提交的原理:
1. Driver的功能是初始化作业。
  - 1.包括DAGScheduler的初始化,DAGScheduler会根据程序中的所有算子维护一个有向无环图DAG
  - 2.包括Taskcheduler的初始化,Taskcheduler会将TaskSet分配到executor上
  
  换句话说,所有的初始化工作都是在Driver端完成,比如,类的实例化,全局变量的初始化。
2.但是真正执行算子时,是在worker的executor里执行的,那么算子需要的形参的值,可能要从Driver中抓取,一定会产生网络IO。而网络IO是需要序列化的
  
3. 案例分析:开发人员自定义了一个类型,来查询RDD的数据中是否包含了某一个字符串,如果包含了就返回。

class Search(val query: String) extends java.io.Serializable{
    //方法1:是判断RDD中的数据s是否包含了指定的query 包含了返回true,不包含返回false
	def isMatch(s: String): Boolean = {
    	s.contains(query)
	}
	//方法2:将一个RDD中满足条件的数据返回,形成一个含有要匹配的字符串的数据的新RDD
	def getMatches(rdd: RDD[String]): RDD[String] = {
    	//注意:filter是在worker上的excutor里计算的,还有isMatch方式是this的,
    	//this对象在Driver上初始化的,因此this要传输到worker上,所以要实现序列化
    	rdd.filter(isMatch)
	}
	
	def getMatchesField(rdd: RDD[String]): RDD[String] = { 
		    //filter在worker上运行,形参中有query变量,而query是this对象的,因此
		    //this也也要传输到worker上,所以this需要序列化
			rdd.filter(x => x.contains(query)) 
	}
	//注意:此方法的初始化在driver中,方法的局部变量就会被初始化,所以query_的值是this的query的值
	//运行时,在worker上,因此this对象不需要进行网络传输。
	def getMatches(rdd: org.apache.spark.rdd.RDD[String]):
		org.apache.spark.rdd.RDD[String] = { 
		// 安全:只把我们需要的字段拿出来放入局部变量中
    		val query_ = this.query    
			rdd.filter(x => x.contains(query_))
    } 
} 
总结:-- 自定义的类如果涉及到网络IO,那么需要实现序列化接口java.io.Serializable
     -- 如果是类的变量在算子中直接被使用,不想进行对象的网络传输,可以提前将类的变量的值赋值给局部变量

八、深入了解Spark

8.1 依赖关系

RDD间的依赖关系,在Spark中分两种,分别如下:
- 窄依赖(NarrowDependency)
	父RDD中的一个分区对应子RDD中的一个分区。父RDD是子RDD的窄依赖。
 	这个转换过程中的算子也可以称之为窄依赖算子,比如有map、filter、flagMap、

	总结:窄依赖我们形象的比喻为**独生子女**

- 宽依赖(wideDependenCy,也叫ShuffleDependency)
	父RDD中的一分区对应子RDD中的多个分区。父RDD是子RDD的宽依赖。
	这个转换过程中的算子也可以称之为宽依赖算子,比如有reduceByKey,groupByKey等
	总结:宽依赖我们形象的比喻为**超生**

- 血统(Lineage)
  多个RDD之间的关系构成了一个血缘(血统)关系。这样的关系在保存在每一个RDD的结构中。这样的好处是,当某一个算子在计算上一个RDD中分区数据时,如果发现分区数据丢失,则会先看数据是否已经缓存,如果有直接读取,如果没有,再查看是否做了检查点机制,如果有就从检查点读取,如果没有,则从上一个父RDD重新计算。这样就大大提高了计算效率,不需要从头计算。  这也叫血统容错。

- 集群容错
   excutor宕机了,worker会重新启动一个新的excutor来完成剩下没有完成的task.
   worker宕机了,master会将此worker没有完成task重新分配给另外的worker上进行计算

8.2 有向无环图

1. 从一个节点出发到终点为止的任何一条路,没有形成过闭环,那么这张图就是有向无环图。

注意:
有向图中一个点经过两种路线到达另一个点未必形成环,因此有向无环图未必能转化成树,但任何有向树均为有向无环图。

8.3 RDD的切分

1. 如何划分job
   截止到一个action算子,就是一个job
   
2. 如何划分stage
   宽依赖算子就是stage的划分界限	
   
3. 一个stage里有多个task(一个分区对应一个task).这些task形成了taskSet,这些taskSet会被taskScheduler分配到executor上执行。   


注意:行动算子一旦结束,就会清空结果集以及DAG依赖关系。

8.4 WEBUI的查看

参考视频或文档

//this对象在Driver上初始化的,因此this要传输到worker上,所以要实现序列化
rdd.filter(isMatch)
}

def getMatchesField(rdd: RDD[String]): RDD[String] = { 
	    //filter在worker上运行,形参中有query变量,而query是this对象的,因此
	    //this也也要传输到worker上,所以this需要序列化
		rdd.filter(x => x.contains(query)) 
}
//注意:此方法的初始化在driver中,方法的局部变量就会被初始化,所以query_的值是this的query的值
//运行时,在worker上,因此this对象不需要进行网络传输。
def getMatches(rdd: org.apache.spark.rdd.RDD[String]):
	org.apache.spark.rdd.RDD[String] = { 
	// 安全:只把我们需要的字段拿出来放入局部变量中
		val query_ = this.query    
		rdd.filter(x => x.contains(query_))
} 

}
总结:-- 自定义的类如果涉及到网络IO,那么需要实现序列化接口java.io.Serializable
– 如果是类的变量在算子中直接被使用,不想进行对象的网络传输,可以提前将类的变量的值赋值给局部变量


# 八、深入了解Spark

## 8.1 依赖关系

```scala
RDD间的依赖关系,在Spark中分两种,分别如下:
- 窄依赖(NarrowDependency)
	父RDD中的一个分区对应子RDD中的一个分区。父RDD是子RDD的窄依赖。
 	这个转换过程中的算子也可以称之为窄依赖算子,比如有map、filter、flagMap、

	总结:窄依赖我们形象的比喻为**独生子女**

- 宽依赖(wideDependenCy,也叫ShuffleDependency)
	父RDD中的一分区对应子RDD中的多个分区。父RDD是子RDD的宽依赖。
	这个转换过程中的算子也可以称之为宽依赖算子,比如有reduceByKey,groupByKey等
	总结:宽依赖我们形象的比喻为**超生**

- 血统(Lineage)
  多个RDD之间的关系构成了一个血缘(血统)关系。这样的关系在保存在每一个RDD的结构中。这样的好处是,当某一个算子在计算上一个RDD中分区数据时,如果发现分区数据丢失,则会先看数据是否已经缓存,如果有直接读取,如果没有,再查看是否做了检查点机制,如果有就从检查点读取,如果没有,则从上一个父RDD重新计算。这样就大大提高了计算效率,不需要从头计算。  这也叫血统容错。

- 集群容错
   excutor宕机了,worker会重新启动一个新的excutor来完成剩下没有完成的task.
   worker宕机了,master会将此worker没有完成task重新分配给另外的worker上进行计算

8.2 有向无环图

1. 从一个节点出发到终点为止的任何一条路,没有形成过闭环,那么这张图就是有向无环图。

注意:
有向图中一个点经过两种路线到达另一个点未必形成环,因此有向无环图未必能转化成树,但任何有向树均为有向无环图。

8.3 RDD的切分

1. 如何划分job
   截止到一个action算子,就是一个job
   
2. 如何划分stage
   宽依赖算子就是stage的划分界限	
   
3. 一个stage里有多个task(一个分区对应一个task).这些task形成了taskSet,这些taskSet会被taskScheduler分配到executor上执行。   


注意:行动算子一旦结束,就会清空结果集以及DAG依赖关系。

8.4 WEBUI的查看

参考视频或文档
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值