大数据spark core的介绍

本文深入解析Spark大数据处理框架,涵盖Spark的特点、与Hadoop的对比、核心模块介绍及快速上手指南。从Spark的内存计算优势、易用性和通用性,到其与Hadoop在数据处理效率上的差异,再到Spark的部署模式、编程模型和算子操作,全面剖析Spark在大数据领域的应用。

Spark是什么

1.Spark是什么

Spark是一种基于内存的快速、通用、可扩展的大数据分析计算引擎。并且 Spark 目前已经成为 Apache 最活跃的开源项目。

2.Spark特点

2.1 快速
与 Hadoop 的 MapReduce 相比, Spark 基于内存的运算是 MapReduce 的 100 倍.基于硬盘的运算也要快 10 倍以上.
2.2 易用
Spark 支持 Scala, Java, Python, R 和 SQL 脚本, 并提供了超过 80 种高性能的算法, 非常容易创建并行 App,而且 Spark 支持交互式的 Python 和 Scala 的 shell, 这意味着可以非常方便地在这些 shell 中使用 Spark 集群来验证解决问题的方法, 而不是像以前一样 需要打包, 上传集群, 验证等. 这对于原型开发非常重要.
2.3 通用
Spark 结合了SQL, Streaming和复杂分析.
Spark 提供了大量的类库, 包括 SQL 和 DataFrames, 机器学习(MLlib), 图计算(GraphicX), 实时流处理(Spark Streaming) .
可以把这些类库无缝的柔和在一个 App 中.
减少了开发和维护的人力成本以及部署平台的物力成本.
2.4可融合性
Spark 可以非常方便的与其他开源产品进行融合.
比如, Spark 可以使用 Hadoop 的 YARN 和 Apache Mesos 作为它的资源管理和调度器, 并且可以处理所有 Hadoop 支持的数据, 包括 HDFS, HBase等.
Spark 得到了众多大数据公司的支持,这些公司包括 Hortonworks、IBM、Intel、Cloudera、MapR、Pivotal
当前某度的 Spark 已应用于大搜索、直达号、某度大数据等业务;
某巴利用 GraphX 构建了大规模的图计算和图挖掘系统,实现了很多生产系统的推荐算法;
企鹅Spark集群达到 8000 台的规模,据说是当前已知的世界上最大的 Spark 集群。

3 Spark and Hadoop

Hadoop的MapReduce是大家广为熟知的计算框架,那为什么咱们还要学习新的计算框架Spark呢,这里就不得不提到Spark和Hadoop的关系。
3.1 功能上来看:
Hadoop是由java语言编写的,在分布式服务器集群上存储海量数据并运行分布式分析应用的开源框架
作为Hadoop分布式文件系统,HDFS处于Hadoop生态圈的最下层,存储着所有的数据,支持着Hadoop的所有服务。它的理论基础源于Google的TheGoogleFileSystem这篇论文,它是GFS的开源实现。
MapReduce是一种编程模型,Hadoop根据Google的MapReduce论文将其实现,作为Hadoop的分布式计算模型,是Hadoop的核心。基于这个框架,分布式并行程序的编写变得异常简单。综合了HDFS的分布式存储和MapReduce的分布式计算,Hadoop在处理海量数据时,性能横向扩展变得非常容易。
HBase是对Google的Bigtable的开源实现,但又和Bigtable存在许多不同之处。HBase是一个基于HDFS的分布式数据库,擅长实时地随机读/写超大规模数据集。它也是Hadoop非常重要的组件。
Spark
Spark是一种由Scala语言开发的快速、通用、可扩展的大数据分析引擎
Spark Core中提供了Spark最基础与最核心的功能
Spark SQL是Spark用来操作结构化数据的组件。通过Spark SQL,用户可以使用SQL或者Apache Hive版本的SQL方言(HQL)来查询数据。
Spark Streaming是Spark平台上针对实时数据进行流式计算的组件,提供了丰富的处理数据流的API。
由上面的信息可以获知,Spark出现的时间相对较晚,并且主要功能主要是用于数据计算,所以其实Spark一直被认为是Hadoop MR框架的升级版。
3.2 Spark or Hadoop
Hadoop的MR框架和Spark框架都是数据处理框架,那么我们在使用时如何选择呢?
Hadoop MapReduce由于其设计初衷并不是为了满足循环迭代式数据流处理,因此在多并行运行的数据可复用场景(如:机器学习、图挖掘算法、交互式数据挖掘算法)中存在诸多计算效率等问题。所以Spark应运而生,Spark就是在传统的MapReduce 计算框架的基础上,利用其计算过程的优化,从而大大加快了数据分析、挖掘的运行和读写速度,并将计算单元缩小到更适合并行计算和重复使用的RDD计算模型。
机器学习中ALS、凸优化梯度下降等。这些都需要基于数据集或者数据集的衍生数据反复查询反复操作。MR这种模式不太合适,即使多MR串行处理,性能和时间也是一个问题。数据的共享依赖于磁盘。另外一种是交互式数据挖掘,MR显然不擅长。而Spark所基于的scala语言恰恰擅长函数的处理。
Spark是一个分布式数据快速分析项目。它的核心技术是弹性分布式数据集(Resilient Distributed Datasets),提供了比MapReduce丰富的模型,可以快速在内存中对数据集进行多次迭代,来支持复杂的数据挖掘算法和图形计算算法。
Spark和Hadoop的根本差异是多个作业之间的数据通信问题 : Spark多个作业之间数据通信是基于内存,而Hadoop是基于磁盘。
Spark Task的启动时间快。Spark采用fork线程的方式,而Hadoop采用创建新的进程的方式。
Spark只有在shuffle的时候将数据写入磁盘,而Hadoop中多个MR作业之间的数据交互都要依赖于磁盘交互
Spark的缓存机制比HDFS的缓存机制高效。
经过上面的比较,我们可以看出在绝大多数的数据计算场景中,Spark确实会比MapReduce更有优势。但是Spark是基于内存的,所以在实际的生产环境中,由于内存的限制,可能会由于内存资源不够导致Job执行失败,此时,MapReduce其实是一个更好的选择,所以Spark并不能完全替代MR。
4 Spark 核心模块
Spark Core
Spark Core中提供了Spark最基础与最核心的功能,Spark其他的功能如:Spark SQL,Spark Streaming,GraphX, MLlib都是在Spark Core的基础上进行扩展的
Spark SQL
Spark SQL是Spark用来操作结构化数据的组件。通过Spark SQL,用户可以使用SQL或者Apache Hive版本的SQL方言(HQL)来查询数据。
Spark Streaming
Spark Streaming是Spark平台上针对实时数据进行流式计算的组件,提供了丰富的处理数据流的API。
Spark MLlib
MLlib是Spark提供的一个机器学习算法库。MLlib不仅提供了模型评估、数据导入等额外的功能,还提供了一些更底层的机器学习原语。
Spark GraphX
GraphX是Spark面向图计算提供的框架与算法库。

5 Spark快速上手
在大数据早期的课程中我们已经学习了MapReduce框架的原理及基本使用,并了解了其底层数据处理的实现方式。接下来,就让咱们走进Spark的世界,了解一下它是如何带领我们完成数据处理的。
5.1 创建Maven项目
5.1.1 增加Scala插件
Spark由Scala语言开发的,所以本课件接下来的开发所使用的语言也为Scala,咱们当前使用的Spark版本为3.0.0,默认采用的Scala编译版本为2.12,所以后续开发时。我们依然采用这个版本。开发前请保证IDEA开发工具中含有Scala开发插件

5.1.2 增加依赖关系
修改Maven项目中的POM文件,增加Spark框架的依赖关系。本课件基于Spark2.4.5版本,使用时请注意对应版本。

    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-core_2.12</artifactId>
        <version>3.0.0</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <!-- 该插件用于将Scala代码编译成class文件 -->
        <plugin>
            <groupId>net.alchim31.maven</groupId>
            <artifactId>scala-maven-plugin</artifactId>
            <version>3.2.2</version>
            <executions>
                <execution>
                    <!-- 声明绑定到maven的compile阶段 -->
                    <goals>
                        <goal>compile</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-assembly-plugin</artifactId>
            <version>3.0.0</version>
            <configuration>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
            </configuration>
            <executions>
                <execution>
                    <id>make-assembly</id>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

5.1.3 WordCount
为了能直观地感受Spark框架的效果,接下来我们实现一个大数据学科中最常见的教学案例WordCount
// 创建Spark运行配置对象
val sparkConf = new SparkConf().setMaster(“local[*]”).setAppName(“WordCount”)
// 创建Spark上下文环境对象(连接对象)
val sc : SparkContext = new SparkContext(sparkConf)
// 读取文件数据
val fileRDD: RDD[String] = sc.textFile(“input/word.txt”)
// 将文件中的数据进行分词
val wordRDD: RDD[String] = fileRDD.flatMap( .split(" ") )
val word2OneRDD: RDD[(String, Int)] = wordRDD.map((
,1))
// 将转换结构后的数据按照相同的单词进行分组聚合
val word2CountRDD: RDD[(String, Int)] = word2OneRDD.reduceByKey(+)
// 将数据聚合结果采集到内存中
val word2Count: Array[(String, Int)] = word2CountRDD.collect()
word2Count.foreach(println)
//关闭Spark连接
sc.stop()
5.1.4 异常处理
如果本机操作系统是Windows,在程序中使用了Hadoop相关的东西,比如写入文件到HDFS,则会遇到如下异常:
出现这个问题的原因,解决办法是通过配置关联到windows的系统依赖就可以了,在IDEA中配置Run Configuration,添加HADOOP_HOME变量

原理

Spark框架的核心是一个计算引擎,整体来说,它采用了标准 master-slave 的结构。如下图所示,它展示了一个 Spark执行时的基本结构。图形中的Driver表示master,负责管理整个集群中的作业任务调度。图形中的Executor 则是 slave,负责实际执行任务。
在这里插入图片描述

1 核心组件
由上图可以看出,对于Spark框架有两个核心组件:
1.1 Driver
Spark驱动器节点,用于执行Spark任务中的main方法,负责实际代码的执行工作。Driver在Spark作业执行时主要负责:
将用户程序转化为作业(job)
在Executor之间调度任务(task)
跟踪Executor的执行情况
通过UI展示查询运行情况
实际上,我们无法准确地描述Driver的定义,因为在整个的编程过程中没有看到任何有关Driver的字眼。所以简单理解,所谓的Driver就是驱使整个应用运行起来的程序,也称之为Driver类。
1.2 Executor
Spark Executor是集群中工作节点(Worker)中的一个JVM进程,负责在 Spark 作业中运行具体任务(Task),任务彼此之间相互独立。Spark 应用启动时,Executor节点被同时启动,并且始终伴随着整个 Spark 应用的生命周期而存在。如果有Executor节点发生了故障或崩溃,Spark 应用也可以继续执行,会将出错节点上的任务调度到其他Executor节点上继续运行。
Executor有两个核心功能:
负责运行组成Spark应用的任务,并将结果返回给驱动器进程
它们通过自身的块管理器(Block Manager)为用户程序中要求缓存的 RDD 提供内存式存储。RDD 是直接缓存在Executor进程内的,因此任务可以在运行时充分利用缓存数据加速运算。
1.3 Master & Worker
Spark集群的独立部署环境中,不需要依赖其他的资源调度框架,自身就实现了资源调度的功能,所以环境中还有其他两个核心组件:Master和Worker,这里的Master是一个进程,主要负责资源的调度和分配,并进行集群的监控等职责,类似于Yarn环境中的RM, 而Worker呢,也是进程,一个Worker运行在集群中的一台服务器上,由Master分配资源对数据进行并行的处理和计算,类似于Yarn环境中NM。
1.4 ApplicationMaster
Hadoop用户向YARN集群提交应用程序时,提交程序中应该包含ApplicationMaster,用于向资源调度器申请执行任务的资源容器Container,运行用户自己的程序任务job,监控整个任务的执行,跟踪整个任务的状态,处理任务失败等异常情况。
说的简单点就是,ResourceManager(资源)和Driver(计算)之间的解耦合靠的就是ApplicationMaster。
1.5 核心概念
1.5.1 Executor与Core
Spark Executor是集群中运行在工作节点(Worker)中的一个JVM进程,是整个集群中的专门用于计算的节点。在提交应用中,可以提供参数指定计算节点的个数,以及对应的资源。这里的资源一般指的是工作节点Executor的内存大小和使用的虚拟CPU核(Core)数量。

运行模式

1. Local模式

1.1 解压缩文件
将spark-3.0.0-bin-hadoop3.2.tgz文件上传到Linux并解压缩,放置在指定位置,路径中不要包含中文或空格,课件后续如果涉及到解压缩操作,不再强调。
tar -zxvf spark-3.0.0-bin-hadoop3.2.tgz -C /opt/module
cd /opt/module
mv spark-3.0.0-bin-hadoop3.2.tgz spark-local
1.2 启动Local环境
1)进入解压缩后的路径,执行如下指令
bin/spark-shell
2)启动成功后,可以输入网址进行Web UI监控页面访问
http://虚拟机地址:4040
1.3 命令行工具
在解压缩文件夹下的data目录中,添加word.txt文件。在命令行工具中执行如下代码指令
sc.textFile(“data/word.txt”).flatMap(.split(" ")).map((,1)).reduceByKey(+).collect
1.4 退出本地模式
按键Ctrl+C或输入Scala指令
:quit
1.5 提交应用
bin/spark-submit
–class org.apache.spark.examples.SparkPi
–master local[2]
./examples/jars/spark-examples_2.12-3.0.0.jar
10
1)–class表示要执行程序的主类
2)–master local[2] 部署模式,默认为本地模式,数字表示分配的虚拟CPU核数量
3)spark-examples_2.12-3.0.0.jar 运行的应用类所在的jar包
4)数字10表示程序的入口参数,用于设定当前应用的任务数量

2 Standalone模式

local本地模式用来体验的,真实工作中还是要将应用提交到对应的集群中去执行,这里我们来看看只使用Spark自身节点运行的集群模式,也就是我们所谓的独立部署(Standalone)模式。Spark的Standalone模式体现了经典的master-slave模式。
集群规划:
Linux19 Linux29 Linux39
Spark Worker Master Worker Worker
2.1 解压缩文件
将spark-3.0.0-bin-hadoop3.2.tgz.tgz文件上传到Linux并解压缩在指定位置
tar -zxvf spark-3.0.0-bin-hadoop3.2.tgz.tgz -C /opt/module
cd /opt/module
mv spark-3.0.0-bin-hadoop3.2.tgz spark-standalone
2.2 修改配置文件
1)进入解压缩后路径的conf目录,修改slaves.template文件名为slaves
mv slaves.template slaves
2)修改slaves文件,添加work节点
linux19
linux29
linux39
3)修改spark-env.sh.template文件名为spark-env.sh
mv spark-env.sh.template spark-env.sh
4)修改spark-env.sh文件,添加JAVA_HOME环境变量和集群对应的master节点
export JAVA_HOME=/opt/module/jdk1.8.0_144
SPARK_MASTER_HOST=linux19
SPARK_MASTER_PORT=7077
注意:7077端口,相当于hadoop39内部通信的8020端口
5)分发spark-standalone目录
xsync spark-standalone
2.3 启动集群
1)执行脚本命令:
sbin/start-all.sh
2)查看三台服务器运行进程
linux19
3386 Jps
4338 Worker
2363 Master
linux29
6623 Jps
5908 Worker
linux39
2348 Worker
3546 Jps
3)查看Master资源监控Web UI界面: http://linux1:8080
2.4 提交应用
bin/spark-submit
–class org.apache.spark.examples.SparkPi
–master spark://linux19:7077
./examples/jars/spark-examples_2.12-3.0.0.jar
10
1)–class表示要执行程序的主类
2)–master spark://linux1:7077 独立部署模式,连接到Spark集群
3)spark-examples_2.12-3.0.0.jar 运行类所在的jar包
4)数字10表示程序的入口参数,用于设定当前应用的任务数量
执行任务时,会产生多个Java进程
执行任务时,默认采用服务器集群节点的总核数,每个节点内存1024M。
2.5 提交参数说明
在提交应用中,一般会同时一些提交参数
bin/spark-submit
–class
–master
… # other options

[application-arguments]
参数 解释 可选值举例
–class Spark程序中包含主函数的类
–master Spark程序运行的模式(环境) 本地模式:local[*]、spark://linux19:7077、
Yarn
–executor-memory 1G 指定每个executor可用内存为1G 符合集群内存配置即可,具体情况具体分析。
–total-executor-cores 2 指定所有executor使用的cpu核数为2个
–executor-cores 指定每个executor使用的cpu核数
application-jar 打包好的应用jar,包含依赖。这个URL在集群中全局可见。 比如hdfs:// 共享存储系统,如果是file:// path,那么所有的节点的path都包含同样的jar
application-arguments 传给main()方法的参数
3.2 配置历史服务
由于spark-shell停止掉后,集群监控linux1:4040页面就看不到历史任务的运行情况,所以开发时都配置历史服务器记录任务运行情况。
1)修改spark-defaults.conf.template文件名为spark-defaults.conf
mv spark-defaults.conf.template spark-defaults.conf
2)修改spark-default.conf文件,配置日志存储路径
spark.eventLog.enabled true
spark.eventLog.dir hdfs://linux19:8020/directory
注意:需要启动hadoop集群,HDFS上的directory目录需要提前存在。
sbin/start-dfs.sh
hadoop fs -mkdir /directory
3)修改spark-env.sh文件, 添加日志配置
export SPARK_HISTORY_OPTS="
-Dspark.history.ui.port=18080
-Dspark.history.fs.logDirectory=hdfs://linux1:8020/directory
-Dspark.history.retainedApplications=30"
3)修改spark-env.sh文件, 添加日志配置
export SPARK_HISTORY_OPTS="
-Dspark.history.ui.port=18080
-Dspark.history.fs.logDirectory=hdfs://linux1:8020/directory
-Dspark.history.retainedApplications=30"
参数1含义:WEB UI访问的端口号为18080
参数2含义:指定历史服务器日志存储路径
参数3含义:指定保存Application历史记录的个数,如果超过这个值,旧的应用程序信息将被删除,这个是内存中的应用数,而不是页面上显示的应用数。
4)分发配置文件
5)重新启动集群和历史服务

3.3 配置高可用(HA)
所谓的高可用是因为当前集群中的Master节点只有一个,所以会存在单点故障问题。所以为了解决单点故障问题,需要在集群中配置多个Master节点,一旦处于活动状态的Master发生故障时,由备用Master提供服务,保证作业可以继续执行。这里的高可用一般采用Zookeeper设置
集群规划:
Linux19 Linux29 Linux39
Spark Master
Zookeeper
Worker Master
Zookeeper
Worker
Zookeeper
Worker
1)停止集群
sbin/stop-all.sh
2)启动Zookeeper
xstart zk
3)修改spark-env.sh文件添加如下配置
注释如下内容:
#SPARK_MASTER_HOST=linux19
#SPARK_MASTER_PORT=7077
添加如下内容:
#Master监控页面默认访问端口为8080,但是可能会和Zookeeper冲突,所以改成8989,也可以自定义,访问UI监控页面时请注意
export SPARK_MASTER_WEBUI_PORT=8989
export SPARK_DAEMON_JAVA_OPTS="
-Dspark.deploy.recoveryMode=ZOOKEEPER
-Dspark.deploy.zookeeper.url=linux19,linux29,linux39
-Dspark.deploy.zookeeper.dir=/spark"
4)分发配置文件
xsync conf/
5)启动集群
sbin/start-all.sh
6)启动linux29的单独Master节点,此时linux29节点Master状态处于备用状态
[root@linux29 spark-standalone]# sbin/start-master.sh

7)提交应用到高可用集群
bin/spark-submit
–class org.apache.spark.examples.SparkPi
–master spark://linux19:7077,linux29:7077
./examples/jars/spark-examples_2.12-3.0.0.jar
10
8)停止linux19的Master资源监控进程

9)查看linux29的Master 资源监控Web UI,稍等一段时间后,linux29节点的Master状态提升为活动状态

3 Yarn模式

独立部署(Standalone)模式由Spark自身提供计算资源,无需其他框架提供资源。这种方式降低了和其他第三方资源框架的耦合性,独立性非常强。但是你也要记住,Spark主要是计算框架,而不是资源调度框架,所以本身提供的资源调度并不是它的强项,所以还是和其他专业的资源调度框架集成会更靠谱一些。所以接下来我们来学习在强大的Yarn环境下Spark是如何工作的(其实是因为在国内工作中,Yarn使用的非常多)。
3.1 解压缩文件
将spark-3.0.0-bin-hadoop3.2.tgz文件上传到linux并解压缩,放置在指定位置。
tar -zxvf spark-3.0.0-bin-hadoop3.2.tgz -C /opt/module
cd /opt/module
mv spark-examples_2.12-3.0.0 spark-yarn
3.2 修改配置文件
1)修改hadoop配置文件/opt/module/hadoop/etc/hadoop/yarn-site.xml, 并分发

<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)修改conf/spark-env.sh,添加JAVA_HOME和YARN_CONF_DIR配置
mv spark-env.sh.template spark-env.sh

export JAVA_HOME=/opt/module/jdk1.8.0_144
YARN_CONF_DIR=/opt/module/hadoop/etc/hadoop
3.3 启动HDFS以及YARN集群
3.4 提交应用
bin/spark-submit
–class org.apache.spark.examples.SparkPi
–master yarn
–deploy-mode cluster
./examples/jars/spark-examples_2.12-3.0.0.jar
10
查看http://linux29:8088页面,点击History,查看历史页面
3.5 配置历史服务器
1)修改spark-defaults.conf.template文件名为spark-defaults.conf
mv spark-defaults.conf.template spark-defaults.conf
2)修改spark-default.conf文件,配置日志存储路径
spark.eventLog.enabled true
spark.eventLog.dir hdfs://linux19:8020/directory
注意:需要启动hadoop集群,HDFS上的目录需要提前存在。
[root@linux19 hadoop]# sbin/start-dfs.sh
[root@linux19 hadoop]# hadoop fs -mkdir /directory
3)修改spark-env.sh文件, 添加日志配置
export SPARK_HISTORY_OPTS="
-Dspark.history.ui.port=18080
-Dspark.history.fs.logDirectory=hdfs://linux1:8020/directory
-Dspark.history.retainedApplications=30"
参数1含义:WEB UI访问的端口号为18080
参数2含义:指定历史服务器日志存储路径
参数3含义:指定保存Application历史记录的个数,如果超过这个值,旧的应用程序信息将被删除,这个是内存中的应用数,而不是页面上显示的应用数。
4)修改spark-defaults.conf
spark.yarn.historyServer.address=linux19:18080
spark.history.ui.port=18080
5)启动历史服务
sbin/start-history-server.sh
6)重新提交应用
bin/spark-submit
–class org.apache.spark.examples.SparkPi
–master yarn
–deploy-mode client
./examples/jars/spark-examples_2.12-3.0.0.jar
10
7)Web页面查看日志:http://linux29:8088

3.4 K8S & Mesos模式
Mesos是Apache下的开源分布式资源管理框架,它被称为是分布式系统的内核,在Twitter得到广泛使用,管理着Twitter超过30,0000台服务器上的应用部署,但是在国内,依然使用着传统的Hadoop大数据框架,所以国内使用Mesos框架的并不多,但是原理其实都差不多,这里我们就不做过多讲解了。
容器化部署是目前业界很流行的一项技术,基于Docker镜像运行能够让用户更加方便地对应用进行管理和运维。容器管理工具中最为流行的就是Kubernetes(k8s),而Spark也在最近的版本中支持了k8s部署模式。

基础编程

1.1 RDD创建

在Spark中创建RDD的创建方式可以分为四种:
1)从集合(内存)中创建RDD
从集合中创建RDD,Spark主要提供了两个方法:parallelize和makeRDD
val sparkConf =
new SparkConf().setMaster(“local[]").setAppName(“spark”)
val sparkContext = new SparkContext(sparkConf)
val rdd1 = sparkContext.parallelize(
List(1,2,3,4)
)
val rdd2 = sparkContext.makeRDD(
List(1,2,3,4)
)
rdd1.collect().foreach(println)
rdd2.collect().foreach(println)
sparkContext.stop()
从底层代码实现来讲,makeRDD方法其实就是parallelize方法
def makeRDD[T: ClassTag](
seq: Seq[T],
numSlices: Int = defaultParallelism): RDD[T] = withScope {
parallelize(seq, numSlices)
}
2)从外部存储(文件)创建RDD
由外部存储系统的数据集创建RDD包括:本地的文件系统,所有Hadoop支持的数据集,比如HDFS、HBase等。
val sparkConf =
new SparkConf().setMaster("local[
]”).setAppName(“spark”)
val sparkContext = new SparkContext(sparkConf)
val fileRDD: RDD[String] = sparkContext.textFile(“input”)
fileRDD.collect().foreach(println)
sparkContext.stop()
3)从其他RDD创建
主要是通过一个RDD运算完后,再产生新的RDD。详情请参考后续章节
4)直接创建RDD(new)
使用new的方式直接构造RDD,一般由Spark框架自身使用。

1.2 RDD并行度与分区

默认情况下,Spark可以将一个作业切分多个任务后,发送给Executor节点并行计算,而能够并行计算的任务数量我们称之为并行度。这个数量可以在构建RDD时指定。记住,这里的并行执行的任务数量,并不是指的切分任务的数量,不要混淆了。
val sparkConf =
new SparkConf().setMaster(“local[*]”).setAppName(“spark”)
val sparkContext = new SparkContext(sparkConf)
val dataRDD: RDD[Int] =
sparkContext.makeRDD(
List(1,2,3,4),
4)
val fileRDD: RDD[String] =
sparkContext.textFile(
“input”,
2)
fileRDD.collect().foreach(println)
sparkContext.stop()
读取内存数据时,数据可以按照并行度的设定进行数据的分区操作,数据分区规则的Spark核心源码如下:
def positions(length: Long, numSlices: Int): Iterator[(Int, Int)] = {
(0 until numSlices).iterator.map { i =>
val start = ((i * length) / numSlices).toInt
val end = (((i + 1) * length) / numSlices).toInt
(start, end)
}
}

1.3 RDD转换算子

RDD根据数据处理方式的不同将算子整体上分为Value类型、双Value类型和Key-Value类型
Value类型
1)map
函数签名
def map[U: ClassTag](f: T => U): RDD[U]
函数说明
将处理的数据逐条进行映射转换,这里的转换可以是类型的转换,也可以是值的转换。
val dataRDD: RDD[Int] = sparkContext.makeRDD(List(1,2,3,4))
val dataRDD1: RDD[Int] = dataRDD.map(
num => {
num * 2
}
)
val dataRDD2: RDD[String] = dataRDD1.map(
num => {
“” + num
}
)

2)mapPartitions
函数签名
def mapPartitions[U: ClassTag](
f: Iterator[T] => Iterator[U],
preservesPartitioning: Boolean = false): RDD[U]
函数说明
将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据。
val dataRDD1: RDD[Int] = dataRDD.mapPartitions(
datas => {
datas.filter(_==2)
}
)

3)mapPartitionsWithIndex
函数签名
def mapPartitionsWithIndex[U: ClassTag](
f: (Int, Iterator[T]) => Iterator[U],
preservesPartitioning: Boolean = false): RDD[U]
函数说明
将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据,在处理时同时可以获取当前分区索引。
val dataRDD1 = dataRDD.mapPartitionsWithIndex(
(index, datas) => {
datas.map(index, _)
}
)
4)flatMap
函数签名
def flatMap[U: ClassTag](f: T => TraversableOnce[U]): RDD[U]
函数说明
将处理的数据进行扁平化后再进行映射处理,所以算子也称之为扁平映射
val dataRDD = sparkContext.makeRDD(List(
List(1,2),List(3,4)
),1)
val dataRDD1 = dataRDD.flatMap(
list => list
)
例如:将List(List(1,2),3,List(4,5))进行扁平化操作
val dataRDD = sc.makeRDD(
List(List(1,2),3,List(4,5))
)

val rdd = dataRDD.flatMap(
data => {
data match {
case list: List[_] =>list
case d => List(d)
}
}
)

5)glom
函数签名
def glom(): RDD[Array[T]]
函数说明
将同一个分区的数据直接转换为相同类型的内存数组进行处理,分区不变
val dataRDD = sparkContext.makeRDD(List(
1,2,3,4
),1)
val dataRDD1:RDD[Array[Int]] = dataRDD.glom()
例如:计算所有分区最大值求和(分区内取最大值,分区间最大值求和)
val dataRDD: RDD[Int] = sc.makeRDD(List(1,2,3,4,5,6), 2)

// 将每个分区的数据转换为数组
val glomRDD: RDD[Array[Int]] = dataRDD.glom()

// 将数组中的最大值取出
// Array => max
val maxRDD: RDD[Int] = glomRDD.map(array=>array.max)

// 将取出的最大值求和
val array: Array[Int] = maxRDD.collect()

println(array.sum)

6)groupBy
函数签名
def groupBy[K](f: T => K)(implicit kt: ClassTag[K]): RDD[(K, Iterable[T])]
函数说明
将数据根据指定的规则进行分组, 分区默认不变,但是数据会被打乱重新组合,我们将这样的操作称之为shuffle。极限情况下,数据可能被分在同一个分区中
一个组的数据在一个分区中,但是并不是说一个分区中只有一个组
val dataRDD = sparkContext.makeRDD(List(1,2,3,4),1)
val dataRDD1 = dataRDD.groupBy(
_%2
)

7)filter
函数签名
def filter(f: T => Boolean): RDD[T]
函数说明
将数据根据指定的规则进行筛选过滤,符合规则的数据保留,不符合规则的数据丢弃。
当数据进行筛选过滤后,分区不变,但是分区内的数据可能不均衡,生产环境下,可能会出现数据倾斜。
val dataRDD = sparkContext.makeRDD(List(
1,2,3,4
),1)
val dataRDD1 = dataRDD.filter(_%2 == 0)
例如:从服务器日志数据apache.log中获取2020年5月20日的请求路径
val fileRDD: RDD[String] = sc.textFile(“input/apache.log”)

val timeRDD = fileRDD.map(
log => {
val datas = log.split(" ")
datas(3)
}
)

val filterRDD: RDD[String] = timeRDD.filter(time => {
val ymd = time.substring(0, 10)
ymd == “20/05/2020”
})
filterRDD.collect().foreach(println)

8)sample
函数签名
def sample(
withReplacement: Boolean,
fraction: Double,
seed: Long = Utils.random.nextLong): RDD[T]
函数说明
根据指定的规则从数据集中抽取数据
val dataRDD = sparkContext.makeRDD(List(
1,2,3,4
),1)
val dataRDD2 = dataRDD.sample(true, 2)

9)distinct
函数签名
def distinct()(implicit ord: Ordering[T] = null): RDD[T]
def distinct(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]
函数说明
将数据集中重复的数据去重
val dataRDD = sparkContext.makeRDD(List(
1,2,3,4,1,2
),1)
val dataRDD1 = dataRDD.distinct()
val dataRDD2 = dataRDD.distinct(2)
10)coalesce
函数签名
def coalesce(numPartitions: Int, shuffle: Boolean = false,
partitionCoalescer: Option[PartitionCoalescer] = Option.empty)
(implicit ord: Ordering[T] = null)
: RDD[T]
函数说明
根据数据量缩减分区,用于大数据集过滤后,提高小数据集的执行效率
当spark程序中,存在过多的小任务的时候,可以通过coalesce方法,收缩合并分区,减少分区的个数,减小任务调度成本
val dataRDD = sparkContext.makeRDD(List(
1,2,3,4,1,2
),6)

val dataRDD1 = dataRDD.coalesce(2)
11)repartition
函数签名
def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]
函数说明
该操作内部其实执行的是coalesce操作,参数shuffle的默认值为true。无论是将分区数多的RDD转换为分区数少的RDD,还是将分区数少的RDD转换为分区数多的RDD,repartition操作都可以完成,因为无论如何都会经shuffle过程。
val dataRDD = sparkContext.makeRDD(List(
1,2,3,4,1,2
),2)
val dataRDD1 = dataRDD.repartition(4
12)sortBy
函数签名
def sortBy[K](
f: (T) => K,
ascending: Boolean = true,
numPartitions: Int = this.partitions.length)
(implicit ord: Ordering[K], ctag: ClassTag[K]): RDD[T]
函数说明
该操作用于排序数据。在排序之前,可以将数据通过f函数进行处理,之后按照f函数处理的结果进行排序,默认为正序排列。排序后新产生的RDD的分区数与原RDD的分区数一致。
val dataRDD = sparkContext.makeRDD(List(
1,2,3,4,1,2
),2)

val dataRDD1 = dataRDD.sortBy(num=>num, false, 4)

13)pipe
函数签名
def pipe(command: String): RDD[String]
函数说明
管道,针对每个分区,都调用一次shell脚本,返回输出的RDD。

  1. 编写一个脚本,并增加执行权限
    [root@linux1 data]# vim pipe.sh
    #!/bin/sh
    echo “Come on”
    while read LINE; do
    echo “>>>”${LINE}
    done

[root@linux1 data]# chmod 777 pipe.sh
2) 命令行工具中创建一个只有一个分区的RDD
scala> val rdd = sc.makeRDD(List(“hi”,“Java”,“hello”,“are”,“you”), 1)
3) 将脚本作用该RDD并打印
scala> rdd.pipe("/opt/module/spark/pipe.sh").collect()
res18: Array[String] = Array(Start, >>>hi, >>>Java, >>>hello, >>>are, >>>you)
双Value类型
14)intersection
函数签名
def intersection(other: RDD[T]): RDD[T]
函数说明
对源RDD和参数RDD求交集后返回一个新的RDD
val dataRDD1 = sparkContext.makeRDD(List(1,2,3,4))
val dataRDD2 = sparkContext.makeRDD(List(3,4,5,6))
val dataRDD = dataRDD1.intersection(dataRDD2)
15)union
函数签名
def union(other: RDD[T]): RDD[T]
函数说明
对源RDD和参数RDD求并集后返回一个新的RDD
val dataRDD1 = sparkContext.makeRDD(List(1,2,3,4))
val dataRDD2 = sparkContext.makeRDD(List(3,4,5,6))
val dataRDD = dataRDD1.union(dataRDD2)

16)subtract
函数签名
def subtract(other: RDD[T]): RDD[T]
函数说明
以一个RDD元素为主,去除两个RDD中重复元素,将其他元素保留下来。求差集
val dataRDD1 = sparkContext.makeRDD(List(1,2,3,4))
val dataRDD2 = sparkContext.makeRDD(List(3,4,5,6))
val dataRDD = dataRDD1.subtract(dataRDD2)

17)partitionBy
函数签名
def partitionBy(partitioner: Partitioner): RDD[(K, V)]
函数说明
将数据按照指定Partitioner重新进行分区。Spark默认的分区器是HashPartitioner
val rdd: RDD[(Int, String)] =
sc.makeRDD(Array((1,“aaa”),(2,“bbb”),(3,“ccc”)),3)
import org.apache.spark.HashPartitioner
val rdd2: RDD[(Int, String)] =
rdd.partitionBy(new HashPartitioner(2))
18)reduceByKey
函数签名
def reduceByKey(func: (V, V) => V): RDD[(K, V)]
def reduceByKey(func: (V, V) => V, numPartitions: Int): RDD[(K, V)]
函数说明
可以将数据按照相同的Key对Value进行聚合
val dataRDD1 = sparkContext.makeRDD(List((“a”,1),(“b”,2),(“c”,3)))
val dataRDD2 = dataRDD1.reduceByKey(+)
val dataRDD3 = dataRDD1.reduceByKey(+, 2)

19)groupByKey
函数签名
def groupByKey(): RDD[(K, Iterable[V])]
def groupByKey(numPartitions: Int): RDD[(K, Iterable[V])]
def groupByKey(partitioner: Partitioner): RDD[(K, Iterable[V])]
函数说明
将分区的数据直接转换为相同类型的内存数组进行后续处理
val dataRDD1 =
sparkContext.makeRDD(List((“a”,1),(“b”,2),(“c”,3)))
val dataRDD2 = dataRDD1.groupByKey()
val dataRDD3 = dataRDD1.groupByKey(2)
val dataRDD4 = dataRDD1.groupByKey(new HashPartitioner(2))
思考一个问题:reduceByKey和groupByKey的区别?
两个算子在实现相同的业务功能时,reduceByKey存在预聚和功能,所以性能比较高,推荐使用。但是,不是说一定就采用这个方法,需要根据场景来选择

20)aggregateByKey
函数签名
def aggregateByKey[U: ClassTag](zeroValue: U)(seqOp: (U, V) => U,
combOp: (U, U) => U): RDD[(K, U)]
函数说明
将数据根据不同的规则进行分区内计算和分区间计算

val dataRDD1 =
sparkContext.makeRDD(List((“a”,1),(“b”,2),(“c”,3)))
val dataRDD2 =
dataRDD1.aggregateByKey(0)(+,+)
取出每个分区内相同key的最大值然后分区间相加
// TODO : 取出每个分区内相同key的最大值然后分区间相加
val rdd =
sc.makeRDD(List(
(“a”,1),(“a”,2),(“c”,3),
(“b”,4),(“c”,5),(“c”,6)
),2)
// 0:(“a”,1),(“a”,2),(“c”,3) => (a,10)(c,10)
// => (a,10)(b,10)(c,20)
// 1:(“b”,4),(“c”,5),(“c”,6) => (b,10)(c,10)

val resultRDD =
rdd.aggregateByKey(10)(
(x, y) => math.max(x,y),
(x, y) => x + y
)

resultRDD.collect().foreach(println)

21)combineByKey
函数签名
def combineByKey[C](
createCombiner: V => C,
mergeValue: (C, V) => C,
mergeCombiners: (C, C) => C): RDD[(K, C)]
函数说明
最通用的对key-value型rdd进行聚集操作的聚集函数(aggregation function)。类似于aggregate(),combineByKey()允许用户返回值的类型与输入不一致。
小练习:将数据List((“a”, 88), (“b”, 95), (“a”, 91), (“b”, 93), (“a”, 95), (“b”, 98))求每个key的平均值

val list: List[(String, Int)] = List((“a”, 88), (“b”, 95), (“a”, 91), (“b”, 93), (“a”, 95), (“b”, 98))
val input: RDD[(String, Int)] = sc.makeRDD(list, 2)

val combineRdd: RDD[(String, (Int, Int))] = input.combineByKey(
(_, 1),
(acc: (Int, Int), v) => (acc._1 + v, acc._2 + 1),
(acc1: (Int, Int), acc2: (Int, Int)) => (acc1._1 + acc2._1, acc1._2 + acc2._2)
)
22)sortByKey
函数签名
def sortByKey(ascending: Boolean = true, numPartitions: Int = self.partitions.length)
: RDD[(K, V)]
函数说明
在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的
val dataRDD1 = sparkContext.makeRDD(List((“a”,1),(“b”,2),(“c”,3)))
val sortRDD1: RDD[(String, Int)] = dataRDD1.sortByKey(true)
val sortRDD1: RDD[(String, Int)] = dataRDD1.sortByKey(false)

23)join
函数签名
def join[W](other: RDD[(K, W)]): RDD[(K, (V, W))]
函数说明
在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素连接在一起的(K,(V,W))的RDD
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, “a”), (2, “b”), (3, “c”)))
val rdd1: RDD[(Int, Int)] = sc.makeRDD(Array((1, 4), (2, 5), (3, 6)))
rdd.join(rdd1).collect().foreach(println)

24)leftOuterJoin
函数签名
def leftOuterJoin[T](other: RDD[(K, T)]): RDD[(K, (V, Option[T]))]
函数说明
类似于SQL语句的左外连接
val dataRDD1 = sparkContext.makeRDD(List((“a”,11),(“b”,22),(“c”,33)))
val dataRDD2 = sparkContext.makeRDD(List((“a”,11),(“b”,22),(“c”,33)))

val rdd: RDD[(String, (Int, Option[Int]))] = dataRDD1.leftOuterJoin(dataRDD2)

没有过不去的坎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值