大数据(二)Spark学习笔记—sparkcore

目录

Spark概述

核心模块

Spark编程配置

IDEA配置scala环境

WordCount案例

Spark-Standalone运行环境

Local配置步骤

集群分工

解压文件 

修改配置

启动集群

 配置历史服务器

Spark-Yarn运行环境

配置步骤

配置历史服务器

 Windows运行环境

配置步骤

 常用端口号

Spark架构

核心组件

Driver

 Executor

Master & Worker

ApplicationMaster Hadoop

核心概念

Executor 与 Core

并行度(Parallelism)

提交流程

 Yarn Client 模式

Yarn Cluster 模式

  分布式计算模拟代码

基础

发送计算任务

分布式任务模拟

Spark Core核心编程

RDD概念

RDD与IO流的关系

 核心属性

执行原理

基础编程

创建

1)从集合(内存)中创建

2)从文件中创建(就是读取了)

其他创建

并行度与分区

分区设定from集合

 分区规则from集合

 分区设定from文件

 分区分配from文件 **?**

算子

转换算子

Value 

map

 map并行

mapPartitions

 mapPartitionsWithIndex

flatMap 

glom

groupBy

 filter

sample

distinct

 coalesce

repartition

sortBy

双Value

intersection、union、subtract、zip

Key-Value

partitionBy

reduceByKey

groupByKey

aggregateByKey什么没用功能?

combineByKey

foldByKey

join

 左外连接、右外连接

cogroup

行动算子

reduce

collect

count

first

take

takeOrdered

 aggregate

fold

countByKey、countByValue

foreach 

序列化

Kryo

RDD依赖关系

血缘关系&依赖

 窄依赖、宽依赖

 RDD 任务划分

持久化

RDD Cache 缓存

 检查点

RDD分区器

文件读取

累加器

概述

没行动算子小问题

 自定义

广播变量


Spark and Hadoop

  • Spark 就是在传统的 MapReduce 计算框 架的基础上,利用其计算过程的优化,从而大大加快了数据分析、挖掘的运行和读写速 度,并将计算单元缩小到更适合并行计算和重复使用的 RDD 计算模型。
  • Spark 和Hadoop 的根本差异是多个作业之间的数据通信问题 : Spark 多个作业之间数据 通信是基于内存,而 Hadoop 是基于磁盘。
  • Spark 只有在 shuffle 的时候将数据写入磁盘,而 Hadoop 中多个 MR 作业之间的数据交 互都要依赖于磁盘交互

但是 Spark 是基于内存的,所以在实际的生产环境中,由于内存的限制,可能会 由于内存资源不够导致 Job 执行失败,此时,MapReduce 其实是一个更好的选择,所以 Spark 并不能完全替代 MR。

  1. Spark与Hadoop在许多方面都存在依赖关系。Spark本身并不会进行分布式数据的存储,而是依赖于Hadoop的分布式文件系统(HDFS)来存储数据。因此,在安装Spark之前,需要先安装Hadoop,并确保Hadoop的HDFS系统正常运行。
  2. Spark与Hadoop的集成可以充分利用两者之间的优势。Hadoop的HDFS为大数据提供了高可靠性的存储,而Spark提供了快速的数据处理能力。通过将两者集成,可以实现数据的快速读写和计算,提高大数据处理效率。
  3. Spark与Hadoop在生态圈上也有着紧密的联系。Spark作为Hadoop生态系统中的一部分,可以很好地融入其中,并成为其中的重要一员。通过与Hadoop的集成,Spark可以借助于Hadoop的集群管理系统YARN实现资源调度管理,更好地协调和管理集群中的资源。

Spark概述

核心模块


Spark编程配置

IDEA配置scala环境

IDEA软件中Scala配置安装教程(Spark计算环境搭建)_jing_zhong的博客-CSDN博客

较全的idea2020.3配置Scala_MI_farmer的博客-CSDN博客_idea配置scala sdk

记得编写Spark之前导入Spark所需库

WordCount案例

 hello,(hello,hello,hello,hello)实现

def main(args: Array[String]): Unit = {
    // Application
    // Spark框架
    // TODO 建立连接
    val conf = new SparkConf().setMaster("local").setAppName("word count")
    val sc = new SparkContext(conf)
    // TODO 执行操作
      //read txt
    val lines:RDD[String] = sc.textFile("D:/txt/goodnight.txt")
    val words:RDD[String] = lines.flatMap(_.split(" "))
    val group:RDD[(String,Iterable[String])] = words.groupBy(word => word)
    val count = group.map{
      case (word, list)=>{
        (word, list.size)
      }
    }.collect().foreach(println)

    // TODO 关闭连接
    sc.stop()
  }

  hello,(1,1,1,1)实现

def main(args: Array[String]): Unit = {
    // Application
    // Spark框架
    // TODO 建立连接
    val conf = new SparkConf().setMaster("local").setAppName("word count")
    val sc = new SparkContext(conf)
    // TODO 执行操作
    val lines:RDD[String] = sc.textFile("D:/txt/goodnight.txt")
    val words:RDD[String] = lines.flatMap(_.split(" "))
    words.map(word=>(word,1)).reduceByKey(_+_).collect().foreach(println(_))
    // TODO 关闭连接
    sc.stop()
  }

Spark-Standalone运行环境

Local配置步骤

将spark-3.0.0-bin-hadoop3.2.tar解压至自定文件夹

tar -zxvf spark-3.0.0-bin-hadoop3.2.tgz

运行bin/spark-shell ( ctrl+C/:quit退出 )

[root@hadoop1 spark-3.0.0-bin-hadoop3.2]# bin/spark-shell
23/01/06 18:09:15 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
Using Spark's default log4j profile: org/apache/spark/log4j-defaults.properties
Setting default log level to "WARN".
To adjust logging level use sc.setLogLevel(newLevel). For SparkR, use setLogLevel(newLevel).
Spark context Web UI available at http://hadoop1:4040
Spark context available as 'sc' (master = local[*], app id = local-1672999762111).
Spark session available as 'spark'.
Welcome to
      ____              __
     / __/__  ___ _____/ /__
    _\ \/ _ \/ _ `/ __/  '_/
   /___/ .__/\_,_/_/ /_/\_\   version 3.0.0
      /_/
         
Using Scala version 2.12.10 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_291)
Type in expressions to have them evaluated.
Type :help for more information.

scala> val i = 10
i: Int = 10

scala> sc.textFile("data/word.txt").flatMap(_.split(" ")).map((_,1)).reduceByKey
reduceByKey   reduceByKeyLocally

scala> sc.textFile("data/word.txt").flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).collect
res0: Array[(String, Int)] = Array((Hello,4), (Java,1), (World,1), (Scala,2))   

 Spark context Web UI available at http://hadoop1:4040

可以使用hostname:4040查看Web UI

集群分工

hadoop1hadoop2hadoop3
Masterworkerworker

解压文件 

将 spark-3.0.0-bin-hadoop3.2.tgz 文件上传到 Linux 并解压缩在指定位置

tar -zxvf spark-3.0.0-bin-hadoop3.2.tgz

mv spark-3.0.0-bin-hadoop3.2 spark-standalone

修改配置

1) 进入解压缩后路径的 conf 目录,修改 slaves.template 文件名为 slaves

2) 用记事本修改 slaves 文件,添加 work 节点

添加所有的worker结点主机名

 hadoop1

hadoop2

hadoop3

3) 修改 spark-env.sh.template 文件名为 spark-env.sh

4) 修改 spark-env.sh 文件,添加 JAVA_HOME 环境变量和指定集群对应的 master 节点

 export JAVA_HOME=/opt/module/jdk1.8.0_144(你的jdk路径)

SPARK_MASTER_HOST=hadoop1

SPARK_MASTER_PORT=7077

注意:7077 端口为默认,相当于 hadoop3 内部通信的 8020 端口,此处的端口需要确认自己的 Hadoop 配置

5) 分发spark-standalone目录到其他worker主机

scp -r /export/server/spark-standalone root@hadoop2:/export/server/spark-standalone

scp -r /export/server/spark-standalone root@hadoop3:/export/server/spark-standalone

启动集群

sbin/start-all.sh

查看 Master 资源监控 Web UI 界面: http://hadoop1:8080

 配置历史服务器

1) 修改conf/spark-defaults.conf.template 文件名为 spark-defaults.conf

2) 修改 spark-default.conf 文件,配置日志存储路径

spark.eventLog.enabled true

spark.eventLog.dir hdfs://hadoop1:8020/directory

 hdfs中的目录需存在

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"

 4) 分发conf到其他worker对应目录

scp -r /export/server/spark-standalone root@hadoop2:/export/server/spark-standalone

scp -r /export/server/spark-standalone root@hadoop3:/export/server/spark-standalone

5) 先启动HDFS,后spark集群和历史服务器

HADOOP_HOME/sbin/start-all.sh

SPARK_HOME/sbin/start-all.sh

SPARK_HOME/sbin/start-history-server.sh

6) 重新执行任务

bin/spark-submit \

--class org.apache.spark.examples.SparkPi \

--master spark://linux1:7077 \

./examples/jars/spark-examples_2.12-3.0.0.jar \

10

7) 查看历史服务:http://linux1:18080

Spark-Yarn运行环境

独立部署(Standalone)模式由 Spark 自身提供计算资源,无需其他框架提供资源。这种方式降低了和其他第三方资源框架的耦合性,独立性非常强。但是也要记住,Spark 主要是计算框架,而不是资源调度框架,所以本身提供的资源调度并不是它的强项,所以还是和其他专业的资源调度框架集成会更靠谱一些,在国内工作中,Yarn 使用的非常多。

配置步骤

将spark-3.0.0-bin-hadoop3.2.tar解压至自定文件夹

tar -zxvf spark-3.0.0-bin-hadoop3.2.tgz

mv spark-3.0.0-bin-hadoop3.2 spark-yarn

1) 修改 hadoop 配置文件HADOOP_HOME/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) 修改 SPARK_HOME/conf/spark-env.sh,添加 JAVA_HOME 和 YARN_CONF_DIR 配置

mv spark-env.sh.template spark-env.sh (改文件名)

添加如下内容:(每个人不同!!!)

export JAVA_HOME=/export/server/jdk1.8.0_291 YARN_CONF_DIR=/export/server/hadoop3.3.3/etc/hadoop

3) 启动HDFS和YARN集群

HADOOP_HOME/sbin/start-all.sh

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

配置历史服务器

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://linux1:8020/directory

注意:需要启动 hadoop 集群,HDFS 上的目录需要提前存在。

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=linux1: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 UI  hostname:8088

 可以跳转到hostname:18080

 Windows运行环境

配置步骤

1) 将文件 spark-3.0.0-bin-hadoop3.2.tgz 解压缩到无中文无空格的路径中

2) 执行解压缩文件路径下 bin 目录中的 spark-shell.cmd 文件,启动 Spark 本地环境,可以配置环境变量后直接在cmd内执行

环境变量:

SPARK_HOME=D:\myjava\spark\spark-3.0.0-bin-hadoop3.2\bin

 常用端口号

➢Spark 查看当前 Spark-shell 运行任务情况端口号:4040(计算)

➢ Spark Master 内部通信服务端口号:7077

➢ Standalone 模式下,Spark Master Web 端口号:8080(资源)

➢ Spark 历史服务器端口号:18080

➢ Hadoop YARN 任务运行情况查看端口号:8088

Spark架构

Spark 框架的核心是一个计算引擎,整体来说,它采用了标准 master-slave 的结构。

如下图所示,它展示了一个 Spark 执行时的基本结构。图形中的 Driver 表示 master, 负责管理整个集群中的作业任务调度。图形中的 Executor 则是 slave,负责实际执行任务。

核心组件

Driver

Spark 驱动器节点,用于执行 Spark 任务中的 main 方法,负责实际代码的执行工作。

Driver 在 Spark 作业执行时主要负责:

        ➢ 将用户程序转化为作业(job)

        ➢ 在 Executor 之间调度任务(task)

        ➢ 跟踪 Executor 的执行情况

        ➢ 通过 UI 展示查询运行情况

实际上,我们无法准确地描述 Driver 的定义,因为在整个的编程过程中没有看到任何有关 Driver 的字眼。所以简单理解,所谓的 Driver 就是驱使整个应用运行起来的程序,也称之为 Driver 类。

 Executor

Spark Executor 是集群中工作节点(Worker)中的一个 JVM 进程,负责在 Spark 作业 中运行具体任务(Task),任务彼此之间相互独立。

Spark 应用启动时,Executor 节点被同时启动,并且始终伴随着整个 Spark 应用的生命周期而存在。

如果有 Executor 节点发生了 故障或崩溃,Spark 应用也可以继续执行,会将出错节点上的任务调度到其他 Executor 节点 上继续运行。

Executor 有两个核心功能:

        ➢ 负责运行组成 Spark 应用的任务,并将结果返回给驱动器进程

        ➢ 它们通过自身的块管理器(Block Manager)为用户程序中要求缓存的 RDD 提供内存 式存储。RDD 是直接缓存在 Executor 进程内的,因此任务可以在运行时充分利用缓存         数据加速运算。

Master & Worker

Spark 集群的独立部署环境中,不需要依赖其他的资源调度框架,自身就实现了资源调 度的功能,所以环境中还有其他两个核心组件:Master 和 Worker,这里的 Master 是一个进 程,主要负责资源的调度和分配,并进行集群的监控等职责,类似于 Yarn 环境中的 RM, 而 Worker 呢,也是进程,一个 Worker 运行在集群中的一台服务器上,由 Master 分配资源对 数据进行并行的处理和计算,类似于 Yarn 环境中 NM。

ApplicationMaster Hadoop

用户向 YARN 集群提交应用程序时,提交程序中应该包含 ApplicationMaster,用 于向资源调度器申请执行任务的资源容器 Container,运行用户自己的程序任务 job,监控整 个任务的执行,跟踪整个任务的状态,处理任务失败等异常情况。 说的简单点就是,ResourceManager(资源)和 Driver(计算)之间的解耦合靠的就是 ApplicationMaster。

核心概念

Executor 与 Core

Spark Executor 是集群中运行在工作节点(Worker)中的一个 JVM 进程,是整个集群中 的专门用于计算的节点。在提交应用中,可以提供参数指定计算节点的个数,以及对应的资 源。这里的资源一般指的是工作节点 Executor 的内存大小和使用的虚拟 CPU 核(Core)数量。

并行度(Parallelism)

在分布式计算框架中一般都是多个任务同时执行,由于任务分布在不同的计算节点进行 计算,所以能够真正地实现多任务并行执行,记住,这里是并行,而不是并发。这里我们将整个集群并行执行任务的数量称之为并行度。那么一个作业到底并行度是多少呢?这个取决 于框架的默认配置。应用程序也可以在运行过程中动态修改。

 并发与并行的区别_一缕阳光a的博客-CSDN博客_并发和并行的区别

提交流程

所谓的提交流程,其实就是我们开发人员根据需求写的应用程序通过 Spark 客户端提交 给 Spark 运行环境执行计算的流程。在不同的部署环境中,这个提交过程基本相同,但是又有细微的区别,国内工作中,将 Spark 引用部署到 Yarn 环境中会更多一些,所以本提交流程是基于 Yarn 环境的。

Spark 应用程序提交到 Yarn 环境中执行的时候,一般会有两种部署执行的方式:Client 和 Cluster。两种模式主要区别在于:Driver 程序的运行节点位置。 

 Yarn Client 模式

Client 模式将用于监控和调度的 Driver 模块在客户端执行,而不是在 Yarn 中,所以一 般用于测试。

➢ Driver 在任务提交的本地机器上运行

➢ Driver 启动后会和 ResourceManager 通讯申请启动 ApplicationMaster

➢ ResourceManager 分配 container,在合适的 NodeManager 上启动 ApplicationMaster,负责向 ResourceManager 申请 Executor 内存

➢ ResourceManager 接到 ApplicationMaster 的资源申请后会分配 container,然后 ApplicationMaster 在资源分配指定的 NodeManager 上启动 Executor 进程

➢ Executor 进程启动后会向 Driver 反向注册,Executor 全部注册完成后 Driver 开始执行 main 函数

➢ 之后执行到 Action 算子时,触发一个 Job,并根据宽依赖开始划分 stage,每个 stage 生 成对应的 TaskSet,之后将 task 分发到各个 Executor 上执行。

Yarn Cluster 模式

Cluster 模式将用于监控和调度的 Driver 模块启动在 Yarn 集群资源中执行。一般应用于 实际生产环境。

➢ 在 YARN Cluster 模式下,任务提交后会和 ResourceManager 通讯申请启动 ApplicationMaster,

➢ 随后 ResourceManager 分配 container,在合适的 NodeManager 上启动 ApplicationMaster, 此时的 ApplicationMaster 就是 Driver。

➢ Driver 启动后向 ResourceManager 申请 Executor 内存,ResourceManager 接到 ApplicationMaster 的资源申请后分配 container,然后在合适 NodeManager 上启动 Executor 进程

➢ Executor 进程启动后会向 Driver 反向注册,Executor 全部注册完成后 Driver 开始执行 main 函数,

➢ 之后执行到 Action 算子时,触发一个 Job,并根据宽依赖开始划分 stage,每个 stage 生 成对应的 TaskSet,之后将 task 分发到各个 Executor 上执行。

  分布式计算模拟代码

基础

import java.net.Socket

object Driver {
    def main(args: Array[String]): Unit = {
        val clint = new Socket("localhost", 9999)
        println("clint连接9999")
        val out = clint.getOutputStream
        out.write(2)
        out.flush()
        out.close()
        clint.close()
    }
}

// =============================================

import java.net.ServerSocket

object Executor {
    def main(args: Array[String]): Unit = {
        val server = new ServerSocket(9999)
        println("Server启动,等待客户端数据")
        val clint = server.accept()
        val stream = clint.getInputStream
        val data = stream.read()
        println("Clint数据: "+ data)
        stream.close()
        clint.close()
        server.close()
    }
}

发送计算任务

import java.io.ObjectInputStream
import java.net.ServerSocket
object Executor {
    def main(args: Array[String]): Unit = {
        val server = new ServerSocket(9999)
        println("Server启动,等待客户端数据")
        val clint = server.accept()

        val stream = clint.getInputStream
        val objstream = new ObjectInputStream(stream)
        val task:Task = objstream.readObject().asInstanceOf[Task]

        val data:List[Int] = task.compute()

        println("Clint数据: "+ data)
        stream.close()
        clint.close()
        server.close()
    }
}

import java.io.ObjectOutputStream
import java.net.Socket
object Driver {
    def main(args: Array[String]): Unit = {
        val clint = new Socket("localhost", 9999)
        println("clint连接9999")


        val out = clint.getOutputStream
        val objout = new ObjectOutputStream(out)
        val task = new Task()
        objout.writeObject(task)
        objout.flush()
        objout.close()
        clint.close()
        println("客户端数据发送完")
    }
}

class Task extends Serializable {
    val datas: List[Int] = List(1,2,3,4,5)
    val logic: Int => Int = (x:Int)=>{x*2}

    def compute()={
        datas.map(logic)
    }
}

分布式任务模拟

spark分布式计算模拟代码-spark文档类资源-CSDN下载

Spark Core核心编程

Spark 计算框架为了能够进行高并发和高吞吐的数据处理,封装了三大数据结构,用于 处理不同的应用场景。

三大数据结构分别是:

➢ RDD : 弹性分布式数据集

➢ 累加器:分布式共享只写变量

➢ 广播变量:分布式共享只读变量

RDD概念

RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是 Spark 中最基本的数据处理模型。

代码中是一个抽象类,它代表一个弹性的、不可变、可分区、里面的元素可并行计算的集合。

➢ 弹性

        ⚫ 存储的弹性:内存与磁盘的自动切换;

        ⚫ 容错的弹性:数据丢失可以自动恢复;

        ⚫ 计算的弹性:计算出错重试机制;

        ⚫ 分片的弹性:可根据需要重新分片。充分利用Executor

➢ 分布式:数据存储在大数据集群不同节点上

➢ 数据集:RDD 封装了计算逻辑,并不保存数据

➢ 数据抽象:RDD 是一个抽象类,需要子类具体实现

➢ 不可变:RDD 封装了计算逻辑,是不可以改变的,想要改变,只能产生新的 RDD,在 新的 RDD 里面封装计算逻辑

➢ 可分区、并行计算

RDD与IO流的关系

RDD只有执行了collect才会真正的执行业务逻辑

 核心属性

@ 分区列表

        RDD 数据结构中存在分区列表,用于执行任务时并行计算,是实现分布式计算的重要属性

@ 分区计算函数

        Spark 在计算时,是使用分区函数对每一个分区进行计算

@ RDD 之间的依赖关系

        RDD 是计算模型的封装,当需求中需要将多个计算模型进行组合时,就需要将多个 RDD 建 立依赖关系

@ 分区器(可选)

        当数据为 KV 类型数据时,可以通过设定分区器自定义数据的分区

@ 首选位置(可选)

        计算数据时,可以根据计算节点的状态选择不同的节点位置进行计算

执行原理

        从计算的角度来讲,数据处理过程中需要计算资源(内存 & CPU)和计算模型(逻辑)。 执行时,需要将计算资源和计算模型进行协调和整合。

        Spark 框架在执行时,先申请资源,然后将应用程序的数据处理逻辑分解成一个一个的 计算任务。然后将任务发到已经分配资源的计算节点上, 按照指定的计算模型进行数据计 算。最后得到计算结果。

RDD 是 Spark 框架中用于数据处理的核心模型,接下来我们看看,在 Yarn 环境中,RDD 的工作原理:

 从以上流程可以看出 RDD 在整个流程中主要用于将逻辑进行封装,并生成 Task 发送给 Executor 节点执行计算

基础编程

创建

1)从集合(内存)中创建

    def main(args: Array[String]): Unit = {
        // TODO 准备环境
        val sparkConf = new SparkConf().setMaster("local[*]").setAppName("memory creat RDD")
        val sc = new SparkContext(sparkConf)
        // TODO 创建
        val seq = Seq[Int](1,2,3,4)
//        val rdd = sc.parallelize(seq)
        // makeRDD在底层就是调用了parallelize
        val rdd = sc.makeRDD(seq)
        rdd.collect().foreach(println)
        // TODO 关闭环境
        sc.stop()
    }

2)从文件中创建(就是读取了)

由外部存储系统的数据集创建 RDD 包括:本地的文件系统,所有 Hadoop 支持的数据集, 比如 HDFS、HBase 等

        // TODO 准备环境
        val sparkConf = new SparkConf().setMaster("local[*]").setAppName("memory creat RDD")
        val sc = new SparkContext(sparkConf)
        // TODO 创建
        // path 可以是文件也可以是目录,可以使用通配符1*.txt
        // wholeTextFile以文件为单位读取
        // val rdd2 = sc.wholeTextFiles("datas")  结果元组(路径,内容)
        //textFile以行为单位读取
        val rdd:RDD[String] = sc.textFile("datas/goodnight.txt")
        rdd.collect().foreach(println(_))
        // TODO 关闭环境
        sc.stop()

其他创建

3) 从其他 RDD 创建是通过一个 RDD 运算完后,再产生新的 RDD。

4) 直接创建 RDD(new) 使用 new 的方式直接构造 RDD,一般由 Spark 框架自身使用。

并行度与分区

分区设定from集合

    def main(args: Array[String]): Unit = {
        // TODO 准备环境
        val sparkConf = new SparkConf().setMaster("local[*]").setAppName("memory creat RDD")
        val sc = new SparkContext(sparkConf)
        // TODO 创建
        // makeRDD第二个参数有分区,默认会分成自己的电脑核数
        val rdd:RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)
        rdd.saveAsTextFile("./spark-core/src/main/java/spark/core/c1_RDD/c2_Partition/output/Par1")
        // TODO 关闭环境
        sc.stop()

运行结果

 分区规则from集合

硬刚源码

 分区设定from文件

 分区分配from文件 **?**

        // TODO 创建
        /*
        * 14byte/2=7byte
        * 14/7 = 2 part
        * 偏移量
        * 1234567@@ => 012345678
        * 89@@ => 9 10 11 12
        * 0 => 13
        *[0,7] => 不足一行,一行读完
        *[7,14]=> 890
        * */
        val rdd1 = sc.textFile("./datas/1.txt")
        rdd1.saveAsTextFile("./spark-core/src/main/java/spark/core/c1_RDD/c2_Partition/output/File_Par3")

 如果数据源多个文件,以文件为单位进行分区,一个一个文件处理,不会两文件分到一个区

算子

转换算子

RDD根据数据处理方式的不同将算子整体上分为Value类型、双Value类型和Key-Value
类型


Value 

map

将处理的数据逐条进行映射转换,这里的转换可以是类型的转换,也可以是值的转换。

        // TODO 创建
        val rdd = sc.makeRDD(List(1,2,3,4))
        rdd.map((num:Int)=> {
            num * 2
        }).collect().foreach(println)

 map并行

 rdd分区内执行时有序的,不同分区内执行时无序的

        val rdd  =sc.makeRDD(List(1,2,3))
        val mapRDD = rdd.map(num=>{
            println("iii-"+num)
            num
        })
        val mapRDD2 = mapRDD.map(num=>{
            println("iiiiii-"+num)
        }).collect()
        /*
            iii-3
            iii-1
            iiiiii-1
            iii-2
            iiiiii-2
            iiiiii-3无序
            */
        val rdd  =sc.makeRDD(List(1,2,3),1)
        val mapRDD = rdd.map(num=>{
            println("iii-"+num)
            num
        })
        val mapRDD2 = mapRDD.map(num=>{
            println("iiiiii-"+num)
        }).collect()
        /*
            iii-1
            iiiiii-1
            iii-2
            iiiiii-2
            iii-3
            iiiiii-3
            */

mapPartitions

@ 分区为单位进行数据操作,会将整个分区的数据都加载到内存中进行引用,容易出现内存的溢出

@ Map 算子是分区内一个数据一个数据的执行,类似于串行操作。而 mapPartitions 算子 是以分区为单位进行批处理操作。

        val rdd = sc.makeRDD(List(1,2,3,4),6)
        rdd.mapPartitions(
            iter=>{
                println(">>>>>>>")  //每个分区走一次,打印6次
                iter.map(_*2)
            }
        ).collect().foreach(println)
        val rdd = sc.makeRDD(List(1,2,3,4),2)
        rdd.mapPartitions(
            iter=>{
                List(iter.max).iterator  // 每个分区的最大值,包装成迭代器返回
            }                            // 2  4
        ).collect().foreach(println)

 mapPartitionsWithIndex

        val rdd = sc.makeRDD(List(1,2,3,4),2)
        rdd.mapPartitionsWithIndex(
            (index, iter)=>{    //就是多了个判断分区下标的数值,0123
                if(index == 1)
                    iter
                else
                    Nil.iterator
            }).collect().foreach(println)
        val rdd = sc.makeRDD(List(1,2,3,4))
        rdd.mapPartitionsWithIndex(
            (index, iter)=>{
                iter.map(num=>{
                    num+" in Part "+index
                })
            }).collect().foreach(println)
        /*
            1 in Part 3
            2 in Part 7
            3 in Part 11
            4 in Part 15*/

flatMap 

将处理的数据进行扁平化后再进行映射处理,所以算子也称之为扁平映射

Me:拆碎

        val rdd = sc.makeRDD(List(List(1,2), 3, List(3,4)))
        rdd.flatMap(data=>{
            data match {
                case list:List[Int]=>list
                case i:Int=>List(i)
            }
        }).collect().foreach(println)

glom

        val rdd:RDD[Int] = sc.makeRDD(List(1,2,3,4),2)
        val glomRDD: RDD[Array[Int]] = rdd.glom()
        // RDD中有两个数组,数组传给data,组成字符串输出---1,2   3,4
        glomRDD.collect().foreach(data=>println(data.mkString(",")))

将同一个分区的数据直接转换为相同类型的内存数组进行处理,分区不变

groupBy

将数据根据指定的规则进行分组, 分区默认不变,但是数据会被打乱重新组合,我们将这样 的操作称之为 shuffle--还起个名字,花里胡哨

极限情况下,数据可能被分在同一个分区中 一个组的数据在一个分区中,但是并不是说一个分区中只有一个组(一个区中有多个组) 

        val rdd:RDD[Int] = sc.makeRDD(List(1,2,3,4),3)
        // groppBy会将数据中每一个数据进行分组判断,根据返回的分组key进行分组
        // key相同的分到一个组里
        rdd.groupBy(_%2).collect().foreach(println)
        /*(0,CompactBuffer(2, 4))
          (1,CompactBuffer(1, 3))*/
        
        // 按首字符分类
        val rdd2 = sc.textFile("./datas/goodnight.txt").flatMap(_.split(" "))
        rdd2.groupBy(_.charAt(0)).collect().foreach(println)
        val rdd = sc.textFile("./datas/apache.log")
        rdd.map(line=>{
            val data = line.split(" ")
            val time = data(3)
            val sdf = new SimpleDateFormat("dd/MM/yyyy:HH:mm:ss")
            val date = sdf.parse(time)
            val sdf_hour = new SimpleDateFormat("HH")
            val hour = sdf_hour.format(date)
            (hour,1)
        }).groupBy(_._1).map{
            case(hour, iter)=>{
                hour+" have "+iter.size
            }
        }.collect().foreach(println)

 filter

传入一个Boolean值,来筛选true的数据,分区不改变,但是分区内的数据不均衡,可能会出现数据倾斜

        val rdd = sc.makeRDD(List(1,2,3,4,5,6,7,8,9))
        rdd.filter(_%2==0).collect().foreach(println)
        rdd.filter(line=>{
            val data = line.split(" ")
            data(3).startsWith("17/05")
        }).collect().foreach(println)

sample

就一不明不白的函数,随机抽取

distinct

去重

val rdd = sc.makeRDD(List(1,2,3,4,1,2,3,3,9))
rdd.distinct().collect().foreach(print)

 coalesce

根据数据量缩减分区,用于大数据集过滤后,提高小数据集的执行效率 当 spark 程序中,存在过多的小任务的时候,可以通过 coalesce 方法,收缩合并分区,减少分区的个数,减小任务调度成本@&$(@&@(*#(#@$&(#

@ 不是把数据打乱组合,是合并分区

@ 可能导致数据不均衡,让第二个shuffle参数打开

        val rdd = sc.makeRDD(List(1,2,3,4,1,2,3,3,9),4)
        // 4 --> 2
        rdd.coalesce(2).saveAsTextFile("./spark-core/src/main/java/spark/core/c1_RDD/c3_transformRDD/output/"+
          this.getClass.getSimpleName.slice(0,this.getClass.getSimpleName.length-1))

repartition

套娃函数,扩大分区 

sortBy

按照内部内容排序,默认升序,可以降序,会shuffle

        val rdd = sc.makeRDD(List(7,6,8,9,5,2,1,3,4))
        rdd.sortBy(x=>x,ascending = false).collect().foreach(print)

双Value

intersection、union、subtract、zip

        // TODO
        val rdd1 = sc.makeRDD(List(1,2,3,4))
        val rdd2 = sc.makeRDD(List(3,4,5,6))

        // 交
        rdd1.intersection(rdd2).collect().mkString(",").foreach(print)
        println()
        // 并
        rdd1.union(rdd2).collect().mkString(",").foreach(print)
        println()
        // 差--注意先后有别
        rdd1.subtract(rdd2).collect().mkString(",").foreach(print)
        println()
        rdd2.subtract(rdd1).collect().mkString(",").foreach(print)
        println()
        // 拉链
        rdd1.zip(rdd2).collect().mkString(",").foreach(print)

@ 类型不一样——编译报错

@ 拉链个数(分区)不一样——报错


Key-Value

partitionBy

将数据按照指定 Partitioner 重新进行分区。Spark 默认的分区器是 HashPartitioner

底层有个判断,看新分区器是否和目前的一样,再分区无变化

        // TODO
        val rdd1 = sc.makeRDD(List(1,2,3,4,6))
        val maprdd1 = rdd1.map(x=>(x,1))
        // 隐式转换 二次编译
        // 分区规则底层是模运算,数值对指定分区取余数
        maprdd1.partitionBy(new HashPartitioner(2)).saveAsTextFile("./spark-core/src/main/java/spark/core/c1_RDD/c4_transformRDD_Dual/output/"
                                                                +this.getClass.getSimpleName.slice(0,this.getClass.getSimpleName.length-1))

reduceByKey

也就是比GroupByKey多了个聚合,主要性能还在shuffle

        // TODO
        val rdd1 = sc.makeRDD(List(
            ("a", 1),("a",4),("a",6),("b",3),("b",9),("c",9)
        ))
        // 语句中不涉及Key
        rdd1.reduceByKey((x,y)=>{
            println(s"x=${x},y=${y}")  // 只有一个不会执行加操作
            x+y
        }).collect().foreach(println)

        /*  x=3,y=9
            x=1,y=4
            x=5,y=6
            (a,11)
            (b,12)
            (c,9)*/

groupByKey

这样别的分区中数据没有过来,可能在内存中堆溢出

        // TODO
        val rdd1 = sc.makeRDD(List(
            ("a", 1),("a",4),("a",6),("b",3),("b",9),("c",9)
        ))
        // 一样一样的
        rdd1.groupBy(_._1).collect().foreach(println)
        rdd1.groupByKey().collect().foreach(println)

aggregateByKey什么没用功能?

将数据根据不同的规则进行分区内计算分区间计算

        // TODO
        val rdd1 = sc.makeRDD(List(
            ("a", 1),("a",4),("a",6),("b",3),("b",9),("c",9)
        ),2)
        // 柯里化 有多个参数
        // 第一个——初始值用于分区内计算
        // 第二个——1内规则2间规则
        rdd1.aggregateByKey(8)(
            (x,y)=>math.min(x,y),
            (x,y)=>(x+y)
        ).collect().foreach(println)
        /*  (b,3)
            (a,1)
            (c,8)*/

初始值存在的意义

combineByKey

aggregateByKey的改进,直接把第一个处理一下作为初始值,往后传递的原理和aggregateByKey一样

        // 3个参数
        // --将相同Key的第一个参数进行结构的处理
        // --分区内计算规则
        // --分区间计算规则
        val rdd2 = rdd1.combineByKey(
            v =>{
                (v,1)
            },
            (t:(Int,Int),v)=>{
                // 这里理论上可以识别类型,但是不加要报错,要加上上一个初始元组的类型
                (t._1+v,t._2+1)  // t是初始值,v是传入的List中的第二个value
            },                   // 初始值以元组的形式带着需要的值与个数一路传递下去
            (a:(Int,Int),b:(Int,Int))=>{
                (a._1+b._1,a._2+b._2)  // 分区之间("a",(value,cnt)) ("a",(value,cnt))
            }                          //                   a                 b
        )

foldByKey

如果上一个函数中分区间和分区内计算规则相同,可以简化为这个函数

        // 柯里化 有多个参数
        rdd1.foldByKey(0)(math.max(_,_))
          .collect().foreach(println)

join

 左外连接、右外连接

        // TODO
        val rdd1 = sc.makeRDD(List(
            ("a", 1),("b",3),("c",5)
        ))
        val rdd2 = sc.makeRDD(List(
            ("a", 11),("d",33),("c",55),("a",111)
        ))
        // 左外连接:左边主表全保留,匹配右边对应值
        rdd1.leftOuterJoin(rdd2).collect().foreach(println)
        println("==========")
        // 右外连接:右边主表全保留,匹配左边对应值
        rdd1.rightOuterJoin(rdd2).collect().foreach(println)

/*        (a,(1,Some(11)))
        (a,(1,Some(111)))
        (b,(3,None))
        (c,(5,Some(55)))
        ==========
        (a,(Some(1),11))
        (a,(Some(1),111))
        (c,(Some(5),55))
        (d,(None,33))*/

cogroup

        // TODO
        val rdd1 = sc.makeRDD(List(
            ("a", 1),("b",3),("c",5)
        ))
        val rdd2 = sc.makeRDD(List(
            ("a", 11),("d",33),("c",55),("a",111)
        ))
        // connect+group ,可以连接三个不同的
        rdd1.cogroup(rdd2,rdd2,rdd1).collect().foreach(println)
        /*(a,(CompactBuffer(1),CompactBuffer(11, 111),CompactBuffer(11, 111),CompactBuffer(1)))
        (b,(CompactBuffer(3),CompactBuffer(),CompactBuffer(),CompactBuffer(3)))
        (c,(CompactBuffer(5),CompactBuffer(55),CompactBuffer(55),CompactBuffer(5)))
        (d,(CompactBuffer(),CompactBuffer(33),CompactBuffer(33),CompactBuffer()))*/

行动算子

底层创建了ActiveJob,提交执行

reduce

collect

会将不同分区的数据按照分区顺序采集到driver端内存中形成Array数组

count

返回 RDD 中元素的个数

first

返回 RDD 中的第一个元素

take

返回一个由 RDD 的前 n 个元素组成的数组

takeOrdered

 返回该 RDD 排序后的前 n 个元素组成的数组

rdd.takeOrdered(3)(Ordering.Int.reverse).foreach(println)

// 432

 aggregate

分区的数据通过初始值和分区内的数据进行聚合,然后再和初始值进行分区间的数据聚合

        //这个初始值会参与内和间的计算
        println(rdd.aggregate(10)(_ + _, _ + _)) //40

fold

鸡你太美

countByKey、countByValue

统计每种 key 的个数,Value,返回结果是Map(x -> x)

foreach 

分布式遍历 RDD 中的每一个元素,调用指定函数

直接对RDD进行foreach,是在Executor中打印,并行进行,所以不知道是谁先打印出来,在数组中进行的话,打印的就是数组的方法?所以会有序 

 rdd算子中传递的函数会包含闭包操作,就会进行检测

闭包是什么?五分钟带你了解闭包_寒烟说的博客-CSDN博客

        从计算的角度,算子以外的代码都是在Driver端执行,算子里面的代码都是在Executor
端执行。那么在scala的函数式编程中,就会导致算子内经常会用到算子外的数据,这样就
形成了闭包的效果

        如果使用的算子外的数据无法序列化,就意味着无法传值给Executor端执行,就会发生错误,所以需要在执行任务计算前,检测闭包内的对象是否可以进行序列化,这个操作我们称之为闭包检测。

序列化

object O_1serial {
    def main(args: Array[String]): Unit = {
        val conf = new SparkConf().setAppName("serial").setMaster("local[*]")
        val sc = new SparkContext(conf)

        // TODO
        val rdd: RDD[String] = sc.makeRDD(Array("hello world", "hello spark",
            "hive", "atguigu"))
        val search = new Search("hello")
        //3.2 函数传递,打印:ERROR Task not serializable
            // 构造参数需要进行闭包检测
            // 也就是类需要进行检测
        search.getMatch1(rdd).collect().foreach(println)
        //3.3 属性传递,打印:ERROR Task not serializable
        search.getMatch2(rdd).collect().foreach(println)

        sc.stop()
    }

    // 类的构造参数其实是类的私有属性,
    // 构造参数需要进行闭包检测
    // 也就是类需要进行检测
    class Search(query: String) extends Serializable {
        def isMatch(s: String): Boolean = {
            s.contains(this.query)
        }

        // 函数序列化案例
        def getMatch1(rdd: RDD[String]): RDD[String] = {
            //rdd.filter(this.isMatch)
            rdd.filter(isMatch)

        }
        // 属性序列化案例
        def getMatch2(rdd: RDD[String]): RDD[String] = {
            //rdd.filter(x => x.contains(this.query))
            rdd.filter(x => x.contains(query))

            // 这样用不到类的属性,可以不用序列化
            //val q = query
            //rdd.filter(x => x.contains(q))
        }
    }
}

Kryo

Java 的序列化能够序列化任何的类。但是比较重(字节多),序列化后,对象的提交也 比较大。Spark 出于性能的考虑,Spark2.0 开始支持另外一种 Kryo 序列化机制。Kryo 速度 是 Serializable 的 10 倍。当 RDD 在 Shuffle 数据的时候,简单数据类型、数组和字符串类型 已经在 Spark 内部使用 Kryo 来序列化。

RDD依赖关系

血缘关系&依赖

 toDebugString方法输出关系

 窄依赖、宽依赖

窄依赖表示每一个父(上游)RDD 的 Partition 最多被子(下游)RDD 的一个 Partition 使用, 窄依赖我们形象的比喻为独生子女。

 宽依赖表示同一个父(上游)RDD 的 Partition 被多个子(下游)RDD 的 Partition 依赖,会 引起 Shuffle,总结:宽依赖我们形象的比喻为多生。

 RDD 任务划分

RDD 任务切分中间分为:Application、Job、Stage 和 Task

⚫ Application:初始化一个 SparkContext 即生成一个 Application;

⚫ Job:一个 Action 算子就会生成一个 Job;

⚫ Stage:Stage 等于宽依赖(ShuffleDependency)的个数加 1;

⚫ Task:一个 Stage 阶段中,最后一个 RDD 的分区个数就是 Task 的个数。

持久化

RDD Cache 缓存

RDD 通过 Cache 或者 Persist 方法将前面的计算结果缓存,默认情况下会把数据以缓存 在 JVM 的堆内存中。但是并不是这两个方法被调用时立即缓存,而是触发后面的 action 算 子时,该 RDD 将会被缓存在计算节点的内存中,并供后面重用。

理论上map不会第二次执行

 

错了不用从头/保存重要数据​​​​

 Spark 会自动对一些 Shuffle 操作的中间数据做持久化操作(比如:reduceByKey)。这样 做的目的是为了当一个节点 Shuffle 失败了避免重新计算整个输入。但是,在实际使用的时 候,如果想重用数据,仍然建议调用 persist 或 cache

 检查点

所谓的检查点其实就是通过将 RDD 中间结果写入磁盘

由于血缘依赖过长会造成容错成本过高,这样就不如在中间阶段做检查点容错,如果检查点 之后有节点出现问题,可以从检查点开始重做血缘,减少了开销。

对 RDD 进行 checkpoint 操作并不会马上被执行,必须执行 Action 操作才能触发

       sc.setCheckpointDir("./checkpoint")
         // TODO
        val list = List("Hello Scala", "Hello Spark")
        val rdd = sc.makeRDD(list)

        val flatRDD = rdd.flatMap(_.split(" "))

        // 看mapRDD第二次执行,这个map还执行不了?√
        val mapRDD = flatRDD.map(word=>{
            println("@@@@@@@@@@@@")
            (word,1)
        })
        println("cache!!!")
        mapRDD.checkpoint()

        // need to disk
        val reduceRDD: RDD[(String, Int)] = mapRDD.reduceByKey(_+_)
        reduceRDD.collect().foreach(println)

        println("**************************************")
        println("ererereer")
        val groupRDD = mapRDD.groupByKey()
        groupRDD.collect().foreach(println)
        sc.stop()

                /*flatmap
        cache!!!
          reduce
        =============  4个单词执行了8次,因为checkpoint又造了个任务独立执行
        =============
        =============
        =============
        =============
        =============
        =============
        =============
        (Hello,2)
        (Scala,1)
        (Spark,1)
        **************************************
        ererereer
        (Hello,CompactBuffer(1, 1))
        (Scala,CompactBuffer(1))
        (Spark,CompactBuffer(1))
        (Hello,CompactBuffer(1, 1))
        (Scala,CompactBuffer(1))
        (Spark,CompactBuffer(1))*/

@ cache:将数据临时存储在内存中进行数据重用

                会在血缘关系中添加新的依赖。一但出现问题,可以读取数据
@ persist:将数据临时存储在磁盘文件中进行数据重用
涉及到磁盘IO,性能铰低,但是数据安全
如果作业执行完毕,临时保存的数据文件就会丢失
@ checkpoint:将数据长久地保存在磁盘文件中进行数据重用
        涉及到磁盘IO,性能校低,但是数据安全
        为了保证数据安全,所以一般情况下,会独立执行作业

        为了提高效率,一般要配合cache一起使用

        执行过程中,会切断血缘关系。重新建立新的血缘关系

RDD分区器

 名字花里胡哨,就是硬匹配哪个key就是哪个分区

def main(args: Array[String]): Unit = {
        val conf = new SparkConf().setAppName("Part1").setMaster("local[*]")
        val sc = new SparkContext(conf)

        // TODO
        val rdd = sc.makeRDD(List(
            ("nba", "xxxxxxxxx"),
            ("cba", "xxxxxxxxx"),
            ("wnba", "xxxxxxxxx"),
            ("nba", "xxxxxxxxx"),
        ),3)

        rdd.partitionBy(new MyPartitioner)
            .saveAsTextFile("./spark-core/src/main/java/spark/core/c1_RDD/c9_part/output/"
                            +this.getClass.getSimpleName.slice(0,this.getClass.getSimpleName.length-1))
        sc.stop()

    }


    /**
     * 自定义分区器
     * 1. 继承Partitioner
     * 2. 重写方法
     */
    class MyPartitioner extends Partitioner{
        // 分区数量
        override def numPartitions: Int = 3

        // 根据数据的key值返回数据所在的分区索引(从0开始)
        override def getPartition(key: Any): Int = {
            key match {
                case "nba" => 0
                case "wnba" => 1
                case _ => 2
            }
        }
    }

文件读取

def main(args: Array[String]): Unit = {
        val sparConf = new SparkConf().setMaster("local").setAppName("WordCount")
        val sc = new SparkContext(sparConf)

        val rdd = sc.textFile("./spark-core/src/main/java/spark/core/c1_RDD/c10_io/output-Text/*")
        println(rdd.collect().mkString(","))

        val rdd1 = sc.objectFile[(String, Int)]("./spark-core/src/main/java/spark/core/c1_RDD/c10_io/output-Object/*")
        println(rdd1.collect().mkString(","))

        val rdd2 = sc.sequenceFile[String, Int]("./spark-core/src/main/java/spark/core/c1_RDD/c10_io/output-Seq/*")
        println(rdd2.collect().mkString(","))

        sc.stop()
    }





def main(args: Array[String]): Unit = {
        val sparConf = new SparkConf().setMaster("local").setAppName("WordCount")
        val sc = new SparkContext(sparConf)

        val rdd = sc.makeRDD(
            List(
                ("a", 1),
                ("b", 2),
                ("c", 3)
            )
        )

        rdd.saveAsTextFile("./spark-core/src/main/java/spark/core/c1_RDD/c10_io/output-Text/")
        rdd.saveAsObjectFile("./spark-core/src/main/java/spark/core/c1_RDD/c10_io/output-Object/")
        rdd.saveAsSequenceFile("./spark-core/src/main/java/spark/core/c1_RDD/c10_io/output-Seq/")

        sc.stop()
    }

累加器

概述

加了白加

 要用系统累加器

 

累加器用来把 Executor 端变量信息聚合到 Driver 端。在 Driver 程序中定义的变量,在 Executor 端的每个 Task 都会得到这个变量的一份新的副本,每个 task 更新这些副本的值后, 传回 Driver 端进行 merge。

没行动算子小问题

所以一般放在行动算子中

 自定义

def main(args: Array[String]): Unit = {
        val conf = new SparkConf().setMaster("local[*]").setAppName("Acc1")
        val sc = new SparkContext(conf)

        val rdd = sc.makeRDD(List("hello", "spark", "hello"))

        // 累加器 : WordCount
        // 创建累加器对象
        val wcAcc = new MyAccumulator()
        // 向Spark进行注册
        sc.register(wcAcc, "wordCountAcc")

        rdd.foreach(
            word => {
                // 数据的累加(使用累加器)
                wcAcc.add(word)
            }
        )

        // 获取累加器累加的结果
        println(wcAcc.value)
        




        sc.stop()
    }


    /*
      自定义数据累加器:WordCount

      1. 继承AccumulatorV2, 定义泛型
         IN : 累加器输入的数据类型 String
         OUT : 累加器返回的数据类型 mutable.Map[String, Long]

      2. 重写方法(6)
     */
    class MyAccumulator extends AccumulatorV2[String, mutable.Map[String, Long]] {

        private var wcMap = mutable.Map[String, Long]()

        // 判断是否初始状态
        override def isZero: Boolean = {
            wcMap.isEmpty
        }

        override def copy(): AccumulatorV2[String, mutable.Map[String, Long]] = {
            new MyAccumulator()
        }

        override def reset(): Unit = {
            wcMap.clear()
        }

        // 获取累加器需要计算的值
        override def add(word: String): Unit = {
            val newCnt = wcMap.getOrElse(word, 0L) + 1
            wcMap.update(word, newCnt)
        }

        // Driver合并多个累加器
        override def merge(other: AccumulatorV2[String, mutable.Map[String, Long]]): Unit = {

            val map1 = this.wcMap
            val map2 = other.value

            map2.foreach{
                case ( word, count ) => {
                    val newCount = map1.getOrElse(word, 0L) + count
                    map1.update(word, newCount)
                }
            }
        }

        // 累加器结果
        override def value: mutable.Map[String, Long] = {
            wcMap
        }
    }

广播变量

广播变量用来高效分发较大的对象。向所有工作节点发送一个较大的只读值,以供一个 或多个 Spark 操作使用。比如,如果你的应用需要向所有节点发送一个较大的只读查询表, 广播变量用起来都很顺手。在多个并行操作中使用同一个变量,但是 Spark 会为每个任务 分别发送。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值