一 Spark快速入门
1 spark官网
https://spark.apache.org/
2 Spark介绍
Apache Spark 是一个快速的,通用的集群计算系统。它对 Java,Scala,Python 和 R 提供了的高层 API,并有一个经优化的支持通用执行图计算的引擎。它还支持一组丰富的高级工具,包括用于 SQL 和结构化数据处理的 Spark SQL,用于机器学习的 MLlib,用于图计算的 GraphX 和 Spark Streaming。
spark官网地址:http://spark.apache.org
Spark是一个通用的可扩展的处理海量数据集的计算引擎。
2.1 Spark特点
2.1.1 快
相比给予MR,官方说,基于内存计算spark要快mr100倍,基于磁盘计算spark要快mr10倍。
快的原因:
- 基于内存计算
- 计算和数据的分离
- 基于DAGScheduler的计算划分
- 只有一次的Shuffle输出操作
2.1.2 易用
Spark提供超过80多个高阶算子函数,来支持对数据集的各种各样的计算,使用的时候,可以使用java、scala、python、R,非常灵活易用。
df = spark.read.json("logs.json")
df.where("age > 21")
.select("name.first")
.show()
2.1.3 通用
2.1.4 随处运行
2.1.5 总结
什么是Spark呢?它就是一个集成离线计算,实时计算,SQL查询,机器学习,图计算为一体的通用的计算框架。
何为通用?就是在一个项目中,既可以使用离线计算,也可以使用其他比如,SQL查询,机器学习,图计算等等,而这时Spark最最最强大的优势,没有之一。
而这一切的基础是SparkCore,速度比传统的mr快的原因就是基于内存的计算。
Spark开发过程中,使用到的模型——RDD(Resilient Distributed Dataset, 弹性式分布式数据集),在编程中起到了非常重要的作用。
2.2 RDD
何为RDD,其实RDD就是一个不可变的scala集合。
Spark的核心概念就是RDD,指的是一个只读的,可分区的分布式数据集,这个数据的全部或者部分可以缓存在内存中,在多次计算间被重用。
RDD在抽象来说是一种元素集合,包含了数据。他是被分区的,分为多个分区,每个分区分布在集群中的不同worker节点上面,从而让RDD中的数据可以被并行操作。
RDD通常通过Hadoop上的文件,即HDFS文件或者Hive表来进行创建;也可以通过RDD的本地创建转换而来。
传统的MapReduce虽然具有自动容错、平衡负载和可拓展性的优点,但是其最大缺点是采用非循环式的数据流模型,使得在迭代计算式要进行大量的磁盘IO操作。RDD正式解决这个缺点的抽象方法。
RDD最重要的特性就是,提供了容错性,可以自动从节点失败中恢复过来。即如果某个节点上的RDD Partition,因为节点故障,导致数据丢失,那么RDD会自动通过自己的数据来源重新计算该Partition。这一切对使用者是透明的,这一切的背后工作都是通过RDD的lineage特性来实现的。
RDD的数据默认情况下是存放在内存中的,但是内存资源不足的时候,Spark会自动将RDD数据溢出到磁盘(弹性)。
2.3 RDD特点
通过上述的描述,我们可以从以下几个方面来描述RDD
- 弹性:如果内存充足,那集合数据的存储和计算,就都在内存中完成;如果内存不足,需要有一部分数据溢出到磁盘,然后在磁盘完成存储和计算。
- 分布式:就和之前学习的分布式概念一样,一个集合的数据被拆分成多个部分,这每一个部分被称之为一个分区partition,还是一个scala的不可变的集合。默认情况下,partition是和hdfs中data-block块对应的,spark加载hdfs文件时,一个data-block块对应一个partition。
所以,对RDD的操作,本质上是对着每一个RDD对应分区partition的操作。
- 数据集:存放数据的集合
而Spark就是对这个RDD及其集合功能算子的实现。 RDD,弹性式分布式数据集,是Spark的第一代编程模型,spark预计将在3.0中让rdd光荣退休,转而使用Dataset来完成相应的功能。 说白了RDD就是一个抽象数据类型:ADT。
- RDD之间是存在依赖关系的
这些RDD之间的依赖关系,就形成了一个RDD的有向无环图DAG,RDD串儿,称之为RDD血缘关系或者血统,因为lineage。 依赖关系呢,分为了两种:窄依赖和宽依赖。具体我们会在spark stage阶段划分的时候进行具体说明。
- 移动计算优于移动数据
partition提供的最佳计算位置,利于数据处理的本地化即计算向数据移动而不是移动数据
- 总结
说白了这种分布式的计算,必然开始的时候,数据和计算它的代码是分开的,要想完成计算,要么数据到计算的位置上去,要么计算到数据的位置上去,综合大数据特点,spark选择计算到数据的位置上去更优(移动计算优于移动数据)。 基于数据和计算的距离,分为了如下几个级别: PROCESS_LOCAL:进程级别,数据和计算在同一个进程中 NODE_LOCAL: 节点级别,数据和计算在不同节点或者同一节点的不同进程 RACK_LOCAL:机架,说白了就是同一机架上 ANY:任意级别
2.4 RDD在Spark中的地位和作用
- 为什么会有Spark
因为传统的并行计算模型无法有效的进行交互式计算;二Spark的使命便是解决这两个问题,这也是它存在的价值和理由。
- Spark如何解决迭代计算
其主要实现思想就是RDD,把所有计算的数据保存在分布式的内存中。迭代计算通常情况下都是对同一个数据集做反复的迭代计算,数据在内存中将大大提升IO操作。这也是Spark设计的核心:内存计算。
- Spark如何实现交互式计算
因为Spark是用scala语言实现的,Spark和scala能够紧密的集成。所以Spark可以完美的运用scala的解释器,使得其中的scala可以向操作本地集合对象一样轻松的操作分布式数据集。
- Spark和RDD的关系
可以理解为:RDD是一种具有容错性,基于内存的集群计算抽象方法,Spark则是这个抽象方法的实现。
3 安装spark
3.0 安装说明
使用的Spark的版本是2.2.0,最新的版本应该2.4.4。
下载地址:https://archive.apache.org/dist/spark/spark-2.2.0/
提供的安装包:
spark-2.2.0.tgz ---->源码包
spark-2.2.0-bin-hadoop2.7.tgz ---->安装包
3.1 windows安装
1.解压安装包(和linux使用的同一个安装包即可)
2.配置环境变量即可
3.2 测试windows版的spark
3.2.1 代码
1. 运行bin目录spark-shell2.cmd
2. 执行
scala> sc.textFile("i://wc.txt",1).flatMap(_.split("\\s+")).map((_,1)).reduceByKey(_+_).foreach(println)
(hehe,1)
(hello,4)
(xxx,1)
(everybody,1)
(world,1)
3. 通过:ip:4040去查看你的webui
3.2.2 同时观察到了web-ui的变化
能够得出的基本结论是什么?
- Spark的application,可以有非常多的job作业,和mr不同,一个应用就提交一个job就行。
- job的执行,好像得需要某些操作触发,否则不会执行,触发的操作就是spark作业执行的动因。
- spark job作业的执行是分stage阶段的
- spark job作业的执行stage阶段形成了一个stage的DAG有向无环图
3.3 spark的全分布式安装
3.3.0 前提
1. JAVA_HOME/SCALA_HOME
2. HADOOP_HOME
3.3.1 安装scala
[root@lihl01 software]# tar -zxvf scala-2.11.8.tgz -C /opt/apps/
[root@lihl01 scala-2.11.8]# vi /etc/profile
#envrioment
export JAVA_HOME=/opt/apps/jdk1.8.0_45
export HADOOP_HOME=/opt/apps/hadoop-2.6.0-cdh5.7.6
export SCALA_HOME=/opt/apps/scala-2.11.8
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export PATH=$PATH:$JAVA_HOME/bin:$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$SCALA_HOME/bin
3.3.2 安装spark
//1. 解压以及配置环境变量
[root@lihl01 software]# tar -zxvf spark-2.2.0-bin-hadoop2.7.tgz -C /opt/apps/
[root@lihl01 apps]# mv spark-2.2.0-bin-hadoop2.7/ spark-2.2.0
[root@lihl01 scala-2.11.8]# vi /etc/profile
#envrioment
export JAVA_HOME=/opt/apps/jdk1.8.0_45
export HADOOP_HOME=/opt/apps/hadoop-2.6.0-cdh5.7.6
export SCALA_HOME=/opt/apps/scala-2.11.8
export SPARK_HOME=/opt/apps/spark-2.2.0
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export PATH=$PATH:$JAVA_HOME/bin:$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$SCALA_HOME/bin
export PATH=$PATH:$SPARK_HOME/bin:$SPARK_HOME/sbin
//2. 修改spark的配置
[root@lihl01 conf]# mv spark-env.sh.template spark-env.sh
[root@lihl01 conf]# mv slaves.template slaves
//2.1 从机的ip
[root@lihl01 conf]# vi slaves
# A Spark Worker will be started on each of the machines listed below.
lihl01
lihl02
lihl03
//2.2 启动环境
[root@lihl01 conf]# vi spark-env.sh
export JAVA_HOME=/opt/apps/jdk1.8.0_45
export SCALA_HOME=/opt/apps/scala-2.11.8
export SPARK_MASTER_IP=lihl01
export SPARK_MASTER_PORT=7077 ##rpc通信端口,类似hdfs的9000端口,不是50070
export SPARK_WORKER_CORES=2 ##每一个spark的worker从节点拥有几个cpu的core
export SPARK_WORKER_INSTANCES=1 ##每一台spark服务器上面拥有几个worker的实例
export SPARK_WORKER_MEMORY=1g ##每一个worker节点的内存资源
export HADOOP_CONF_DIR=/opt/apps/hadoop-2.6.0-cdh5.7.6/etc/hadoop
//3. 同步到其他两台节点
//3.1 同步scala
[root@lihl01 apps]# scp -r scala-2.11.8/ lihl02:/opt/apps/
[root@lihl01 apps]# scp -r scala-2.11.8/ lihl03:/opt/apps/
//3.2 同步spark
[root@lihl01 apps]# scp -r spark-2.2.0/ lihl02:/opt/apps/
[root@lihl01 apps]# scp -r spark-2.2.0/ lihl03:/opt/apps/
//3.3 同步环境变量
[root@lihl01 apps]# scp -r /etc/profile lihl02:/etc/profile
[root@lihl01 apps]# scp -r /etc/profile lihl03:/etc/profile
//3.4 修改lihl01的启动和关闭spark集群的脚本名称,因为他和hadoop的启动脚本同名了
[root@lihl01 sbin]# mv start-all.sh start-all-spark.sh
[root@lihl01 sbin]# mv stop-all.sh stop-all-spark.sh
3.3.3 启动spark集群
[root@lihl01 sbin]# start-all-spark.sh
//2. 测试集群
http://192.168.49.111:8081/
//3. 停止集群
[root@lihl01 sbin]# stop-all-spark.sh
3.4 spark的HA安装
3.4.1 安装zookeeper集群:此处省略1w字
3.4.2 修改spark-env.sh
export JAVA_HOME=/opt/apps/jdk1.8.0_45
export SCALA_HOME=/opt/apps/scala-2.11.8
##export SPARK_MASTER_IP=lihl01
##export SPARK_MASTER_PORT=7077 ##rpc通信端口,类似hdfs的9000端口,不是50070
export SPARK_WORKER_CORES=2 ##每一个spark的worker从节点拥有几个cpu的core
export SPARK_WORKER_INSTANCES=1 ##每一台spark服务器上面拥有几个worker的实例
export SPARK_WORKER_MEMORY=1g ##每一个worker节点的内存资源
export HADOOP_CONF_DIR=/opt/apps/hadoop-2.6.0-cdh5.7.6/etc/hadoop
export SPARK_DAEMON_JAVA_OPTS="-Dspark.deploy.recoveryMode=ZOOKEEPER -Dspark.deploy.zookeeper.url=lihl01,lihl02,lihl03 -Dspark.deploy.zookeeper.dir=/spark"
3.4.3 分发并启动
[root@lihl01 conf]# scp spark-env.sh lihl02:$SPARK_HOME/conf/spark-env.sh
[root@lihl01 conf]# scp spark-env.sh lihl03:$SPARK_HOME/conf/spark-env.sh
[root@lihl01 logs]# zkServer.sh start
[root@lihl01 logs]# zkServer.sh start
[root@lihl01 logs]# zkServer.sh start
[root@lihl01 logs]# start-all-spark.sh
lihl02也启动master
[root@lihl02 sbin]# start-master.sh
3.4.4 查看两个master的状态
4 Spark的常用组件介绍
1. ClusterManager
ClusterManager:在Standalone(上述安装的模式,也就是依托于spark集群本身)模式中即为Master(主节点),控制整个集群,监控Worker。在YARN模式中为资源管理器ResourceManager(国内spark主要基于yarn集群运行,欧美主要基于mesos来运行)。
2. Worker
Worker:从节点,负责控制计算节点,启动Executor。在YARN模式中为NodeManager,负责计算节点的控制,启动的进程叫Container。
3. Driver
运行Application的main()函数并创建SparkContext(是spark中最重要的一个概念,是spark编程的入口,作用相当于mr中的Job)。
4. Executor
执行器,在worker node上执行任务的组件、用于启动线程池运行任务。每个Application拥有独立的一组Executors。
5.SparkContext
整个应用的上下文,控制应用的生命周期,是spark编程的入口。
6.RDD
Spark的基本计算单元,一组RDD可形成执行的有向无环图RDD Graph。
RDD是弹性式分布式数据集,理解从3个方面去说:弹性、数据集、分布式。
是Spark的第一代的编程模型。
7. DAGScheduler
实现将Spark作业分解成一到多个Stage,每个Stage根据RDD的Partition个数决定Task的个数,然后生成相应的Task set放到TaskScheduler中。
8. TaskScheduler
将任务(Task)分发给Executor执行
9. Stage
一个Spark作业一般包含一到多个Stage。
10. Task
一个Stage包含一到多个Task,通过多个Task实现并行运行的功能。
task的个数由rdd的partition分区决定,spark是一个分布式计算程序,所以一个大的计算任务,就会被拆分成多个小的部分,同时进行计算。
11. Transformations
转换(Transformations) (如:map, filter, groupBy, join等),Transformations操作是Lazy的,也就是说从一个RDD转换生成另一个RDD的操作不是马上执行,Spark在遇到Transformations操作时只会记录需要这样的操作,并不会去执行,需要等到有Actions操作的时候才会真正启动计算过程进行计算。
12. Actions
操作/行动(Actions)算子 (如:count, collect, foreach等),Actions操作会返回结果或把RDD数据写到存储系统中。Actions是触发Spark启动计算的动因。
13. SparkEnv
线程级别的上下文,存储运行时的重要组件的引用。SparkEnv内创建并包含如下一些重要组件的引用。
14. MapOutPutTracker
负责Shuffle元信息的存储。
15. BroadcastManager
负责广播变量的控制与元信息的存储。
16. BlockManager
负责存储管理、创建和查找块。
17.MetricsSystem
监控运行时性能指标信息。
18. SparkConf
负责存储配置信息。作用相当于hadoop中的Configuration。
5 官网的组件调度说明
Spark应用程序在群集上作为独立的进程集运行,由主程序(称为Driver Program)中的SparkContext对象协调。
具体来说,要在集群上运行,SparkContext可以连接到几种类型的集群管理器(Spark自己的独立集群管理器Mesos或YARN),它们可以在应用程序之间分配资源。 连接后,Spark会在集群中的节点上获取执行程序,这些节点是运行计算并为您的应用程序存储数据的进程。 接下来,它将您的应用程序代码(由传递给SparkContext的JAR或Python文件定义)发送给Executor。 最后,SparkContext将任务发送给Executor以运行。
关于此体系结构,有几点有用的注意事项:
每个应用程序都有其自己的Executor程序进程,该进程在整个应用程序期间保持不变,并在多个线程中运行任务。这样的好处是可以在调度方面(每个Driver程序调度自己的任务)和Executor方面(来自不同应用程序的任务在不同JVM中运行)彼此隔离应用程序。但是,这也意味着,如果不将数据写入外部存储系统,则无法在不同的Spark应用程序(SparkContext实例)之间共享数据。
Spark与基础群集管理器无关。只要它可以获取执行程序进程,并且它们彼此通信,即使在还支持其他应用程序(例如Mesos / YARN)的集群管理器上运行它也相对容易。
Driver程序在其整个生命周期内必须侦听并接受其执行程序的传入连接(例如,请参见网络配置部分中的spark.driver.port)。这样,Driver程序必须是可从Worker节点访问的网络。
由于驱动程序在群集上调度任务,因此应在Worker节点附近运行,最好在同一局域网上运行。如果您想将请求远程发送到集群,最好是在驱动程序中打开RPC,并让它在附近提交操作,而不是在远离工作节点的地方运行驱动程序。
二 Spark核心编程
1 创建idea的聚合工程(+++++)
1.1 创建一个maven的父工程
1.1.1 修改本地的maven工具
1.1.2 删除父工程的src目录
1.1.3 修改pom.xml
4.0.0
cn.lihl.spark
day34-spark-parent
1.0-SNAPSHOT
pom
1.2 在父工程中创建子模块
1.2.1 创建子模块
1. 自己手动的创建scala目录,并设置为可编写代码的颜色
2. 需要自定删除global lib的scala sdk,然后重新添加
1.3 导入依赖管理
1.3.1 parent的父工程
4.0.0
cn.lihl.spark
day34-spark-parent
1.0-SNAPSHOT
spark-core
spark-sql
pom
2.11.8
2.2.0
org.scala-lang
scala-library
${scala-version}
org.apache.spark
spark-core_2.11
${spark-version}
1.3.2 spark-common工程
day34-spark-parent
cn.lihl.spark
1.0-SNAPSHOT
4.0.0
spark-common
jar
org.scala-lang
scala-library
1.3.3 spark-core工程
day34-spark-parent
cn.lihl.spark
1.0-SNAPSHOT
4.0.0
spark-core
jar
cn.lihl.spark
spark-common
1.0-SNAPSHOT
org.apache.spark
spark-core_2.11
2 wordcount案例
2.1 Java的原始代码来完成wordcount
package cn.lihl.spark.core.day1;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.FlatMapFunction;
import org.apache.spark.api.java.function.Function2;
import org.apache.spark.api.java.function.PairFunction;
import org.apache.spark.api.java.function.VoidFunction;
import scala.Tuple2;
import java.util.Arrays;
import java.util.Iterator;
public class Demo1_Java {
public static void main(String[] args) {
//1. 获取到入口
SparkConf conf = new SparkConf();
conf.setAppName(Demo1_Java.class.getSimpleName());
conf.setMaster("local[*]");
JavaSparkContext context = new JavaSparkContext(conf);
//2. 加载文件
JavaRDD lineRDD = context.textFile("i://wc.txt");
System.out.println("lineRDD" + "--------------------------------------------");
lineRDD.foreach(new VoidFunction() {
public void call(String s) throws Exception {
System.out.println(s);
}
});
//3. 切割
JavaRDD wordRDD = lineRDD.flatMap(new FlatMapFunction() {
public Iterator call(String line) throws Exception {
return Arrays.asList(line.split("\\s+")).iterator();
}
});
System.out.println("wordRDD" + "--------------------------------------------");
wordRDD.foreach(new VoidFunction() {
public void call(String s) throws Exception {
System.out.println(s);
}
});
//4. 生成(word,1)
JavaPairRDD retRDD = wordRDD.mapToPair(new PairFunction() {
public Tuple2 call(String word) throws Exception {
Tuple2 tuple2 = new Tuple2(word, 1);
return tuple2;
}
});
System.out.println("retRDD" + "--------------------------------------------");
retRDD.foreach(new VoidFunction>() {
public void call(Tuple2 tuple2) throws Exception {
System.out.println(tuple2._1 + "-->" + tuple2._2);
}
});
//5. 聚合:reduceByKey
JavaPairRDD wordCountRDD = retRDD.reduceByKey(new Function2() {
public Integer call(Integer v1, Integer v2) throws Exception {
return v1 + v2;
}
});
System.out.println("wordCountRDD" + "--------------------------------------------");
wordCountRDD.foreach(new VoidFunction>() {
public void call(Tuple2 tuple2) throws Exception {
System.out.println(tuple2._1 + "-->" + tuple2._2);
}
});
}
}
2.2 Java lambda表达式
//1. 修改idea的编译器版本
package cn.lihl.spark.core.day1;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.FlatMapFunction;
import org.apache.spark.api.java.function.Function2;
import org.apache.spark.api.java.function.PairFunction;
import org.apache.spark.api.java.function.VoidFunction;
import scala.Tuple2;
import java.util.Arrays;
import java.util.Iterator;
public class Demo2_Lamda {
public static void main(String[] args) {
//1. 获取到入口
SparkConf conf = new SparkConf();
conf.setAppName(Demo2_Lamda.class.getSimpleName());
conf.setMaster("local[*]");
JavaSparkContext context = new JavaSparkContext(conf);
//2. 加载文件
JavaRDD lineRDD = context.textFile("i://wc.txt");
System.out.println("lineRDD" + "--------------------------------------------");
lineRDD.foreach(s -> System.out.println(s));
//3. 切割
JavaRDD wordRDD = lineRDD.flatMap(line -> Arrays.asList(line.split("\\s+")).iterator());
System.out.println("wordRDD" + "--------------------------------------------");
wordRDD.foreach(s -> System.out.println(s));
//4. 生成(word,1)
JavaPairRDD retRDD = wordRDD.mapToPair(word -> new Tuple2(word, 1));
System.out.println("retRDD" + "--------------------------------------------");
retRDD.foreach(tuple2 -> System.out.println(tuple2._1 + "-->" + tuple2._2));
//5. 聚合:reduceByKey
JavaPairRDD wordCountRDD = retRDD.reduceByKey((Integer v1, Integer v2) -> v1 + v2);
System.out.println("wordCountRDD" + "--------------------------------------------");
wordCountRDD.foreach(tuple2 -> System.out.println(tuple2._1 + "-->" + tuple2._2));
}
}
2.3 scala
package cn.lihl.spark.core.day1
import org.apache.spark.{SparkConf, SparkContext}
object Demo1_Scala {
def main(args: Array[String]): Unit = {
//1. 获取到入口
val sparkContext = new SparkContext(
new SparkConf()
.setAppName(Demo1_Scala.getClass.getSimpleName)
.setMaster("local[*]")
)
//2. 加载文件,flatmap,map,reducebyKey
sparkContext.textFile("i://wc.txt").flatMap(_.split("\\s+")).map((_,1)).reduceByKey(_+_).foreach(println)
}
}
3 Master URL说明(++++)
首先在编程过程中,至少需要给spark程序传递一个参数master-url,通过sparkConf.setMaster来完成。改参数,代表的是spark作业的执行方式,或者指定的spark程序的cluster-manager的类型。
master | 含义 |
---|---|
local | 程序在本地运行,同时为本地程序提供一个线程来处理 |
local[M] | 程序在本地运行,同时为本地程序分配M个工作线程来处理 |
local[*] | 程序在本地运行,同时为本地程序分配机器可用的CPU core的个数工作线程来处理 |
local[M, N] | 程序在本地运行,同时为本地程序分配M个工作线程来处理,如果提交程序失败,会进行最多N次的重试 |
spark://ip:port | 基于standalone的模式运行,提交撑到ip对应的master上运行 |
spark://ip1:port1,ip2:port2 | 基于standalone的ha模式运行,提交撑到ip对应的master上运行 |
yarn/启动脚本中的deploy-mode配置为cluster | 基于yarn模式的cluster方式运行,SparkContext的创建在NodeManager上面,在yarn集群中 |
yarn/启动脚本中的deploy-mode配置为client | 基于yarn模式的client方式运行,SparkContext的创建在提交程序(文件块所在的)的那台机器上面,不在yarn集群中 |
4 修改日志级别
4.1 log4j.properties
# 基本日志输出级别为INFO,输出目的地为console
log4j.rootCategory=INFO, console
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.target=System.err
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %p %c{1}: %m%n
# 输出配置的是spark提供的webui的日志级别
log4j.logger.org.spark_project.jetty=INFO
log4j.logger.org.spark_project.jetty.util.component.AbstractLifeCycle=ERROR
log4j.logger.org.apache.spark.repl.SparkIMain$exprTyper=INFO
log4j.logger.org.apache.spark.repl.SparkILoop$SparkILoopInterpreter=INFO
log4j.logger.org.apache.parquet=ERROR
log4j.logger.parquet=ERROR
4.2 提取了LoggerTrait的特质
package cn.lihl.spark.core.utils
import org.apache.log4j.{Level, Logger}
trait LoggerTrait {
Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
Logger.getLogger("org.apache.hadoop").setLevel(Level.WARN)
Logger.getLogger("org.spark_project").setLevel(Level.WARN)
}
4.3 想要使用日志处理的类中继承这个特质即可
package cn.lihl.spark.core.day1
import cn.lihl.spark.core.utils.LoggerTrait
import org.apache.spark.{SparkConf, SparkContext}
object Demo1_Scala extends LoggerTrait{
def main(args: Array[String]): Unit = {
//1. 获取到入口
val sparkContext = new SparkContext(
new SparkConf()
.setAppName(Demo1_Scala.getClass.getSimpleName)
.setMaster("local[*]")
)
//2. 加载文件,flatmap,map,reducebyKey
sparkContext.textFile("i://wc.txt")
.flatMap(_.split("\\s+"))
.map((_,1)).reduceByKey(_+_)
.foreach(println)
}
}
4.4 解决编译版本重置5的问题
4.4.1 properties的方式解决
parent工程中添加properties的配置即可
2.11.8
2.2.0
1.2.17
UTF-8
1.8
1.8
4.4.2 插件方式解决
org.apache.maven.plugins
maven-compiler-plugin
${java-compiler-version}
5 将jar包移动到服务器中执行(+++++)
5.1 读取hdfs中的文件
object Demo1_Scala extends LoggerTrait{
def main(args: Array[String]): Unit = {
//1. 获取到入口
val sparkContext = new SparkContext(
new SparkConf()
.setAppName(Demo1_Scala.getClass.getSimpleName)
.setMaster("local[*]")
)
//2. 加载文件,flatmap,map,reducebyKey
sparkContext.textFile("hdfs://192.168.49.111:9000/words/azkaban.txt")
.flatMap(_.split("\\s+"))
.map((_,1)).reduceByKey(_+_)
.foreach(println)
}
}
如果是高可用,可能出现如下问题:
java.lang.IllegalArgumentException: java.net.UnknownHostException: ns1
解决方案,最简单的就是将hadoop的两个配置文件core-site.xml和hdfs-site.xml添加到项目的classpath中即可。
但是此时在此基础之上,将textFile去加载本地的文件sc.textFile("E:/data/hello.txt")
则会报错:java.lang.IllegalArgumentException: Pathname /E:/data/hello.txt from hdfs://ns1/E:/data/hello.txt is not a valid DFS filename
原因就在于,已经在classpath下面加载了hdfs-site.xml和core-site.xml的配置文件,则会自动理解输入的文件路径为hdfs的,自然会报错。所以,在此情况下还想加载本地文件,那么就告诉机器以本地文件的格式或者协议读取即可
val linesRDD:RDD[String] = sc.textFile("file:/E:/data/hello.txt")
5.2 提交spark程序到集群中
5.2.1 parent中添加打包插件
4.0.0
cn.lihl.spark
day34-spark-parent
1.0-SNAPSHOT
spark-core
spark-sql
spark-common
pom
2.11.8
2.2.0
1.2.17
1.8
2.15.2
3.2.0
UTF-8
1.8
1.8
org.scala-lang
scala-library
${scala-version}
org.apache.spark
spark-core_2.11
${spark-version}
log4j
log4j
${log4j-version}
org.apache.maven.plugins
maven-compiler-plugin
${java-compiler-version}
maven-assembly-plugin
${assembly-plugin-version}
jar-with-dependencies
make-assembly
package
single
org.scala-tools
maven-scala-plugin
${scala-tools-version}
compile
testCompile
${scala-version}
-target:jvm-1.8
5.2.2 core
day34-spark-parent
cn.lihl.spark
1.0-SNAPSHOT
4.0.0
spark-core
jar
cn.lihl.spark
spark-common
1.0-SNAPSHOT
org.apache.spark
spark-core_2.11
provided
src/main/scala
maven-assembly-plugin
jar-with-dependencies
make-assembly
package
single
org.scala-tools
maven-scala-plugin
compile
testCompile
-target:jvm-1.8
5.2.3 common
day34-spark-parent
cn.lihl.spark
1.0-SNAPSHOT
4.0.0
spark-common
jar
org.scala-lang
scala-library
provided
log4j
log4j
provided
src/main/scala
maven-assembly-plugin
jar-with-dependencies
make-assembly
package
single
org.scala-tools
maven-scala-plugin
compile
testCompile
-target:jvm-1.8
5.2.4 打包,然后将core的jar包上传到服务器即可
5.3 提交
5.3.1 重构代码
object Demo1_Scala extends LoggerTrait{
def main(args: Array[String]): Unit = {
//0. 处理参数
if (args == null || args.length != 1) {
return
"""
|Usage
|""".stripMargin
}
//1. 获取到入口
val sparkContext = new SparkContext(
new SparkConf()
.setAppName(Demo1_Scala.getClass.getSimpleName)
.setMaster("spark://192.168.49.111:7077")
// .setMaster("local[*]")
)
//2. 加载文件,flatmap,map,reducebyKey
sparkContext.textFile("hdfs://192.168.49.111:9000/" + args(0))
.flatMap(_.split("\\s+"))
.map((_,1)).reduceByKey(_+_)
.foreach(println)
//3. 释放资源
sparkContext.stop()
}
}
5.3.2 使用spark-shell连接到master
执行
[root@lihl01 jars]# spark-shell --master spark://lihl01:7077
scala> sc.textFile("/words/azkaban.txt").flatMap(_.split("\\s+")).map((_,1)).reduceByKey(_ + _).foreach(println)
scala>
查看结果
1. 得到你的spark的集群的安装目录下work目录,查找对应的app,在app下面查看日志文件:
StdErr : 错误
Stdout : 标准输出
2. collect的action算子将worker的数据拷贝到本地的数组中
scala> val retRDD = sc.textFile("/words/azkaban.txt").flatMap(_.split("\\s+")).map((_,1)).reduceByKey(_ + _)
retRDD: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[4] at reduceByKey at :24
scala> val array = retRDD.collect
array: Array[(String, Int)] = Array((22,4), (33,1), (11,4))
scala> array.foreach(println)
(22,4)
(33,1)
(11,4)
tip:
这种做法在生产环境中是严格杜绝的,因为我们大数据的理念,移动算法/计算,不移动数据
参考流程
1. 通过命令连接到spark的master
[root@hbase1 home]# spark-shell --master spark://hbase1:7077
2. 观察zookeeper产生进程信息
ls /spark/master_status
[worker_worker-20200409173835-192.168.49.201-39142, worker_worker-20200409173835-192.168.49.202-36402, worker_worker-20200409173832-192.168.49.200-35849, app_app-20200409175024-0000]
3. 通过jps观察进程,发现多台机器中都有如下进程,因为spark要占用多个core
[root@hbase3 ~]# jps
1238 DataNode
1435 CoarseGrainedExecutorBackend
1467 Jps
1326 QuorumPeerMain
1375 Worker
4. 查询对应的有spark进程的目录发现如下:
[root@hbase3 ~]# cd /opt/apps/spark-2.2.0/work/
[root@hbase3 work]# ll
总用量 0
drwxr-xr-x. 5 root root 33 4月 8 22:07 app-20200408220322-0000
drwxr-xr-x. 3 root root 15 4月 9 17:50 app-20200409175024-0000
5. 发现日志
[root@hbase3 work]# cd app-20200409175024-0000/
[root@hbase3 app-20200409175024-0000]# ll
总用量 0
drwxr-xr-x. 2 root root 34 4月 9 17:50 0
[root@hbase3 app-20200409175024-0000]# cd 0
[root@hbase3 0]# ll
总用量 4
-rw-r--r--. 1 root root 3736 4月 9 17:50 stderr
-rw-r--r--. 1 root root 0 4月 9 17:50 stdout
6. 切回到saprk shell登录的界面执行之前的代码
import org.apache.spark.{SparkConf, SparkContext}
val conf = new SparkConf().setAppName("SparkWordCount")
val ret = sc.textFile("/wc.txt").flatMap(_.split("\\s+")).map((_,1)).reduceByKey(_ + _)
ret.foreach(println(_))
7. 在指定的日志中查看结果
cat
[root@hbase1 1]# cat stdout
(cccc,3)
(aa,2)
(ss,1)
(aaaa,2)
(dd,2)
(bbbb,2)
(bb,1)
(cc,2)
(cccc,3)
(aa,2)
(ss,1)
(aaaa,2)
(dd,2)
(bbbb,2)
(bb,1)
(cc,2)
8. 因为现在的具体的操作是交给worker执行的,所以要把结果数据拉取到本地
scala> val array = ret.collect
array: Array[(String, Int)] = Array((cccc,3), (aa,2), (ss,1), (aaaa,2), (dd,2), (bbbb,2), (bb,1), (cc,2))
scala> array.foreach(println)
5.3.3 提交jar包
//1. 编写脚本用于提交我们的spark程序
[root@lihl01 home]# vi spark-submit-wc.sh
#!/bin/bash
path=$1
SPARK_HOME=/opt/apps/spark-2.2.0
echo "spark local application start -------------------------"
$SPARK_HOME/bin/spark-submit \
--master local \
--class cn.lihl.spark.core.day2.Demo1_Scala \
/home/wc.jar $path
echo "spark local application end -------------------------"
//2. 授权
[root@lihl01 home]# chmod +x spark-submit-wc.sh
//3. 提交执行
[root@lihl01 home]# ./spark-submit-wc.sh /words/azkaban.txt
5.3.4 local模式
#!/bin/bash
path=$1
SPARK_HOME=/opt/apps/spark-2.2.0
echo "spark local application start -------------------------"
echo $path
$SPARK_HOME/bin/spark-submit \
--master local \
--class cn.lihl.spark.core.day2.Demo1_Scala \
--deploy-mode client \
/home/wc.jar \
$path
echo "spark local application end -------------------------"
tip:
./spark-submit-wc.sh /words/azkaban.txt
5.3.5 standalone模式
#!/bin/bash
path=$1
SPARK_HOME=/opt/apps/spark-2.2.0
echo "spark local application start -------------------------"
echo $path
$SPARK_HOME/bin/spark-submit \
--master spark://lihl01:7077 \
--class cn.lihl.spark.core.day2.Demo1_Scala \
--deploy-mode client \
--total-executor-cores 2 \
--executor-cores 1 \
--executor-memory 600M \
/home/wc.jar \
$path
echo "spark local application end -------------------------"
tip:
./spark-submit-wc.sh /words/azkaban.txt
5.3.6 yarn模式来运行
#!/bin/bash
jar_path=$1
hdfs_path=$2
SPARK_HOME=/opt/apps/spark-2.2.0
echo "spark local application start -------------------------"
echo $path
$SPARK_HOME/bin/spark-submit \
--master yarn \
--class cn.lihl.spark.core.day2.Demo1_Scala \
--deploy-mode client/cluster \
--executor-cores 1 \
--num-executors 1 \
--executor-memory 600M \
$jar_path \
$hdfs_path
echo "spark local application end -------------------------"
tip:
我们的运行结果不在spark/work下去寻找,而应该是到hadoop/log/userlogs/。因为现在使用yarn平台来执行我们的spark程序,所以产生的日志应该在hadoop的日志中
5.3.6 小结
1. 申明代码
1.1 要写参数判定的逻辑
1.2 释放资源
2. 打包
2.1 一定得将我们的需要依赖的第三方jar包打包过去
2.2 服务器有的jar包我们一般都不需要打包
(导入打包插件)、(导入对scala进行打包的插件)
3. 将jar包上传到服务器中执行
3.1 输出结果是由master分配给某个worker来执行,所以结果在这个对用的worker的日志中
3.2 提交jar包使用spark-submit脚本来执行
4. 自定义个脚本
4.1 deploy-mode client 一定得指定,否则这个结果无法运行
5.3.7 一个疑问
在local的方式中,可以看到foreach的结果,但是client模式下面就看不到这个结果,这是为何?
算子操作都是在worker中执行的,包括foreach操作,所以要想看结果就应该去worker上面查看,而不是提交作业的本机。
[图片上传失败...(image-76f72b-1607674046573)]
三 shell脚本几个变量对比
#!/bin/bash
echo '$0--->'$0
echo '$1--->'$1
echo '$2--->'$2
echo '$#--->'$#
echo '$?--->'$?
echo '$@--->'$@
$0 ./echo_shell.sh
$1 aaa
$2 bbb
$# 5
$? 0
$@ aaa bbb ccc ddd eee
./echo_shell.sh aaa bbb ccc ddd eee
四 Spark编程
1 算子
广义的讲,对任何函数进行某一项操作都可以认为是一个算子,甚至包括求幂次,开方都可以认为是一个算子
tip:
一般在函数式编程中,我们都把以前的函数视作为算子
2 wordcount的案例的代码执行流程
2.1 流程图
2.2 小结
1. 宽依赖/窄依赖
2. 有向无环图(DAG)
3. lineage(血缘)
4. 有shuffle必产生宽依赖
五 Spark Core 的RDD操作
1
2 Transformation算子(+++++)
2.0 加载数据
textFile : 加载数据文件
parallelize:加载集合数据
2.1 map算子
rdd.map[T:ClassTag] (A => T):RDD[T],对rdd集合中的每一个元素,都作用一次该func函数,之后返回值为生成元素构成的一个新的RDD。
object Demo3_Transformation {
def main(args: Array[String]): Unit = {
/*
* 第一代:RDD(spark core)
* 第二代:DataFrame
* 第三代:DataSet
*/
//1. 获取到sparkcontext
val sc = new SparkContext(new SparkConf()
.setMaster("local[*]")
.setAppName(Demo3_Transformation.getClass.getSimpleName)
)
//2. 加载数据
//2.1 加载数据文件/本地集合
// sc.textFile() 加载数据文件
// sc.parallelize() 本地集合
val list:List[Int] = List(1, 2, 3, 4, 5, 6)
val listRDD: RDD[Int] = sc.parallelize(list)
//3. map
val value: RDD[Double] = listRDD.map(element => element.toDouble)
}
}
2.2 flatmap
rdd.flatMap(func):RDD ==>rdd集合中的每一个元素,都要作用func函数,返回0到多个新的元素,这些新的元素共同构成一个新的RDD。所以和上述map算子进行总结:
- map操作是一个one-2-one的操作
- flatMap操作是一个one-2-many的操作
val list:List[String] = List(
"11 14 155",
"12 15 159",
"13 16 136",
"13 13 159")
val listRDD: RDD[String] = sc.parallelize(list)
//4. flatmap
listRDD.flatMap(line => {
val wordArray: Array[String] = line.split("\\s+")
val phone = wordArray(2)
val up = wordArray(0).toInt
val down = wordArray(1).toInt
(phone -> (up, down, up + down)).productIterator
})
2.3 filter
- rdd.filter(func):RDD ==> 对rdd中的每一个元素操作func函数,该函数的返回值为Boolean类型,保留返回值为true的元素,共同构成一个新的RDD,过滤掉哪些返回值为false的元素。
val list:List[String] = List(
"11 14 155",
"12 15 159",
"13 16 136",
"13 13 159")
val listRDD: RDD[String] = sc.parallelize(list)
val fliterRDD: RDD[String] = listRDD.filter(str => str.contains("159"))
fliterRDD.foreach(println)
2.4 sample
rdd.sample(withReplacement:Boolean, fraction:Double [, seed:Long]):RDD ===> 抽样,需要注意的是spark的sample抽样不是一个精确的抽样。一个非常重要的作用,就是来看rdd中数据的分布情况,根据数据分布的情况,进行各种调优与优化。--->数据倾斜(dataskew),找到那些发生数据倾斜的key,sample算子+reduceByKey就可以知道哪一个key出现的次数最多,出现次数最多的key往往便是发生数据倾斜的key,紧接着便进行数据倾斜优化。
首先得要知道这三个参数是啥意思
withReplacement:抽样的方式,true有返回重复抽样, false为无返回重复抽样
fraction: 抽样比例,取值范围就是0~1
seed: 抽样的随机数种子,有默认值,通常也不需要传值
val list:List[String] = List(
"11 14 155",
"12 15 159",
"13 16 136",
"13 13 159",
"13 13 158",
"13 13 157",
"13 13 156",
"13 13 154",
"13 13 153",
"13 13 152")
val listRDD: RDD[String] = sc.parallelize(list)
val sampleRDD: RDD[String] = listRDD.sample(true, 0.2, 3)
sampleRDD.foreach(println)
2.5 union/++
rdd1.union(rdd2),联合rdd1和rdd2中的数据,形成一个新的rdd,其作用相当于sql中的union all。
2.5.1 sparkUtils
package cn.lihl.spark.utils
import org.apache.spark.{SparkConf, SparkContext}
object SparkUtils {
def getContext(appName:String):SparkContext = {
getContext(appName, "local[*]")
}
def getContext(appName:String, masterUrl:String):SparkContext = {
val sc = new SparkContext(
new SparkConf().setMaster(masterUrl).setAppName(appName)
)
sc
}
def stop(sparkContext: SparkContext) = {
if (null != sparkContext && !sparkContext.isStopped) {
sparkContext.stop()
}
}
}
tip:
我们在spark-common中申明的此类,因为以后spark-sql或者spark-streaming使用此类。要在spark-common中使用SparkContext类必须在spark-common中导入spark-core的依赖!!!!!!!!!!
2.5.2 union
package cn.lihl.spark.core.day3
import cn.lihl.spark.utils.{LoggerTrait, SparkUtils}
import org.apache.spark.rdd.RDD
object Demo4_Transformation extends LoggerTrait{
def main(args: Array[String]): Unit = {
//1. 获取到spark core的编程入口
val sc = SparkUtils.getContext("demo4")
//2. 创建两个数据
val left: List[Int] = List(1, 3, 5, 7, 9)
val right: List[Int] = List(2, 4, 6, 8, 10)
val leftRDD: RDD[Int] = sc.parallelize(left, 1)
val rightRDD: RDD[Int] = sc.parallelize(right, 1)
// val unionRDD: RDD[Int] = leftRDD.union(rightRDD) // 合并并排序
val unionRDD: RDD[Int] = leftRDD union rightRDD
// val unionRDD: RDD[Int] = leftRDD ++ rightRDD //合并不排序
unionRDD.foreach(println)
}
}
2.6 distinct
rdd元素去重
package cn.lihl.spark.core.day3
import cn.lihl.spark.utils.{LoggerTrait, SparkUtils}
import org.apache.spark.rdd.RDD
import scala.beans.BeanProperty
object Demo4_Transformation extends LoggerTrait{
def main(args: Array[String]): Unit = {
//1. 获取到spark core的编程入口
val sc = SparkUtils.getContext("demo4")
//2. 创建两个数据
val left: List[Int] = List(1, 3, 5, 7, 9)
val right: List[Int] = List(2, 4, 6, 8, 10)
val leftRDD: RDD[Int] = sc.parallelize(left, 1)
val rightRDD: RDD[Int] = sc.parallelize(right, 1)
//3. union
// val unionRDD: RDD[Int] = leftRDD.union(rightRDD) // 合并并排序
val unionRDD: RDD[Int] = leftRDD union rightRDD
// val unionRDD: RDD[Int] = leftRDD ++ rightRDD //合并不排序
unionRDD.foreach(println)
println("-"*80)
//4. distinct
var list:List[Int] = List(1, 3, 3, 4, 9)
val listRDD:RDD[Int] = sc.parallelize(list)
val distinctRDD: RDD[Int] = listRDD.distinct()
distinctRDD.foreach(println)
println("*"*80)
// 需求,判断两个person只要用户名和性别一样,就认为是一个人,在distinct中就能够去重
val p1: Person = new Person("lixi", "man")
val p2: Person = new Person("lixi", "man")
var plist:List[Person] = List(
p1,p2
)
val plistRDD: RDD[Person] = sc.parallelize(plist)
plistRDD.distinct.foreach(println)
}
}
class Person extends Serializable {
@BeanProperty var name:String = _
@BeanProperty var sex:String = _
def this(name:String, sex:String) = {
this
this.name = name
this.sex = sex
}
override def equals(obj: Any): Boolean = {
var bool = false
if (obj == null) return bool
val that = obj.asInstanceOf[Person]
if (this.name == that.name && this.sex == that.sex) {
bool = true
return bool
}
return bool
}
override def hashCode(): Int = this.name.hashCode + this.sex.hashCode
}
2.7 join
一、join就是sql中的inner join,join的效果工作7种。
从具体的写法上面有如下几种
- 交叉连接
A a accross join B b;这种操作方式会产生笛卡尔积,在工作中一定要避免。- 内连接
A a [inner] join B b [where|on a.id = b.id]; 有时候也写成:A a, B b(自连接) 是一种等值连接。所谓等值连接,就是获取A和B的交集。不等值连接格式如下:
A a inner join B b on a.id != b.id- 外连接
3.1 左外连接:以左表为主体,查找右表中能够关联上的数据,如果管理不上,显示null。
A a left outer join B b on a.id = b.id。
3.2 右外连接:是以右表为主体,查找左表中能够关联上的数据,如果关联不上,显示null。
A a right outer join B b on a.id = b.id。
3.3 全连接:就是左外连接+右外连接
A a full outer join B b on a.id = b.id。
3.4 半连接:一般在工作很少用二、 Spark:sparkcore中支持的连接有:笛卡尔积、内连接join,外连接(左、右、全)
- spark连接:要想两个RDD进行连接,那么这两个rdd的数据格式,必须是k-v键值对的,其中的k就是关联的条件,也就是sql中的on连接条件。
e.g. : 假设,RDD1的类型[K, V], RDD2的类型[K, W]
1.1 内连接
val joinedRDD:RDD[(K, (V, W))] = rdd1.join(rdd2)
1.2 左外连接
val leftJoinedRDD:RDD[(K, (V, Option[W]))] = rdd1.leftOuterJoin(rdd2)
1.3 右外连接
val rightJoinedRDD:RDD[(K, (Option[V], W))] = rdd1.rightOuterJoin(rdd2)
1.4 全连接
val fullJoinedRDD:RDD[(K, (Option[V], Option[W]))] = rdd1.fullOuterJoin(rdd2)
package cn.lihl.spark.core.day3
import cn.lihl.spark.utils.{LoggerTrait, SparkUtils}
import org.apache.spark.rdd.RDD
import scala.beans.BeanProperty
object Demo4_Join extends LoggerTrait{
def main(args: Array[String]): Unit = {
//1. 获取到spark core的编程入口
val sc = SparkUtils.getContext("demo4")
//2. 准备
//stu表:id,name,gender,age
//score表:stuid,course, score
val stuList = List(
"1 刘诗诗 女 18",
"2 欧阳娜娜 女 55",
"3 大幂幂 女 33",
"4 李冰冰 女 31"
)
val scoreList = List(
"1 语文 59",
"3 数学 0",
"2 英语 60",
"5 体育 99"
)
//3. 加载数据
val stuListRDD: RDD[String] = sc.parallelize(stuList)
val scoreListRDD: RDD[String] = sc.parallelize(scoreList)
//4. 连接
//4.1 内连接
val tStuListRDD: RDD[(Int, (String, String, Int))] = stuListRDD.map(line => {
val fields: Array[String] = line.split("\\s+")
val id = fields(0).toInt
val name = fields(1)
val gender = fields(2)
val age = fields(3).toInt
(id, (name, gender, age))
})
val tScoreListRDD: RDD[(Int, (String, Double))] = scoreListRDD.map(line => {
val fields: Array[String] = line.split("\\s+")
val stuid = fields(0).toInt
val course = fields(1)
val score = fields(2).toDouble
(stuid, (course, score))
})
val joinRDD: RDD[(Int, ((String, String, Int), (String, Double)))] = tStuListRDD join tScoreListRDD
// joinRDD.foreach(println)
joinRDD.filter(_._1 == 3).foreach(t => println(t._2._1._1))
}
}
2.8 groupByKey
原始rdd的类型时[(K, V)]
rdd.groupByKey(),按照key进行分组,那必然其结果就肯定[(K, Iterable[V])],是一个shuffle dependency宽依赖shuffle操作,但是这个groupByKey不建议在工作过程中使用,除非必要,因为groupByKey没有本地预聚合,性能较差,一般我们能用下面的reduceByKey或者combineByKey或者aggregateByKey代替就尽量代替。
object Demo6_GroupByKey {
def main(args: Array[String]): Unit = {
//1. 数据
//stu表:id, name, gender, age, class
val stuList = List(
"1,杨过,1,22,1904-bd-bj",
"2,郭靖,1,19,1904-bd-bj",
"3,令狐冲,0,27,1904-bd-sz",
"4,韦小宝,1,27,1904-bd-bj",
"5,胡斐,2,17,1904-bd-hz",
"6,张无忌,0,28,1904-bd-hz"
)
//2. 加载数据
SparkUtils.getContext("demo6").parallelize(stuList).map(line => {
val fields: Array[String] = line.split(",")
(fields(4), (fields(0), fields(1), fields(2), fields(3), fields(4)))
}).groupByKey.foreach(println)
}
}
2.9 reduceByKey
rdd的类型为[(K, V)]
rdd.reduceByKey(func:(V, V) => V):RDD[(K, V)] ====>在scala集合中学习过一个reduce(func:(W, W) => W)操作,是一个聚合操作,这里的reduceByKey按照就理解为在groupByKey(按照key进行分组[(K, Iterable[V])])的基础上,对每一个key对应的Iterable[V]执行reduce操作。同时reduceByKey操作会有一个本地预聚合的操作,所以是一个shuffle dependency宽依赖shuffle操作。
object Demo1_ReduceByKey {
def main(args: Array[String]): Unit = {
//1. 数据
//stu表:id, name, gender, age, class
val stuList = List(
"1,杨过,1,22,1904-bd-bj",
"2,郭靖,1,19,1904-bd-bj",
"3,令狐冲,0,27,1904-bd-sz",
"4,韦小宝,1,27,1904-bd-bj",
"5,胡斐,2,17,1904-bd-hz",
"6,张无忌,0,28,1904-bd-hz"
)
//2. 加载数据
val context: SparkContext = SparkUtils.getContext("Demo1_ReduceByKey")
val stuListRDD: RDD[String] = context.parallelize(stuList)
val tStuListRDD: RDD[(String,(String, String, String, String))] = stuListRDD.map(line => {
val fields: Array[String] = line.split(",")
(fields(4), (fields(1),fields(2),fields(3),fields(4)))
})
val reduceByKeyRDD: RDD[(String, (String, String, String, String))] = tStuListRDD.reduceByKey((previous, next) => next)
reduceByKeyRDD.foreach(println)
}
}
2.10 sortByKey
按照key进行排序
sortByKey(boolean): RDD[(K, V)]
object Demo1_ReduceByKey extends LoggerTrait{
def main(args: Array[String]): Unit = {
//1. 数据
//stu表:id, name, gender, age, class
val stuList = List(
"1,杨过,1,22,1",
"2,郭靖,1,19,1",
"3,令狐冲,0,27,2",
"4,韦小宝,1,27,2",
"5,胡斐,2,17,3",
"6,张无忌,0,28,3"
)
//2. 加载数据
val context: SparkContext = SparkUtils.getContext("Demo1_ReduceByKey")
val stuListRDD: RDD[String] = context.parallelize(stuList)
val tStuListRDD: RDD[(String, String)] = stuListRDD.map(line => {
val fields: Array[String] = line.split(",")
(fields(4), fields(1))
})
val reduceByKeyRDD: RDD[(String, String)] = tStuListRDD.reduceByKey(_ + "_" + _).sortByKey(false, 1)
reduceByKeyRDD.foreach(println)
}
}
2.11 mapPartitions(func)
是map算子的批量处理版本。map操做是每一条记录调用一次func,而mapPartitions操作,是每一个分区中的数据调用一次func函数,性能在一定程度上要比map高,但是需要注意的是OOM,因为一次要加载所有的数据到内存进行处理。
import cn.lihl.spark.core.day3.Demo3_Transformation
import cn.lihl.spark.utils.{LoggerTrait, SparkUtils}
import org.apache.spark.rdd.RDD
object Demo2 extends LoggerTrait{
def main(args: Array[String]): Unit = {
val sc = SparkUtils.getContext(Demo3_Transformation.getClass.getSimpleName)
//2. 加载数据
//2.1 加载数据文件/本地集合
val list:List[String] = List(
"11 14 155",
"12 15 159",
"13 16 136",
"13 13 150",
"13 13 158",
"13 13 157",
"13 13 156",
"13 13 154",
"13 13 153",
"13 13 152")
val listRDD: RDD[String] = sc.parallelize(list)
val value: RDD[String] = listRDD.mapPartitions(iterator =>{
val arr: Array[String] = iterator.toArray
arr(0).split("\\s+").iterator
}, true)
value.foreach(println)
}
}
2.12 mapPartitionWithIndex
该函数时可以获取具体的分区信息,func函数的第一个参数类型为Int,其含义时分区partition的编号
mapPartitionWithIndex(func:(Int, Iterable[T]) => Iterable[U])
val sc = SparkUtils.getContext(Demo3_Transformation.getClass.getSimpleName)
//2. 加载数据
//2.1 加载数据文件/本地集合
val list:List[String] = List(
"11 14 155",
"12 15 159",
"13 16 136",
"13 13 150",
"13 13 158",
"13 13 157",
"13 13 156",
"13 13 154",
"13 13 153",
"13 13 152")
//在我们的rdd的转换算子中有一个特性,叫做延迟/懒加载。你的转换算子不通过action算子调用的话,他是不会运行的
//2. mapPartitionsWithIndex
listRDD.mapPartitionsWithIndex((partition, iterator) => {
println(s"partition is ${partition}, data is ${iterator.mkString("[", ",", "]")}")
iterator
}).foreach(println)
2.13 coalesce和repartition
从字面意思上来看这两算子都是进行重分区的。其实repartition就是通过coalesce来实现的,coalesce默认是窄依赖,repartition是一个宽依赖;coalesce一般用于分区减少的操作,repartition一般用于分区增大的操作。
//3. 重分区
val repartitionRDD: RDD[String] = listRDD.repartition(3) // 宽依赖,多用于增加分区
val coalesceRDD: RDD[String] = listRDD.coalesce(3, true) // 默认是窄依赖,也可以是宽依赖
2.14 cogroup
可以对多3个RDD根据key进行分组,将每个key相同的元素分别聚集为一个集合返回一个新的RDD。格式为:
(K, (Iterable[V], Iterable[W])), 将两个pairRDD按key合并,返回各自的迭代,效果如下图:
object Demo3_Cogroup extends LoggerTrait{
def main(args: Array[String]): Unit = {
val stuList = List(
"1 刘诗诗 女 18",
"2 欧阳娜娜 女 55",
"3 大幂幂 女 33",
"4 李冰冰 女 31",
"3 范冰冰 女 38"
)
val scoreList = List(
"1 语文 59",
"3 数学 0",
"2 英语 60",
"5 体育 99"
)
//二、 获取配置
val context = SparkUtils.getContext("Demo3_Cogroup")
//三、 加载RDD
//1. 加载数据道rdd
val stuListRDD = context.parallelize(stuList)
val scoreListRDD = context.parallelize(scoreList)
//2. 分离第一列数据作为key,后面的数据作为value
val sid2StuInfoRDD = stuListRDD.map(line => {
val sid = line.substring(0, line.indexOf(" ")).toInt
val info = line.substring(line.indexOf(" ") + 1)
(sid, info)
})
val sid2ScoreInfoRDD = scoreListRDD.map(line => {
val sid = line.substring(0, line.indexOf(" ")).toInt
val info = line.substring(line.indexOf(" ") + 1)
(sid, info)
})
val cogroupRDD:RDD[(Int, (Iterable[String], Iterable[String]))] = sid2StuInfoRDD.cogroup(sid2ScoreInfoRDD)
cogroupRDD.foreach {
case (sid, (stus, scores)) => {
println(s"sid = ${sid}, stus = ${stus.mkString("[",",","]")}, scores = ${scores.mkString("[",",","]")}")
}
}
SparkUtils.stop(context)
}
}
2.15 CombineByKey(+++++)
2.15.1 介绍
通过查看reduceByKey和groupByKey的实现,发现其二者底层都是基于一个combineByKeyWithClassTag的底层算子来实现的,包括下面的aggregateByKey也是使用该算子实现。该算子又和combineByKey有啥关系呢?
CombineByKey是CombineByKeyWithClassTag的简化版本,它使用现有的分区程序/并行度级别对生成的RDD进行哈希分区。 此方法是为了向后兼容。 它不向shuffle提供combiner的类标签信息。
通过api学习,我们了解到combineByKey是combineByKeyWithClassTag的简写的版本。
这是spark最底层的聚合算子之一,按照key进行各种各样的聚合操作,spark提供的很多高阶算子,都是基于该算子实现的。
def combineByKey[C]( createCombiner: V => C, mergeValue: (C, V) => C, mergeCombiners: (C, C) => C, numPartitions: Int): RDD[(K, C)] = self.withScope { combineByKeyWithClassTag(createCombiner, mergeValue, mergeCombiners, numPartitions)(null) }
上述源码便是combineByKey的定义,是将一个类型为[(K, V)]的RDD聚合转化为[(K, C)]的类型,也就是按照K来进行聚合。这里的V是聚合前的类型,C聚合之后的类型。
如何理解聚合函数?切入点就是如何理解分布式计算?总--->分--->总createCombiner: V => C, 相同的Key在分区中会调用一次该函数,用于创建聚合之后的类型,为了和后续Key相同的数据进行聚合
mergeValue: (C, V) => C, 在相同分区中基于上述createCombiner基础之上的局部聚合
mergeCombiners: (C, C) => C) 将每个分区中相同key聚合的结果在分区间进行全局聚合所以combineByKey就是分布式计算。
2.15.2 模拟groupByKey
- 具体执行流程
object Demo4_CombineByKey_GroupByKey extends LoggerTrait{
def main(args: Array[String]): Unit = {
val context = SparkUtils.getContext("Demo4_CombineByKey_GroupByKey")
val stuList = List(
"白普州,1904-bd-bj",
"伍齐城,1904-bd-bj",
"曹佳,1904-bd-sz",
"曹莹,1904-bd-sz",
"刘文浪,1904-bd-wh",
"姚远,1904-bd-bj",
"匿名大哥,1904-bd-sz",
"欧阳龙生,1904-bd-sz"
)
val stuRDD: RDD[String] = context.parallelize(stuList, 3)
val class2InfoRDD: RDD[(String, String)] = stuRDD.mapPartitionsWithIndex {
case (partition, iterator) => {
val array = iterator.toArray
println(s"${partition} : ${array.mkString("[", ",", "]")}")
array.map(line => {
val dotIndex = line.lastIndexOf(",")
val className = line.substring(dotIndex + 1)
val info = line.substring(0, dotIndex)
(className, info)
}).toIterator
}
}
/*
1 : [曹佳,1904-bd-sz,曹莹,1904-bd-sz,刘文浪,1904-bd-wh]
2 : [姚远,1904-bd-bj,匿名大哥,1904-bd-sz,欧阳龙生,1904-bd-sz]
0 : [白普州,1904-bd-bj,伍齐城,1904-bd-bj]
*/
val combineByKeyRDD: RDD[(String, ArrayBuffer[String])] = class2InfoRDD.combineByKey(createCombiner, mergeValue, mergeCombiners)
println("*" * 80)
combineByKeyRDD.foreach(println)
}
def createCombiner(value:String) = {
println("============createCombiner<" + value + ">====================>>>>")
val array = new ArrayBuffer[String]()
array.append(value)
array
}
def mergeValue(array:ArrayBuffer[String], str:String) = {
println("》》》>>========mergeValue:局部聚合结果<" + array + ">,被聚合的值:" + str + "===========>>>>")
array.append(str)
array
}
def mergeCombiners(array1:ArrayBuffer[String], array2:ArrayBuffer[String]) = {
println("|-|-|<>|-|>>========mergeCombiners:全局聚合临时结果<" + array1 + ">,局部聚合的值:" + array2 + "===========>>>>")
array1.++:(array2)
}
}
2.15.3 模拟reduceByKey
object Demo5_CombineByKey_ReduceByKey extends LoggerTrait{
def main(args: Array[String]): Unit = {
val context = SparkUtils.getContext("Demo5_CombineByKey_ReduceByKey")
val wordList = List(
"hello everyone",
"hello everybody",
"hello world",
"hello lihl",
"lihl nihao",
"chengzhiyuan zhen shuai"
)
val stuRDD: RDD[String] = context.parallelize(wordList, 3)
val mapRDD: RDD[(String, Int)] = stuRDD.flatMap(_.split("\\s+")).map((_, 1))
mapRDD.foreach(println)
println("reduceByKey --------------------------------------------------->")
mapRDD.reduceByKey(_+_).foreach(println)
println("combineByKey --------------------------------------------------->")
val combineByKey: RDD[(String, Int)] = mapRDD.combineByKey(num => num, (sum, num) => sum + num, (sum1, sum2) => sum1 + sum2)
combineByKey.foreach(println)
}
def createCombiner(num:Int):Int = num
def mergeValue(sum:Int, num:Int):Int = sum + num
def mergeCombiners(sum1:Int, sum2:Int):Int = sum1 + sum2
}
2.16 aggregateByKey
aggregateByKey和combineByKey都是一个相对底层的聚合算子,可以完成系统没有提供的其它操作,相当于自定义算子。
aggregateByKey底层还是使用combineByKeyWithClassTag来实现,所以本质上二者没啥区别,区别就在于使用时的选择而已。
2.16.1 groupByKey
object Demo6_AggregateByKey_GroupByKey extends LoggerTrait{
def main(args: Array[String]): Unit = {
val context = SparkUtils.getContext("Demo4_CombineByKey_GroupByKey")
val stuList = List(
"白普州,1904-bd-bj",
"伍齐城,1904-bd-bj",
"曹佳,1904-bd-sz",
"曹莹,1904-bd-sz",
"刘文浪,1904-bd-wh",
"姚远,1904-bd-bj",
"匿名大哥,1904-bd-sz",
"欧阳龙生,1904-bd-sz"
)
val stuRDD: RDD[String] = context.parallelize(stuList, 3)
val class2InfoRDD: RDD[(String, String)] = stuRDD.mapPartitionsWithIndex {
case (partition, iterator) => {
val array = iterator.toArray
println(s"${partition} : ${array.mkString("[", ",", "]")}")
array.map(line => {
val dotIndex = line.lastIndexOf(",")
val className = line.substring(dotIndex + 1)
val info = line.substring(0, dotIndex)
(className, info)
}).toIterator
}
}
println("AggregateByKey-----------------------------------")
class2InfoRDD.aggregateByKey(ArrayBuffer[String]())(seqOp, combOp).foreach(println)
}
def seqOp(array:ArrayBuffer[String], str:String) = {
println("》》》>>========seqOp:局部聚合结果<" + array + ">,被聚合的值:" + str + "===========>>>>")
array.append(str)
array
}
def combOp(array1:ArrayBuffer[String], array2:ArrayBuffer[String]) = {
println("|-|-|<>|-|>>========combOp:全局聚合临时结果<" + array1 + ">,局部聚合的值:" + array2 + "===========>>>>")
array1.++:(array2)
}
}
2.16.2 reduceByKey
object Demo7_AggregateByKey_ReduceByKey extends LoggerTrait{
def main(args: Array[String]): Unit = {
val context = SparkUtils.getContext("Demo5_CombineByKey_ReduceByKey")
val wordList = List(
"hello everyone",
"hello everybody",
"hello world",
"hello lihl",
"lihl nihao",
"chengzhiyuan zhen shuai"
)
val stuRDD: RDD[String] = context.parallelize(wordList, 3)
val mapRDD: RDD[(String, Int)] = stuRDD.flatMap(_.split("\\s+")).map((_, 1))
mapRDD.foreach(println)
println("reduceByKey --------------------------------------------------->")
mapRDD.reduceByKey(_+_).foreach(println)
println("combineByKey --------------------------------------------------->")
val aggregateByKey: RDD[(String, Int)] = mapRDD.aggregateByKey(0)((sum, num) => sum + num, (sum1, sum2) => sum1 + sum2)
aggregateByKey.foreach(println)
}
}
3 action算子(++++)
所有的这些算子都是在rdd,rdd上的分区partition上面执行的,不是在driver本地执行。
3.1 foreach
略
3.2 count
统计该rdd中元素的个数
val sc = SparkUtils.getContext("Demo8_action")
val valueRDD: RDD[Int] = sc.parallelize(1 to 100)
val count: Long = valueRDD.count()
println(count) // 统计rdd中的元素个数
3.3 take
取前n个元素
val top10: Array[Int] = valueRDD.take(10)
println(top10.mkString(","))
3.4 first
take(n)中比较特殊的一个take(1)(0)
//3. first
val i: Int = valueRDD.first()
println(i)
3.5 collect
字面意思就是收集,拉取的意思,该算子的含义就是将分布在集群中的各个partition中的数据拉回到driver中,进行统一的处理;但是这个算子有很大的风险存在,第一,driver内存压力很大,第二数据在网络中大规模的传输,效率很低;所以一般不建议使用,如果非要用,请先执行filter。
val context = new SparkContext(new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]"))
val listRDD = context.parallelize(1 to 100)
val arrRDD = listRDD.filter(_ % 2 == 0).collect()
arrRDD.foreach(println)
3.6 reduce
一定记清楚,reduce是一个action操作,reduceByKey是一个transformation。reduce对一个rdd执行聚合操作,并返回结果,结果是一个值。
使用指定的可交换和关联的二进制运算符减少此RDD的元素。
//4. reduce
val sum: Int = valueRDD.reduce((previous, next) => previous + next)
println(sum)
3.7 countByKey
分组统计key的个数
//5. countByKey:要求你的rdd存放的元素都必须要二维的元组类型
val keyRDD: RDD[(String, String)] = sc.parallelize(List(
("bj", "程志远"),
("hz", "杨豪"),
("bj", "张海英")
))
val map: collection.Map[String, Long] = keyRDD.countByKey()
println(map.mkString(","))
3.8 saveTextFile和saveObjectFile和saveSequenceFile
//6. saveTextFile
// keyRDD.saveAsTextFile("file:///d:/1")
//7. saveSequenceFile
// keyRDD.saveAsSequenceFile("file:///d:/1")
//8. saveObjectFile
// keyRDD.saveAsObjectFile("file:///d:/1")
4 RDD的弹性持久化策略(+++)
4.1 什么是持久化,为什么要持久化
Spark中最重要的功能之一是跨操作在内存中持久化(或缓存)数据集。 当您保留RDD时,每个节点都会将其计算的所有分区存储在内存中,并在该数据集(或从其派生的数据集)上的其他操作中重用它们。 这样可以使以后的操作更快(通常快10倍以上)。 缓存是用于迭代算法和快速交互使用的关键工具。
4.2 如何进行持久化
您可以使用其上的persist()或cache()方法将一个RDD标记为持久。 第一次在操作中对其进行计算时,它将被保存在节点上的内存中。 Spark的缓存是容错的-如果RDD的任何分区丢失,它将使用最初创建它的转换自动重新计算。
持久化的方法就是rdd.persist()或者rdd.cache()
4.3 持久化策略
持久化策略 | 含义 |
---|---|
MEMORY_ONLY(默认) | rdd中的数据,以未经序列化的java对象格式,存储在内存中。如果内存不足,剩余的部分不持久化,使用的时候,没有持久化的那一部分数据重新加载。这种效率是最高,但是是对内存要求最高的。 |
MEMORY_ONLY_SER | 就比MEMORY_ONLY多了一个SER序列化,保存在内存中的数据是经过序列化之后的字节数组,同时每一个partition此时就是一个比较大的字节数组。 |
MEMORY_AND_DISK | 和MEMORY_ONLY相比就多了一个,内存存不下的数据存储在磁盘中。 |
MEMEORY_AND_DISK_SER | 比MEMORY_AND_DISK多了个序列化。 |
DISK_ONLY | 就是MEMORY_ONLY对应,都保存在磁盘,效率太差,一般不用。 |
xxx_2 | 就是上述多个策略后面加了一个_2,比如MEMORY_ONLY_2,MEMORY_AND_DISK_SER_2等等,就多了一个replicate而已,备份,所以性能会下降,但是容错或者高可用加强了。所以需要在二者直接做权衡。如果说要求数据具备高可用,同时容错的时间花费比从新计算花费时间少,此时便可以使用,否则一般不用。 |
HEAP_OFF(experimental) | 使用非Spark的内存,也即堆外内存,比如Tachyon,HBase、Redis等等内存来补充spark数据的缓存。 |
4.4 持久化和非持久化性能比较
object Demo1_Persistent {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setAppName(s"${Demo1_Persistent.getClass.getSimpleName}")
.setMaster("local[*]")
val context = new SparkContext(conf)
//读取外部数据
var start = System.currentTimeMillis()
val lines = context.textFile("file:///E:/wc.txt")
var count = lines.count()
println("没有持久化:#######lines' count: " + count + ", cost time: " + (System.currentTimeMillis() - start) + "ms")
lines.persist(StorageLevel.MEMORY_AND_DISK) //lines.cache()
start = System.currentTimeMillis()
count = lines.count()
println("持久化之后:#######lines' count: " + count + ", cost time: " + (System.currentTimeMillis() - start) + "ms")
lines.unpersist()//卸载持久化数据
context.stop()
}
}
没有持久化:#######lines' count: 4, cost time: 920ms
持久化之后:#######lines' count: 4, cost time: 23ms
5 共享变量
5.1 广播变量(+++++)
不使用广播变量产生的问题
广播变量解决方式
var num = 1
val bNum: Broadcast[Int] = sc.broadcast(num)
5.2 累加器
accumulator累加器的概念和mr中出现的counter计数器的概念有异曲同工之妙,对默写具备某些特征的数据进行累加。累加器的一个好处是,不需要修改程序的业务逻辑来完成数据累加,同时也不需要额外的触发一个action job来完成累加,反之必须要添加新的业务逻辑,必须要触发一个新的action job来完成,显然这个accumulator的操作性能更佳!
累加的使用:
构建一个累加器:
val accu = sc.longAccumuator()
累加的操作:
accu.add(参数)
获取累加器的结果:
val ret = accu.value
val accumulator: LongAccumulator = sc.longAccumulator // 获取累加器
accumulator.add(1) // 对累加器进行递增
accumulator.value //获取到累加器的值
object Demo2_Accumulator {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setAppName(s"${Demo2_Accumulator.getClass.getSimpleName}")
.setMaster("local[*]")
val sc = new SparkContext(conf)
val list = List(
"A second is in Spark is shared variables that can be is in parallel is"
)
val lines = sc.parallelize(list)
val words = lines.flatMap(_.split("\\s+"))
//统计每个单词出现的次数
val accumulator = sc.longAccumulator
val rbk = words.map(word => {
if(word == "is") accumulator.add(1)
(word, 1)
}).reduceByKey(_+_)
rbk.foreach(println)
println("================使用累加器===================")
println("is: " + accumulator.value)
Thread.sleep(10000000)
sc.stop()
}
}
6 高级排序
6.1 sortByKey
object Demo1_SortByKey extends LoggerTrait{
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
.setAppName(s"Demo1_SortByKey")
.setMaster("local[*]")
val sc = new SparkContext(conf)
//sortByKey 数据类型为k-v,且是按照key进行排序
val stuRDD:RDD[Student] = sc.parallelize(List(
Student(1, "吴轩宇", 19, 168),
Student(2, "彭国宏", 18, 175),
Student(3, "随国强", 18, 176),
Student(4, "闫 磊", 20, 180),
Student(5, "王静轶", 18, 168.5)
))
//按照学生身高进行降序排序
val height2Stu = stuRDD.map(stu => (stu.height, stu))
//注意:sortByKey是局部排序,不是全局排序,如果要进行全局排序,
// 必须将所有的数据都拉取到一台机器上面才可以
val sorted = height2Stu.sortByKey(ascending = false, 1)
sorted.foreach { case (height, stu) => println(stu)}
sc.stop()
}
}
case class Student(id:Int, name:String, age:Int, height:Double)
6.2 sortBy
这个sortByKey其实使用sortByKey来实现,但是比sortByKey更加灵活,因为sortByKey只能应用在k-v数据格式上,而这个sortBy可以应在非k-v键值对的数据格式上面。
object Demo2_SortBy extends LoggerTrait{
def main(args: Array[String]): Unit = {
val sc = SparkUtils.getContext("Demo2_SortBy")
//sortByKey 数据类型为k-v,且是按照key进行排序
val stuRDD:RDD[Student] = sc.parallelize(List(
Student(1, "吴轩宇", 19, 168),
Student(2, "彭国宏", 18, 175),
Student(3, "随国强", 18, 176),
Student(4, "闫 磊", 20, 180),
Student(5, "王静轶", 18, 168.5)
))
val sortByRDD: RDD[Student] = stuRDD.sortBy(stu => stu.height, false, 1)
sortByRDD.foreach(println)
}
}
6.3 takeOrder
takeOrdered也是对rdd进行排序,但是和上述的sortByKey和sortBy相比较,takeOrdered是一个action操作,返回值为一个集合,而前两者为transformation,返回值为rdd。如果我们想在driver中获取排序之后的结果,那么建议使用takeOrdered,因为该操作边排序边返回。
其实是take和sortBy的一个结合体。
takeOrdered(n),获取排序之后的n条记录
object Demo3_takeOrder extends LoggerTrait{
def main(args: Array[String]): Unit = {
val sc = SparkUtils.getContext("Demo2_SortBy")
//sortByKey 数据类型为k-v,且是按照key进行排序
val stuRDD:RDD[Student] = sc.parallelize(List(
Student(1, "吴轩宇", 19, 168),
Student(2, "彭国宏", 18, 175),
Student(3, "随国强", 18, 176),
Student(4, "闫 磊", 20, 180),
Student(5, "王静轶", 18, 169.5)
),1)
stuRDD.takeOrdered(3)(new Ordering[Student]{
override def compare(x: Student, y: Student): Int = {
y.height.compareTo(x.height)
}
}).foreach(println)
}
}
6.4 分组topn
object Demo4_group_topn extends LoggerTrait{
def main(args: Array[String]): Unit = {
val list = List(
"chinese ls 91",
"english ww 56",
"chinese zs 90",
"chinese zl 76",
"english zq 88",
"english lx 100"
)
val context: SparkContext = SparkUtils.getContext("Demo4_group_topn")
val listRDD: RDD[String] = context.parallelize(list)
val groupRDD: RDD[(String, Iterable[(String, String)])] = listRDD.map(line => {
val arr: Array[String] = line.split("\\s+")
(arr(0), (arr(1), arr(2)))
}).groupByKey
val value: RDD[(String, List[(String, String)])] = groupRDD.map(tup => {
val tuples: List[(String, String)] = tup._2.toList.sortWith((prevous, next) => prevous._2.toInt > next._2.toInt).take(2)
(tup._1, tuples)
})
val takeRDD = value
takeRDD.foreach(println)
}
7 分区器
Spark目前支持hash分区和range分区,用户也可以自定义分区。hash分区是默认的分区规则,spark中分区器直接决定了rdd中的分区的个数,rdd中的每条数据经过shuffle过程。
tip:
1 分区类必须要继承Partitioner的父类
2 定义分区规则的时候,分区编号不能超过分区数-1
3 在执行分区自定义类的时候,必须要手动的将数据zipWithIndex()
4 rdd.partitionBy(自定义分区对象)
object Demo5_Partitioner extends LoggerTrait{
def main(args: Array[String]): Unit = {
val sc = SparkUtils.getContext("Demo2_SortBy")
// val stuRDD = sc.parallelize(1 to 100).zipWithIndex()
val stuRDD = sc.parallelize(List(
Student(1, "李轩宇", 19, 168),
Student(2, "王国宏", 18, 175),
Student(3, "陈国强", 18, 176),
Student(4, "闫 磊", 20, 180),
Student(5, "王静轶", 18, 169.5)
)).zipWithIndex()
val value: RDD[(Student, Long)] = stuRDD.partitionBy(new MyPartitioner(3))
value.mapPartitionsWithIndex((partition, iterator) => {
println(s"$partition, ${iterator.toList.mkString(",")}")
iterator
}).foreach(println)
}
}
class MyPartitioner(partitions:Int) extends Partitioner {
override def numPartitions: Int = partitions
override def getPartition(key: Any): Int = {
println("mypatiton ----------------->")
if (key.isInstanceOf[Student]) {
var stu = key.asInstanceOf[Student]
if(stu.name.contains("李")) return 1
else if(stu.name.contains("王")) return 2
else return 4
}else return 0
}
}