6.2.1 Spark 《概述》意义,架构,部署模式《安装》本地,伪分布,集群(S/Y),开发环境《RDD编程》概述,RDD创建(集合/文件/RDD),转换子(宽/窄依赖)

目录

第一部分 Spark Core

第1节 Spark概述

1.1 什么是Spark

1.2 Spark 与 Hadoop

1.3 系统架构

1.4 Spark集群部署模式

1、Standalone模式

2、Spark On Yarn模式

3、Spark On Mesos模式

1.5 相关术语

第2节 Spark安装配置

2.1 Spark安装

2.2 本地模式

2.3 伪分布式

2.4 集群模式--Standalone模式

2.4.1 Standalone 配置

2.4.2 运行模式(cluster / client)

2.4.3 History Server

2.4.4 高可用配置

2.5 集群模式--Yarn模式

整合HistoryServer服务

2.6 开发环境搭建IDEA

第3节 RDD编程

3.1 什么是RDD

3.2 RDD的特点

1、分区

2、只读

3、依赖

4、缓存

5、checkpoint

3.3 Spark编程模型

3.4 RDD的创建

1、SparkContext

2、从集合创建RDD

3、从文件系统创建RDD

4、从RDD创建RDD

3.5 Transformation【重要】

常见转换算子1

常见转换算子2

常见转换算子3


 

第一部分 Spark Core

第1节 Spark概述

1.1 什么是Spark

 

Spark 是一个快速、通用的计算引擎。

Spark的特点:

速度快。

使用简单。

通用。批处理、交互式查询(Spark SQL)、实时流处理(Spark Streaming)、机器学习(Spark MLlib)和图计算(GraphX)。

兼容好。Spark可以使用YARN、Mesos作为它的资源管理和调度器;可以处理所有Hadoop支持的数据,包括HDFS、HBase和Cassandra等。这对于已经部署Hadoop集群的用户特别重要,因为不需要做任何数据迁移就可以使用Spark的强大处理能力。Spark也可以不依赖于第三方的资源管理和调度器,它实现了Standalone作为其内置的资源管理和调度框架,这样进一步降低了Spark的使用门槛,使得所有人都可以非常容易地部署和使用Spark。此外,Spark还提供了在EC2上部署Standalone的Spark集群的工具。

 

1.2 Spark 与 Hadoop

从狭义的角度上看:Hadoop是一个分布式框架,由存储、资源调度、计算三部分组成;
Spark是一个分布式计算引擎,由 Scala 语言编写的计算框架,基于内存的快速、通用、可扩展的大数据分析引擎;
从广义的角度上看,Spark是Hadoop生态中不可或缺的一部分;

 

MapReduce的不足:

表达能力有限, 只有map和reduce

磁盘IO开销大

延迟高

  • 任务之间的衔接有IO开销
  • 在前一个任务执行完成之前,后一个任务无法开始。难以胜任复杂的、多阶段计算任务

 

Spark在借鉴MapReduce优点的同时,很好地解决了MapReduce所面临的问题。


备注:Spark的计算模式也属于MapReduce;Spark框架是对MR框架的优化;

 

在实际应用中,大数据应用主要包括以下三种类型:

  • 批量处理(离线处理):通常时间跨度在数十分钟到数小时之间
  • 交互式查询:通常时间跨度在数十秒到数分钟之间
  • 流处理(实时处理):通常时间跨度在数百毫秒到数秒之间

 

当同时存在以上三种场景时,传统的Hadoop框架需要同时部署三种不同的软件。如:

  • MapReduce / Hive 或 Impala / Storm

 

这样做难免会带来一些问题:

  • 不同场景之间输入输出数据无法做到无缝共享,通常需要进行数据格式的转换
  • 不同的软件需要不同的开发和维护团队,带来了较高的使用成本
  • 比较难以对同一个集群中的各个系统进行统一的资源协调和分配

 

Spark所提供的生态系统足以应对上述三种场景,即同时支持批处理、交互式查询和流数据处理:

  • Spark的设计遵循“一个软件栈满足不同应用场景”的理念(all in one),逐渐形成了一套完整的生态系统
  • 既能够提供内存计算框架,也可以支持SQL即席查询、实时流式计算、机器学习和图计算等
  • Spark可以部署在资源管理器YARN之上,提供一站式的大数据解决方案

 

Spark 为什么比 MapReduce 快:

1、Spark积极使用内存。MR框架中一个Job 只能拥有一个 map task 和一个 reduce task。如果业务处理逻辑复杂,一个map和一个reduce是表达不出来的,这时就需要将多个 job 组合起来;然而前一个job的计算结果必须写到HDFS,才能交给后一个job。这样一个复杂的运算,在MR框架中会发生很多次写入、读取操作操作;Spark框架则可以把多个map reduce task组合在一起连续执行,中间的计算结果不需要落地;

复杂的MR任务:mr + mr + mr + mr +mr ... 
复杂的Spark任务:mr -> mr -> mr ... 

2、多进程模型(MR) vs 多线程模型(Spark)。MR框架中的的Map Task和Reduce Task是进程级别的,而Spark Task是基于线程模型的。MR框架中的 map task、reduce task都是 jvm 进程,每次启动都需要重新申请资源,消耗了不必要的时间。Spark则是通过复用线程池中的线程来减少启动、关闭task所需要的系统开销。

 

1.3 系统架构

Spark运行架构包括:

  • Cluster Manager
  • Worker Node
  • Driver
  • Executor

 

Cluster Manager 是集群资源的管理者。Spark支持3种集群部署模式:Standalone、Yarn、Mesos;

Worker Node 工作节点,管理本地资源;

Driver Program。运行应用的 main() 方法并且创建了 SparkContext。由Cluster Manager分配资源,SparkContext发送 Task 到 Executor 上执行;

Executor:在工作节点上运行,执行 Driver 发送的 Task,并向 Dirver 汇报计算结果;

 

1.4 Spark集群部署模式

Spark支持3种集群部署模式:Standalone、Yarn、Mesos;

1、Standalone模式

  • 独立模式,自带完整的服务,可单独部署到一个集群中,无需依赖任何其他资源管理系统。从一定程度上说,该模式是其他两种的基础
  • Cluster Manager:Master
  • Worker Node:Worker
  • 仅支持粗粒度的资源分配方式

2、Spark On Yarn模式

  • Yarn拥有强大的社区支持,且逐步已经成为大数据集群资源管理系统的标准
  • 在国内生产环境中运用最广泛的部署模式
  • Spark on yarn 的支持两种模式:yarn-cluster:适用于生产环境    yarn-client:适用于交互、调试,希望立即看到app的输出
  • Cluster Manager:ResourceManager
  • Worker Node:NodeManager
  • 仅支持粗粒度的资源分配方式

3、Spark On Mesos模式

  • 官方推荐的模式。Spark开发之初就考虑到支持Mesos
  • Spark运行在Mesos上会比运行在YARN上更加灵活,更加自然
  • Cluster Manager:Mesos Master
  • Worker Node:Mesos Slave
  • 支持粗粒度、细粒度的资源分配方式

 

粗粒度模式(Coarse-grained Mode):每个应用程序的运行环境由一个Dirver和若干个Executor组成,其中,每个Executor占用若干资源,内部可运行多个Task。应用程序的各个任务正式运行之前,需要将运行环境中的资源全部申请好,且运行过程中要一直占用这些资源,即使不用,最后程序运行结束后,回收这些资源。

细粒度模式(Fine-grained Mode):鉴于粗粒度模式会造成大量资源浪费,Spark On Mesos还提供了另外一种调度模式:细粒度模式,这种模式类似于现在的云计算,核心思想是按需分配

 

三种集群部署模式如何选择:

  • 生产环境中选择Yarn,国内使用最广的模式
  • Spark的初学者:Standalone,简单
  • 开发测试环境,可选择Standalone
  • 数据量不太大、应用不是太复杂,建议可以从Standalone模式开始
  • mesos不会涉及到

 

 

1.5 相关术语

http://spark.apache.org/docs/latest/cluster-overview.html

  • Application 用户提交的spark应用程序,由集群中的一个driver 和 许多executor 组成
  • Application jar 一个包含spark应用程序的jar,jar不应该包含 Spark 或 Hadoop的 jar,这些jar应该在运行时添加
  • Driver program 运行应用程序的main(),并创建SparkContext(Spark应用程序)
  • Cluster manager 管理集群资源的服务,如standalone,Mesos,Yarn
  • Deploy mode 区分 driver 进程在何处运行。在 Cluster 模式下,在集群内部运行 Driver。 在 Client 模式下,Driver 在集群外部运行
  • Worker node 运行应用程序的工作节点
  • Executor 运行应用程序 Task 和保存数据,每个应用程序都有自己的executors,并且各个executor相互独立
  • Task  executors应用程序的最小运行单元
  • Job  在用户程序中,每次调用Action函数都会产生一个新的job,也就是说每个 Action 生成一个job
  • Stage  一个 job 被分解为多个 stage,每个 stage 是一系列 Task 的集合

 

第2节 Spark安装配置

2.1 Spark安装

官网地址:http://spark.apache.org/
文档地址:http://spark.apache.org/docs/latest/
下载地址:http://spark.apache.org/downloads.html
下载Spark安装包

 

下载地址:https://archive.apache.org/dist/spark/
备注:不用安装scala

安装步骤:
1、下载软件解压缩,移动到指定位置

cd /opt/lagou/software/
tar zxvf spark-2.4.5-bin-without-hadoop-scala-2.12.tgz
mv spark-2.4.5-bin-without-hadoop-scala-2.12/ ../servers/spark-2.4.5/

 

2、设置环境变量,并使之生效

vi /etc/profile

export SPARK_HOME=/opt/lagou/servers/spark-2.4.5
export PATH=$PATH:$SPARK_HOME/bin:$SPARK_HOME/sbin

source /etc/profile

 

3、修改配置

文件位置:$SPARK_HOME/conf
修改文件:slaves、spark-defaults.conf、spark-env.sh、log4j.properties
more slaves

linux121
linux122
linux123

more spark-defaults.conf

spark.master                spark://linux121:7077
spark.eventLog.enabled      true
spark.eventLog.dir          hdfs://linux121:9000/spark-eventlog
spark.serializer            org.apache.spark.serializer.KryoSerializer
spark.driver.memory         512m  # 缺省是 1G

创建 HDFS 目录:hdfs dfs -mkdir /spark-eventlog
备注:

  • spark.master。定义master节点,缺省端口号 7077
  • spark.eventLog.enabled。开启eventLog
  • spark.eventLog.dir。eventLog的存放位置
  • spark.serializer。一个高效的序列化器
  • spark.driver.memory。定义driver内存的大小(缺省1G)

修改spark-env.sh

export JAVA_HOME=/opt/lagou/servers/jdk1.8.0_231
export HADOOP_HOME=/opt/lagou/servers/hadoop-2.9.2
export HADOOP_CONF_DIR=/opt/lagou/servers/hadoop-2.9.2/etc/hadoop
export SPARK_DIST_CLASSPATH=$(/opt/lagou/servers/hadoop-2.9.2/bin/hadoop classpath)
export SPARK_MASTER_HOST=linux121
export SPARK_MASTER_PORT=7077

备注:这里使用的是 spark-2.4.5-bin-without-hadoop,所以要将 Hadoop 相关 jars 的位置告诉Spark

 

4、将Spark软件分发到集群;修改其他节点上的环境变量

cd /opt/lagou/servers/


scp -r spark-2.4.5/ linux122:$PWD
scp -r spark-2.4.5/ linux123:$PWD

5、启动集群

cd $SPARK_HOME/sbin
./start-all.sh

分别在linux121、linux122、linux123上执行 jps,可以发现:

linux121:Master、Worker

linux122:Worker

linux123:Worker

此时 Spark 运行在 Standalone 模式下。

 

在浏览器中输入:http://linux121:8080/
可以看见如下 Spark 的 Web 界面:

 

备注:在$HADOOP_HOME/sbin 及 $SPARK_HOME/sbin 下都有 start-all.sh 和 stop-all.sh 文件

在输入 start-all.sh / stop-all.sh 命令时,谁的搜索路径在前面就先执行谁,此时会产生冲突。
解决方案:

  • 删除一组 start-all.sh / stop-all.sh 命令,让另外一组命令生效
  • 将其中一组命令重命名。如:将 $HADOOP_HOME/sbin 路径下的命令重命名为:start-all-hadoop.sh / stop-all-hadoop.sh
  • 将其中一个框架的 sbin 路径不放在 PATH 中

 

6、集群测试

run-example SparkPi 10
spark-shell

// HDFS 文件
val lines = sc.textFile("/wcinput/wc.txt")
lines.flatMap(_.split(" ")).map((_, 1)).reduceByKey(_+_).collect().foreach(println)

Spark集群是否一定依赖hdfs?不是的,除非用到了hdfs。

 

Apache Spark支持多种部署模式。最简单的就是单机本地模式(Spark所有进程都运行在一台机器的JVM中)、伪分布式模式(在一台机器中模拟集群运行,相关的进程在同一台机器上)。分布式模式包括:Standalone、Yarn、Mesos。

 

Apache Spark支持多种部署模式:

本地模式。最简单的运行模式,Spark所有进程都运行在一台机器的 JVM 中
伪分布式模式。在一台机器中模拟集群运行,相关的进程在同一台机器上(用的非常少)
分布式模式。包括:Standalone、Yarn、Mesos

  • Standalone。使用Spark自带的资源调度框架
  • Yarn。使用 Yarn 资源调度框架
  • Mesos。使用 Mesos 资源调度框架

 

2.2 本地模式

本地模式部署在单机,主要用于测试或实验;最简单的运行模式,所有进程都运行在一台机器的 JVM 中;

本地模式用单机的多个线程来模拟Spark分布式计算,通常用来验证开发出来的应用程序逻辑上有没有问题;

这种模式非常简单,只需要把Spark的安装包解压后,改一些常用的配置即可使用。不用启动Spark的Master、Worker守护进程,也不用启动Hadoop的服务(除非用到HDFS)。

  • local:在本地启动一个线程来运行作业;
  • local[N]:启动了N个线程;
  • local[*]:使用了系统中所有的核;
  • local[N,M]:第一个参数表示用到核的个数;第二个参数表示容许作业失败的次数

前面几种模式没有指定M参数,其默认值都是1;

 

测试:
1、关闭相关服务

stop-dfs.sh
stop-all.sh

2、启动 Spark 本地运行模式

spark-shell --master local

备注:此时可能有错误。主要原因是配置了日志聚合(即是用来了hdfs,但hdfs服务关闭了),关闭该选项即可

# spark-defaults.conf文件中,注释以下两行:
spark.eventLog.enabled true
spark.eventLog.dir hdfs://linux121:9000/spark-eventlog

3、使用 jps 检查,发现一个 SparkSubmit 进程

这个SparkSubmit进程又当爹、又当妈。既是客户提交任务的Client进程、又是Spark的driver程序、还充当着Spark执行Task的Executor角色。

4、执行简单的测试程序

val lines = sc.textFile("file:///root/a.txt")
lines.count

 

2.3 伪分布式

伪分布式模式:在一台机器中模拟集群运行,相关的进程在同一台机器上;

备注:不用启动集群资源管理服务;

local-cluster[N,cores,memory]

  • N模拟集群的 Slave(或worker)节点个数
  • cores模拟集群中各个Slave节点上的内核数
  • memory模拟集群的各个Slave节点上的内存大小

备注:参数之间没有空格,memory不能加单位

 

1、启动 Spark 伪分布式模式

spark-shell --master local-cluster[4,2,1024]

 

2、使用 jps 检查,发现1个 SparkSubmit 进程和4个 CoarseGrainedExecutorBackend 进程

SparkSubmit依然充当全能角色,又是Client进程,又是Driver程序,还有资源管理的作用。

4个CoarseGrainedExecutorBackend,用来并发执行程序的进程。

 

3、执行简单的测试程序

spark-submit --master local-cluster[4,2,1024] --class org.apache.spark.examples.SparkPi
$SPARK_HOME/examples/jars/spark-examples_2.11-2.4.5.jar 10

备注:

  • local-cluster[4,2,1024],参数不要给太大,资源不够
  • 这种模式少用,有Bug。SPARK-32236

 

2.4 集群模式--Standalone模式

参考:http://spark.apache.org/docs/latest/spark-standalone.html

  • 分布式部署才能真正体现分布式计算的价值
  • 与单机运行的模式不同,这里必须先启动Spark的Master和Worker守护进程;关闭 yarn 对应的服务
  • 不用启动Hadoop服务,除非要使用HDFS的服务

 

使用jps检查,可以发现:

  • linux121:Master、Worker
  • linux122:Worker
  • linux123:Worker

使用浏览器查看(linux121:8080)

 

2.4.1 Standalone 配置

  • sbin/start-master.sh /  sbin/stop-master.sh
  • sbin/start-slaves.sh /  sbin/stop-slave.sh
  • sbin/start-slave.sh /  sbin/stop-slaves.sh
  • sbin/start-all.sh /  sbin/stop-all.sh

备注:./sbin/start-slave.sh [options];启动节点上的worker进程,调试中较为常用

 

在 spark-env.sh 中定义:


SPARK_WORKER_CORES:Total number of cores to allow Spark applications to use on the machine (default: all available cores).
SPARK_WORKER_MEMORY:Total amount of memory to allow Spark applications to use on the machine, e.g.1000m ,  2g (default: total memory minus 1 GiB); note that each application's individual memory is configured using its  spark.executor.memory property.

 

测试在 spark-env.sh 中增加参数,分发到集群,重启服务:

export SPARK_WORKER_CORES=10
export SPARK_WORKER_MEMORY=20g

在浏览器中观察集群状态,测试完成后将以上两个参数分别改为2、2g,重启服务。

 

2.4.2 运行模式(cluster / client)

最大的区别:Driver运行在哪里;client是缺省的模式,能看见返回结果,适合调试;cluster与此相反;

  • Client模式(缺省)。Driver运行在提交任务的Client,此时可以在Client模式下,看见应用的返回结果,适合交互、调试
  • Cluster模式。Driver运行在Spark集群中,看不见程序的返回结果,合适生产环境

 

测试1(Client 模式):

spark-submit --class org.apache.spark.examples.SparkPi \
$SPARK_HOME/examples/jars/spark-examples_2.11-2.4.5.jar 1000

再次使用 jps 检查集群中的进程:

  • Master进程做为cluster manager,管理集群资源
  • Worker 管理节点资源
  • SparkSubmit 做为Client端,运行 Driver 程序。Spark Application执行完成,进程终止
  • CoarseGrainedExecutorBackend,运行在Worker上,用来并发执行应用程序

 

测试2(Cluster 模式):

spark-submit --class org.apache.spark.examples.SparkPi \
--deploy-mode cluster \
$SPARK_HOME/examples/jars/spark-examples_2.11-2.4.5.jar 1000

  • SparkSubmit 进程会在应用程序提交给集群之后就退出
  • Master会在集群中选择一个 Worker 进程生成一个子进程 DriverWrapper 来启动 Driver 程序
  • Worker节点上会启动 CoarseGrainedExecutorBackend
  • DriverWrapper 进程会占用 Worker 进程的一个core (缺省分配1个core,1G内存)
  • 应用程序的结果,会在执行 Driver 程序的节点的 stdout 中输出,而不是打印在屏幕上

 

在启动 DriverWrapper 的节点上,进入 $SPARK_HOME/work/,可以看见类似 driver-20200810233021-0000 的目录,这个就是 driver 运行时的日志文件,进入该目录,会发现:

  • jar 文件,这就是移动的计算
  • stderr 运行日志
  • stdout 输出结果

 

2.4.3 History Server

# spark-defaults.conf
# history server
spark.eventLog.enabled true
spark.eventLog.dir  hdfs://linux121:9000/spark-eventlog
spark.eventLog.compress true

 

# spark-env.sh
export SPARK_HISTORY_OPTS="-Dspark.history.ui.port=18080 -Dspark.history.retainedApplications=50 -Dspark.history.fs.logDirectory=hdfs://linux121:9000/spark-eventlog"

spark.history.retainedApplications。设置缓存Cache中保存的应用程序历史记录的个数(默认50),如果超过这个值,旧的将被删除;

缓存文件数不表示实际显示的文件总数。只是表示不在缓存中的文件可能需要从硬盘读取,速度稍有差别

 

前提条件:启动hdfs服务(日志写到HDFS)

启动historyserver,使用 jps 检查,可以看见 HistoryServer 进程。如果看见该进程,请检查对应的日志。

$SPARK_HOME/sbin/start-history-server.sh

 

web端地址:http://linux121:18080/

 

2.4.4 高可用配置

Spark Standalone集群是 Master-Slaves架构的集群模式,和大部分的Master-Slaves结构集群一样,存着Master单点故障的问题。如何解决这个问题,Spark提供了两种方案:

(1)基于zookeeper的Standby Master,适用于生产模式。将 Spark 集群连接到Zookeeper,利用 Zookeeper提供的选举和状态保存的功能,一个 Master 处于 Active 状态,其他 Master 处于Standby状态;保证在ZK中的元数据主要是集群的信息,包括:Worker,Driver和Application以及Executors的信息如果Active的Master挂掉了,通过选举产生新的 Active 的 Master,然后执行状态恢复,整个恢复过程可能需要1~2分钟;

(2)基于文件系统的单点恢复(Single-Node Rcovery with Local File System),主要用于开发或者测试环境。将Spark Application 和 Worker 的注册信息保存在文件中,一旦Master发生故障,就可以重新启动Master进程,将系统恢复到之前的状态

配置步骤:

1、安装ZooKeeper,并启动

2、修改 spark-env.sh 文件,并分发到集群中

# 注释以下两行!!!
# export SPARK_MASTER_HOST=linux121
# export SPARK_MASTER_PORT=7077

 

# 添加以下内容
export SPARK_DAEMON_JAVA_OPTS="-Dspark.deploy.recoveryMode=ZOOKEEPER -Dspark.deploy.zookeeper.url=linux121,linux122,linux123 -Dspark.deploy.zookeeper.dir=/spark"

备注:

  • spark.deploy.recoveryMode:可选值 Zookeeper、FileSystem、None
  • deploy.zookeeper.url:Zookeeper的URL,主机名:端口号(缺省2181)
  • deploy.zookeeper.dir:保存集群元数据信息的地址,在ZooKeeper中保存该信息

 

3、启动 Spark 集群(linux121)

$SPARK_HOME/sbin/start-all.sh

浏览器输入:http://linux121:8080/,刚开始 Master 的状态是STANDBY,稍等一会变为:RECOVERING,最终是:ALIVE

 

4、在 linux122 上启动master

$SPARK_HOME/sbin/start-master.sh

进入浏览器输入:http://linux122:8080/,此时 Master 的状态为:STANDBY

 

5、杀掉linux121上 Master 进程,再观察 linux122 上 Master 状态,由 STANDBY => RECOVERING => ALIVE

 

小结:

  • 配置每个worker的core、memory
  • 运行模式:cluster、client;client缺省模式,有返回结果,适合调试;cluster与此相反
  • History server
  • 高可用(ZK、Local Flile;在ZK中记录集群的状态)

 

2.5 集群模式--Yarn模式

参考:http://spark.apache.org/docs/latest/running-on-yarn.html
需要启动的服务:hdfs服务、yarn服务
需要关闭 Standalone 对应的服务(即集群中的Master、Worker进程),一山不容二虎!

 

在Yarn模式中,Spark应用程序有两种运行模式:

  • yarn-client。Driver程序运行在客户端,适用于交互、调试,希望立即看到app的输出
  • yarn-cluster。Driver程序运行在由RM启动的 AppMaster中,适用于生产环境

二者的主要区别:Driver在哪里

 

1、关闭 Standalon 模式下对应的服务;开启 hdfs、yarn、historyserver 服务

 

2、修改 yarn-site.xml 配置

在 $HADOOP_HOME/etc/hadoop/yarn-site.xml 中增加,分发到集群,重启 yarn 服务

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

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

备注:

  • yarn.nodemanager.pmem-check-enabled。是否启动一个线程检查每个任务正使用的物理内存量,如果任务超出分配值,则直接将其杀掉,默认是true
  • yarn.nodemanager.vmem-check-enabled。是否启动一个线程检查每个任务正使用的虚拟内存量,如果任务超出分配值,则直接将其杀掉,默认是true

 

3、修改配置,分发到集群

# spark-env.sh 中这一项必须要有
export HADOOP_CONF_DIR=/opt/lagou/servers/hadoop-2.9.2/etc/hadoop

# spark-default.conf(以下是优化)
# 与 hadoop historyserver集成
spark.yarn.historyServer.address linux121:18080

# 添加(以下是优化)
spark.yarn.jars hdfs:///spark-yarn/jars/*.jar

# 将 $SPARK_HOME/jars 下的jar包上传到hdfs
hdfs dfs -mkdir -p /spark-yarn/jars/
cd $SPARK_HOME/jars
hdfs dfs -put * /spark-yarn/jars/

 

4、测试

# client
spark-submit --master yarn \
--deploy-mode client \
--class org.apache.spark.examples.SparkPi \
$SPARK_HOME/examples/jars/spark-examples_2.12-2.4.5.jar 2000

在提取App节点上可以看见:SparkSubmit、CoarseGrainedExecutorBackend
在集群的其他节点上可以看见:CoarseGrainedExecutorBackend
在提取App节点上可以看见:程序计算的结果(即可以看见计算返回的结果)

 

# cluster
spark-submit --master yarn \
--deploy-mode cluster \
--class org.apache.spark.examples.SparkPi \
$SPARK_HOME/examples/jars/spark-examples_2.12-2.4.5.jar 2000

在提取App节点上可以看见:SparkSubmit
在集群的其他节点上可以看见:CoarseGrainedExecutorBackend、ApplicationMaster(Driver运行在此)
在提取App节点上看不见最终的结果

 

整合HistoryServer服务

前提:Hadoop的 HDFS、Yarn、HistoryServer 正常;Spark historyserver服务正常;
Hadoop:JobHistoryServer
Spark:HistoryServer

spark-submit \
--class org.apache.spark.examples.SparkPi \
--master yarn \
--deploy-mode client \
$SPARK_HOME/examples/jars/spark-examples_2.12-2.4.5.jar \
20

 

1、修改 spark-defaults.conf,并分发到集群

# 修改 spark-defaults.conf
spark.master spark://linux121:7077
spark.eventLog.enabled true
spark.eventLog.dir hdfs://linux121:9000/spark-eventlog
spark.serializer org.apache.spark.serializer.KryoSerializer
spark.driver.memory 512m

# 新增
spark.yarn.historyServer.address linux121:18080
spark.history.ui.port 18080

2、重启/启动 spark 历史服务

stop-history-server.sh
start-history-server.sh

3、提交任务

spark-submit \
--class org.apache.spark.examples.SparkPi \
--master yarn \
--deploy-mode client \
$SPARK_HOME/examples/jars/spark-examples_2.12-2.4.5.jar \
100

4、Web页面查看日志(图见上)

备注:
1、在课程学习的过程中,大多数情况使用Standalone模式 或者 local模式
2、建议不使用HA;更多关注的Spark开发

 

2.6 开发环境搭建IDEA

前提:安装scala插件;能读写HDFS文件

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.ch</groupId>
    <artifactId>spark_code</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <scala.version>2.12.10</scala.version>
        <spark.version>2.4.5</spark.version>
        <hadoop.version>2.9.2</hadoop.version>
        <encoding>UTF-8</encoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>${scala.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-core_2.12</artifactId>
            <version>${spark.version}</version>
        </dependency>
    </dependencies>
    <build>
        <pluginManagement>
            <plugins>
                <!-- 编译scala的插件 -->
                <plugin>
                    <groupId>net.alchim31.maven</groupId>
                    <artifactId>scala-maven-plugin</artifactId>
                    <version>3.2.2</version>
                </plugin>
                <!-- 编译java的插件 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.5.1</version>
                </plugin>
            </plugins>
        </pluginManagement>
        <plugins>
            <plugin>
                <groupId>net.alchim31.maven</groupId>
                <artifactId>scala-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <id>scala-compile-first</id>
                        <phase>process-resources</phase>
                        <goals>
                            <goal>add-source</goal>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>scala-test-compile</id>
                        <phase>process-test-resources</phase>
                        <goals>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>compile</phase>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <!-- 打jar插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.4.3</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <filters>
                                <filter>
                                    <artifact>*:*</artifact>
                                    <excludes>
                                        <exclude>META-INF/*.SF</exclude>
                                        <exclude>META-INF/*.DSA</exclude>
                                        <exclude>META-INF/*.RSA</exclude>
                                    </excludes>
                                </filter>
                            </filters>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

 

package com.ch.sparkcore

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

/**
 * @author CH
 */
object WordCount {
  def main(args: Array[String]): Unit = {

    val conf = new SparkConf().setMaster("local").setAppName("WordCount")
    val sc = new SparkContext(conf)

    // 使用hdfs文件
    //val lines: RDD[String] = sc.textFile("hdfs://linux121:9000/wcinput/wc.txt")

    // 使用hdfs文件, 有配置文件 src/main/resources/core-site.xml
    val lines: RDD[String] = sc.textFile("/wcinput/wc.txt")

    // 使用本地文件
    // val lines: RDD[String] = sc.textFile("data/wc.dat")

    lines.flatMap(_.split(" ")).map((_, 1)).reduceByKey(_+_).collect().foreach(println)
    sc.stop()
  }
}

备注:core-site.xml;链接源码;

 

 

第3节 RDD编程

3.1 什么是RDD

RDD是 Spark 的基石,是实现 Spark 数据处理的核心抽象。
RDD 是一个抽象类,它代表一个不可变、可分区、里面的元素可并行计算的集合。


RDD(Resilient Distributed Dataset)是 Spark 中的核心概念,它是一个容错、可以并行执行的分布式数据集。

RDD包含5个特征(记住):

1. 一个分区的列表
2. 一个计算函数compute,对每个分区进行计算
3. 对其他RDDs的依赖(宽依赖、窄依赖)列表
4. 对key-value RDDs来说,存在一个分区器(Partitioner)【可选的】
5. 对每个分区有一个优先位置的列表【可选的】

 

  • 一组分片(Partition),即数据集的基本组成单位。对于RDD来说,每个分片都会被一个计算任务处理,并决定并行计算的粒度。用户可以在创建RDD时指定RDD的分片个数,如果没有指定,那么就会采用默认值;
  • 一个对分区数据进行计算的函数。Spark中RDD的计算是以分片为单位的,每个RDD都会实现 compute 函数以达到该目的。compute函数会对迭代器进行组合,不需要保存每次计算的结果;
  • RDD之间的存在依赖关系。RDD的每次转换都会生成一个新的RDD,RDD之间形成类似于流水线一样的前后依赖关系(lineage)。在部分分区数据丢失时,Spark可以通过这个依赖关系重新计算丢失的分区数据,而不是对RDD的所有分区进行重新计算;
  • 对于 key-value 的RDD而言,可能存在分区器(Partitioner)。Spark 实现了两种类型的分片函数,一个是基于哈希的HashPartitioner,另外一个是基于范围的RangePartitioner。只有 key-value 的RDD,才可能有Partitioner,非key-value的RDD的Parititioner的值是None。Partitioner函数决定了RDD本身的分片数量,也决定了parent RDD Shuffle输出时的分片数量;
  • 一个列表,存储每个Partition的优先位置(preferred location)。对于一个HDFS文件来说,这个列表保存的就是每个Partition所在的块的位置。按照“移动计算不移动数据”的理念,Spark在任务调度的时候,会尽可能地将计算任务分配到其所要处理数据块的存储位置。

 

3.2 RDD的特点

1、分区

RDD逻辑上是分区的,每个分区的数据是抽象存在的,计算的时候会通过一个 compute 函数得到每个分区的数据。
如果RDD是通过已有的文件系统构建,则compute函数是读取指定文件系统中的数据,如果RDD是通过其他RDD转换而来,则compute函数是执行转换逻辑将其他RDD的数据进行转换。

 

2、只读

RDD是只读的,要想改变RDD中的数据,只能在现有的RDD基础上创建新的RDD;

一个RDD转换为另一个RDD,通过丰富的操作算子(map、filter、union、join、reduceByKey… …)实现,不再像MR那样只能写map和reduce了。

RDD的操作算子包括两类:

  • transformation。用来对RDD进行转化,延迟执行(Lazy);
  • action。用来触发RDD的计算;得到相关计算结果或者将RDD保存的文件系统中;

 

3、依赖

RDDs通过操作算子进行转换,转换得到的新RDD包含了从其他RDDs衍生所必需的信息,RDDs之间维护着这种血缘关系(lineage),也称之为依赖。

依赖包括两种:

  • 窄依赖。RDDs之间分区是一一对应的(1:1 或 n:1)
  • 宽依赖。子RDD每个分区与父RDD的每个分区都有关,是多对多的关系(即 n:m)。有shuffle发生

 

4、缓存

可以控制存储级别(内存、磁盘等)来进行缓存。
如果在应用程序中多次使用同一个RDD,可以将该RDD缓存起来,该RDD只有在第一次计算的时候会根据血缘关系得到分区的数据,在后续其他地方用到该RDD的时候,会直接从缓存处取而不用再根据血缘关系计算,这样就加速后期的重用。

 

5、checkpoint

虽然RDD的血缘关系天然地可以实现容错,当RDD的某个分区数据失败或丢失,可以通过血缘关系重建。

但是于长时间迭代型应用来说,随着迭代的进行,RDDs之间的血缘关系会越来越长,一旦在后续迭代过程中出错,则需要通过非常长的血缘关系去重建,势必影响性能。

RDD支持 checkpoint 将数据保存到持久化的存储中,这样就可以切断之前的血缘关系,因为checkpoint后的RDD不需要知道它的父RDDs了,它可以从 checkpoint 处拿到数据。

 

3.3 Spark编程模型

  • RDD表示数据对象
  • 通过对象上的方法调用来对RDD进行转换
  • 最终显示结果 或 将结果输出到外部数据源
  • RDD转换算子称为Transformation是Lazy的(延迟执行)
  • 只有遇到Action算子,才会执行RDD的转换操作

 

要使用Spark,需要编写 Driver 程序,它被提交到集群运行

  • Driver中定义了一个或多个 RDD ,并调用 RDD 上的各种算子
  • Worker则执行RDD分区计算任务

 

3.4 RDD的创建

1、SparkContext

SparkContext是编写Spark程序用到的第一个类,是Spark的主要入口点,它负责和整个集群的交互;

如把Spark集群当作服务端,那么Driver就是客户端,SparkContext 是客户端的核心;
SparkContext是Spark的对外接口,负责向调用者提供 Spark 的各种功能;
SparkContext用于连接Spark集群、创建RDD、累加器、广播变量;

 

在 spark-shell 中 SparkContext 已经创建好了,可直接使用;
编写Spark Driver程序第一件事就是:创建SparkContext;

建议:Standalone模式或本地模式学习RDD的各种算子;
不需要HA;不需要IDEA

 

2、从集合创建RDD

从集合中创建RDD,主要用于测试。Spark 提供了以下函数:parallelize、makeRDD、range

val rdd1 = sc.parallelize(Array(1,2,3,4,5))

rdd1.collect
// res16: Array[Int] = Array(1, 2, 3, 4, 5)

val rdd2 = sc.parallelize(1 to 100)

// 检查 RDD 分区数
rdd2.getNumPartitions
rdd2.partitions.length

// 创建 RDD,并指定分区数
val rdd2 = sc.parallelize(1 to 100)
rdd2.getNumPartitions

val rdd3 = sc.makeRDD(List(1,2,3,4,5))

val rdd4 = sc.makeRDD(1 to 100)
rdd4.getNumPartitions        // res17: Int = 6

val rdd5 = sc.range(1, 100, 3)
rdd5.getNumPartitions        // res18: Int = 6

val rdd6 = sc.range(1, 100, 2 ,10)
rdd6.getNumPartitions        // res19: Int = 10

val rdd5 = sc.range(1, 100, numSlices = 3)
rdd5.getNumPartitions        // res18: Int = 3

备注:rdd.collect 方法在生产环境中不要使用,会造成Driver OOM

 

3、从文件系统创建RDD

用 textFile() 方法来从文件系统中加载数据创建RDD。方法将文件的 URI 作为参数,这个URI可以是:

  • 本地文件系统  (注意:该文件是不是在所有的节点存在(在Standalone模式下))
  • 分布式文件系统HDFS的地址
  • Amazon S3的地址
// 从本地文件系统加载数据   需要保证 路径文件 真实存在
val lines = sc.textFile("file:///root/data/wc.txt")

// 从分布式文件系统加载数据
val lines = sc.textFile("hdfs://linux121:9000/user/root/data/uaction.dat")
val lines = sc.textFile("/user/root/data/uaction.dat")
val lines = sc.textFile("data/uaction.dat")    // 不写前面的路径, 因为配置文件中已经写好


4、从RDD创建RDD

本质是将一个RDD转换为另一个RDD。详细信息参见 3.5 Transformation

 

3.5 Transformation【重要】

RDD的操作算子分为两类:

  • Transformation。用来对RDD进行转化,这个操作时延迟执行的(或者说是 Lazy 的);
  • Action。用来触发RDD的计算;得到相关计算结果 或者 将结果保存的外部系统中;

说明:

  • Transformation:返回一个新的RDD
  • Action:返回结果 可能是 int、double、集合(不会返回新的RDD)
  • 要很准确区分Transformation、Action

 

每一次 Transformation 操作都会产生新的RDD,供给下一个“转换”使用;
转换得到的RDD是惰性求值的。也就是说,整个转换过程只是记录了转换的轨迹,并不会发生真正的计算,只有遇到Action 操作时,才会发生真正的计算,开始从血缘关系(lineage)源头开始,进行物理的转换操作;


常见的 Transformation 算子:
官方文档:http://spark.apache.org/docs/latest/rdd-programming-guide.html#transformations

 

常见转换算子1

下面全部都是窄依赖, 即处理后分区不变

map(func):对数据集中的每个元素都使用func,然后返回一个新的RDD
filter(func):对数据集中的每个元素都使用func,然后返回一个包含使func为true的元素构成的RDD
flatMap(func):与 map 类似,每个输入元素被映射为0或多个输出元素
mapPartitions(func):和map很像,但是map是将func作用在每个元素上,而mapPartitions是func作用在整个分区上。假设一个RDD有N个元素,M个分区(N >> M),那么map的函数将被调用N次,而mapPartitions中的函数仅被调用M次,一次处理一个分区中的所有元素
mapPartitionsWithIndex(func):与 mapPartitions 类似,多了分区的索引值的信息

val rdd1 = sc.parallelize(1 to 10)
val rdd2 = rdd1.map(_*2)        // map是窄依赖, 分区数不会变
val rdd3 = rdd2.filter(_>10)

// 以上都是 Transformation 操作,没有被执行。如何证明这些操作按预期执行,此时需要引入Action算子
rdd2.collect
rdd3.collect
// collect 是Action算子,触发Job的执行,将RDD的全部元素从 Executor 搜集到 Driver 端。生产环境中禁用

// flatMap 使用案例
val rdd4 = sc.textFile("data/wc.txt")

rdd4.collect   
// res1: Array[String] = Array(hadoop hdfs hadoop, hdfs lagou hadoop, mapreduce mr yarn, yarn mr mapreduce)

rdd4.flatMap(_.split("\\s+")).collect    
// res2: Array[String] = Array(hadoop, hdfs, hadoop, hdfs, lagou, hadoop, mapreduce, mr, yarn, yarn, mr, mapreduce)

// RDD 是分区,rdd1有几个区,每个分区有哪些元素
rdd1.getNumPartitions        // res7: Int = 6
rdd1.partitions.length

rdd1.mapPartitions{iter => Iterator(s"${iter.toList}")}.collect
// res9: Array[String] = Array(List(1), List(2, 3), List(4, 5), List(6), List(7, 8), List(9, 10))


rdd1.mapPartitions{iter =>
Iterator(s"${iter.toArray.mkString("-")}")}.collect
// res10: Array[String] = Array(1, 2-3, 4-5, 6, 7-8, 9-10)

// idx代表分区的索引
rdd1.mapPartitionsWithIndex{(idx, iter) =>
Iterator(s"$idx:${iter.toArray.mkString("-")}")}.collect
// res12: Array[String] = Array(0:1, 1:2-3, 2:4-5, 3:6, 4:7-8, 5:9-10)

// 每个元素 * 2
val rdd5 = rdd1.mapPartitions(iter => iter.map(_*2))        // 分区没变
rdd5.collect
// res13: Array[Int] = Array(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)


map 与 mapPartitions 的区别

  • map:每次处理一条数据
  • mapPartitions:每次处理一个分区的数据,分区的数据处理完成后,数据才能释放,资源不足时容易导致OOM
  • 最佳实践:当内存资源充足时,建议使用mapPartitions,以提高处理效率

 

常见转换算子2

groupBy(func):按照传入函数的返回值进行分组。将key相同的值放入一个迭代器
glom():将每一个分区形成一个数组,形成新的RDD类型 RDD[Array[T]]
sample(withReplacement, fraction, seed):采样算子。以指定的随机种子(seed)随机抽样出数量为fraction的数据,withReplacement表示是抽出的数据是否放回,true为有放回的抽样,false为无放回的抽样
distinct([numTasks])):对RDD元素去重后,返回一个新的RDD。可传入numTasks参数改变RDD分区数
coalesce(numPartitions):缩减分区数,无shuffle
repartition(numPartitions):增加或减少分区数,有shuffle
sortBy(func, [ascending], [numTasks]):使用 func 对数据进行处理,对处理后的结果进行排序

 

宽依赖的算子(有shuffle):groupBy、distinct、repartition、sortBy

// 将 RDD 中的元素按照3的余数分组
val rdd = sc.parallelize(1 to 10)

val group = rdd.groupBy(_%3)
// group: org.apache.spark.rdd.RDD[(Int, Iterable[Int])] = ShuffledRDD[15] at groupBy at <console>:25

group.collect
/ Array[(Int, Iterable[Int])] = Array((0,CompactBuffer(6, 9, 3)), (1,CompactBuffer(1, 4, 10, 7)), (2,CompactBuffer(5, 2, 8)))

// 将 RDD 中的元素每10个元素分组
val rdd = sc.parallelize(1 to 101)
// res17: Array[Int] = Array(1, 2, ... 101)

rdd.glom.map(_.sliding(10, 10).toArray)
// res18: org.apache.spark.rdd.RDD[Array[Array[Int]]] = MapPartitionsRDD[20] at map at <console>:26
// sliding是Scala中的方法

// 对数据采样。fraction采样的百分比,近似数
// 有放回的采样,使用固定的种子
rdd.sample(true, 0.2, 2).collect
// res26: Array[Int] = Array(2, 4, 5, 7, 9, 15, 33, 37, 37, 38, 39, 41, 44, 44, 51, 52, 57, 61, 69, 69, 70, 81, 86)

// 无放回的采样,使用固定的种子
rdd.sample(false, 0.2, 2).collect
// res27: Array[Int] = Array(1, 4, 11, 12, 15, 31, 37, 41, 42, 44, 45, 50, 51, 56, 58, 59, 69, 70, 71, 74, 75, 78, 84, 86, 89, 92)

// 有放回的采样,不设置种子
rdd.sample(false, 0.2).collect
// res28: Array[Int] = Array(1, 2, 5, 11, 16, 18, 25, 26, 28, 29, 33, 39, 41, 52, 62, 67, 73, 74, 80, 89, 96, 98, 100)


// 数据去重  随机产生30个15以内整数
val random = scala.util.Random
val arr = (1 to 20).map(x => random.nextInt(10))
// arr: scala.collection.immutable.IndexedSeq[Int] = Vector(3, 0, 6, 5, 0, 8, 7, 2, 9, 1, 6, 3, 1, 8, 2, 9, 1, 1, 1, 4)
val rdd = sc.makeRDD(arr)
rdd.distinct.collect
// res29: Array[Int] = Array(0, 6, 1, 7, 8, 2, 3, 9, 4, 5)

// RDD重分区
val rdd1 = sc.range(1, 10000, numSlices=10)
val rdd2 = rdd1.filter(_%2==0)
rdd2.getNumPartitions
// res30: Int = 10

// 减少分区数;都生效了
val rdd3 = rdd2.repartition(5)
rdd3.getNumPartitions
// res32: Int = 5

val rdd4 = rdd2.coalesce(5)
rdd4.getNumPartitions
// res33: Int = 5

// 增加分区数
val rdd5 = rdd2.repartition(20)
rdd5.getNumPartitions
// res34: Int = 20

// 增加分区数,这样使用没有效果
val rdd6 = rdd2.coalesce(20)
rdd6.getNumPartitions
// res35: Int = 10

// 增加分区数的正确用法
val rdd6 = rdd2.coalesce(20, true)
rdd6.getNumPartitions
// res36: Int = 20

// RDD元素排序
val random = scala.util.Random
val arr = (1 to 20).map(x => random.nextInt(10))
val rdd = sc.makeRDD(arr)
rdd.collect
// res37: Array[Int] = Array(3, 0, 2, 9, 8, 3, 6, 9, 2, 7, 8, 1, 8, 3, 0, 4, 0, 6, 3, 0)

// 数据全局有序,默认升序
rdd.sortBy(x=>x).collect
// res38: Array[Int] = Array(0, 0, 0, 0, 1, 2, 2, 3, 3, 3, 3, 4, 6, 6, 7, 8, 8, 8, 9, 9)

// 降序
rdd.sortBy(x=>x,false).collect
// res39: Array[Int] = Array(9, 9, 8, 8, 8, 7, 6, 6, 4, 3, 3, 3, 3, 2, 2, 1, 0, 0, 0, 0)

 

coalesce 与 repartition 的区别 (表面上: 有无shuffle)

小结:

  • repartition:增大或减少分区数;有shuffle
  • coalesce:一般用于减少分区数(此时无shuffle)

 

常见转换算子3

RDD之间的交、并、差算子,分别如下:

  • intersection(otherRDD)
  • union(otherRDD)
  • subtract (otherRDD)

cartesian(otherRDD):笛卡尔积
zip(otherRDD):将两个RDD组合成 key-value 形式的RDD,默认两个RDD的partition数量以及元素数量都相同,否则会抛出异常。

 

宽依赖的算子(有shuffle):intersection、subtract

val rdd1 = sc.range(1, 21)
val rdd2 = sc.range(10, 31)
rdd1.intersection(rdd2).sortBy(x=>x).collect
// res40: Array[Long] = Array(10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)

// 元素求并集,不去重
rdd1.union(rdd2).sortBy(x=>x).collect
// res41: Array[Long] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30)
rdd1.subtract(rdd2).sortBy(x=>x).collect
// res42: Array[Long] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9)

// 检查分区数
rdd1.intersection(rdd2).getNumPartitions
// res6: Int = 6
rdd1.union(rdd2).getNumPartitions
// res7: Int = 12
rdd1.subtract(rdd2).getNumPartitions
// res8: Int = 6

// 笛卡尔积
val rdd1 = sc.range(1, 5)        // 不包含5
val rdd2 = sc.range(6, 10)       // 不包含10
rdd1.cartesian(rdd2).collect
// res9: Array[(Long, Long)] = Array((1,6), (1,7), (1,8), (1,9), (2,6), (2,7), (2,8), (2,9), (3,6), (3,7), (3,8), (3,9), (4,6), (4,7), (4,8), (4,9))

// 检查分区数
rdd1.cartesian(rdd2).getNumPartitions
// res10: Int = 36        // ?为什么变成36个分区??

// 拉链操作
rdd1.zip(rdd2).collect
// res11: Array[(Long, Long)] = Array((1,6), (2,7), (3,8), (4,9))
rdd1.zip(rdd2).getNumPartitions
// res12: Int = 6

// zip操作要求:两个RDD的partition数量以及元素数量都相同,否则会抛出异常
val rdd2 = sc.range(6, 20)
rdd1.zip(rdd2).collect
// org.apache.spark.SparkException: Can only zip RDDs with same number of elements in each partition

备注:

  • union是窄依赖。得到的RDD分区数为:两个RDD分区数之和
  • cartesian也是窄依赖。得到的RDD分区数为:两个RDD分区数之积;得到RDD的数据量是两个RDD数据量的积。会有很严重的数据膨胀,慎用
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值