Spark学习记录2

目录

一、Spark编程入门

1. 通过IDEA创建Spark工程

2. Scala实现WordCount

3. 程序打包上传集群

二、算子介绍

2.1 RDD概念

2.2 RDD做了什么

2.3 RDD五大特性

2.4 RDD的弹性

2.5  创建RDD

2.5.1  从集合(内存)中创建RDD

2.5.2  从外部存储(文件)创建RDD

2.5.3  从其他RDD创建

2.5.6  直接创建RDD(new)

2.6 RDD分类

2.7 Transformation算子概述

2.8 Action算子概述


一、Spark编程入门

1. 通过IDEA创建Spark工程

ps:工程创建之前步骤省略,在scala中已经讲解,直接默认是创建好工程的。

导入Pom文件依赖,将如下的内容覆盖pom.xml中的<properties>和<dependencies>标签,注意别把最后一行</project>标签覆盖掉。

    <!-- 声明公有的属性 -->
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <encoding>UTF-8</encoding>
        <scala.version>2.12.8</scala.version>
        <spark.version>3.1.2</spark.version>
        <hadoop.version>3.3.1</hadoop.version>
        <scala.compat.version>2.12</scala.compat.version>
    </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>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-client</artifactId>
            <version>${hadoop.version}</version>
        </dependency>
    </dependencies>

在Pom.xml文件中添加

<!-- 配置构建信息 -->
    <build>
        <!-- 资源文件夹 -->
        <sourceDirectory>src/main/scala</sourceDirectory>
        <!-- 声明并引入构建的插件 -->
        <plugins>
             <!-- 用于编译Scala代码到class -->
            <plugin>
                <groupId>net.alchim31.maven</groupId>
                <artifactId>scala-maven-plugin</artifactId>
                <version>3.2.2</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>testCompile</goal>
                        </goals>
                        <configuration>
                            <args>
                                <arg>-dependencyfile</arg>
                                <arg>${project.build.directory}/.scala_dependencies</arg>
                            </args>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <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>
                            <transformers>
                                <!-- 打成可执行的jar包 的主方法入口-->
                                <transformer  implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass></mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

更新完pom.xml文件后,刷新一下下载依赖,等待依赖下载完成。

2. Scala实现WordCount

在scala package下新建com.spark_example package下编写第一个spark程序 new scala class命名为SparkCore_demo1

package com.spark_example

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

/**
 * spark的第一个程序
 */
object SparkCore_demo1 {
  def main(args: Array[String]): Unit = {
    //1、获取spark的context对象,该对象是上下文--->local[*]  *:使用电脑可用cpu核算
    val sc = new SparkContext("local[*]", "spark-wc")

    //2、使用sc对象初始化数据源
    val rdd:RDD[String] = sc.textFile("C:\\Users\\80621\\Desktop\\words\\book1.txt")
    //3、将第二步中的数据进行加工处理
    val sumRdd = rdd.flatMap(_.split(" "))
      .map(x => (x, 1))
      .reduceByKey(_ + _)

    //4、对第三步中的数据进行存储
    println(sumRdd.collect().toBuffer)
    sumRdd.saveAsTextFile("C:\\Users\\80621\\Desktop\\words\\out")
    //3、4步骤可以写一个行
    //rdd.flatMap(_.split("\t")).map(x => (x, 1)).reduceByKey(_ + _).saveAsTextFile("")

    //5、关闭sc对象
    sc.stop()
  }
}

3. 程序打包上传集群

编写文件放到服务器上去跑,new scala class命名为SparkCore_demo2

package com.spark_example

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

object SparkCore_demo2 {
  def main(args: Array[String]): Unit = {
    //1、获取spark的context对象,该对象是上下文
    val sc = new SparkContext(new SparkConf().setAppName("spark-wc"))

    //2、使用sc对象初始化数据源
    val rdd:RDD[String] = sc.textFile(args(0))
    //3、将第二步中的数据进行加工处理
    val sumRdd = rdd.flatMap(_.split(" "))
      .map(x => (x, 1))
      .reduceByKey(_ + _)

    //4、对第三步中的数据进行存储
    println(sumRdd.collect().toBuffer)
    sumRdd.saveAsTextFile(args(1))

    //5、关闭sc对象
    sc.stop()
  }
}

首先clean

然后打包

 打包好的文件在工程目录下的target目录下  C:\Users\80621\IdeaProjects\QD-Bigdata\target

将original-QD-Bigdata-1.0-SNAPSHOT.jar通过mobaxtermchs上传至softwares中

mv /root/softwares/original-QD-Bigdata-1.0-SNAPSHOT.jar /home/original-QD-Bigdata-1.0.jar 

进行提交作业操作

spark-submit \
--class com.spark_example.SparkCore_demo2 \
--master yarn \
--deploy-mode client \
/home/original-QD-Bigdata-1.0.jar /input1 /output1/701

查看结果

hdfs dfs -cat /output1/701/*

二、算子介绍

2.1 RDD概念

  • RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变、可分区、里面的元素可并行计算的集合。RDD具有数据流模型的特点:自动容错、位置感知性调度和可伸缩性。RDD允许用户在执行多个查询时显式地将工作集缓存在内存中,后续的查询能够重用工作集,这极大地提升了查询速度。
  • 在之前学习MR的过程中对数据是没有进行抽象的,而在Spark中对数据进行了抽象,提供一些列处理方法也就是说RDD(弹性分布式数据集),Spark计算的基石,为用户屏蔽了底层对数据的复杂抽象和处理,为用户提供了一组方便的数据转换与求值方法。
  • 现在开发的过程中都是面向对象的思想,那么我们创建类的时候会对类封装一些属性和方法,那么创建出来的对象就具备着这些属性和方法,类也属于对数据的抽象,而Spark中的RDD就是对操作数据的一个抽象
  • 查看原码可以得知,而且在类中提供了很多方法可以使用
  • 弹性

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

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

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

        d.分片的弹性:可根据需要重新分片。

  • 分布式:数据存储在大数据集群不同节点上
  • 数据集:RDD封装了计算逻辑,并不保存数据
  • 数据抽象:RDD是一个抽象类,需要子类具体实现
  • 不可变:RDD封装了计算逻辑,是不可以改变的,想要改变,只能产生新的RDD,在新的RDD里面封装计算逻辑
  • 可分区、并行计算

总结:

在 Spark 中,对数据的所有操作不外乎创建 RDD、转化已有RDD 以及调用 RDD 操作进行求值。每个 RDD 都被分为多个分区,这些分区运行在集群中的不同节点上。RDD 可以包含 Python、Java、Scala 中任意类型的对象, 甚至可以包含用户自定义的对象。RDD具有数据流模型的特点:自动容错、位置感知性调度和可伸缩性。RDD允许用户在执行多个查询时显式地将工作集缓存在内存中,后续的查询能够重用工作集,这极大地提升了查询速度。

2.2 RDD做了什么

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

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

RDD是Spark框架中用于数据处理的核心模型,例如在SparkShell,执行如下命令:

sc.textFile(“xx").flatMap(_.split("")).map((_,1)).reduceByKey(_+_).saveAsTextFile(“xx")

总结:

从以上流程可以看出RDD在整个流程中主要用于将逻辑进行封装,RDD的创建->RDD的转换(转换过程中为了减少数据计算有添加缓存)->RDD的行动(输出数据)

2.3 RDD五大特性

RDD源码中提供了说明

1)一组分片(Partition),即数据集的基本组成单位。对于RDD来说,每个分片都会被一个计算任务处理,并决定并行计算的粒度。用户可以在创建RDD时指定RDD的分片个数,如果没有指定,那么就会采用默认值。默认值就是程序所分配到的CPU Core的数目。
#RDD数据结构中存在分区列表,用于执行任务时并行计算,是实现分布式计算的重要属性。

2)一个计算每个分区的函数。Spark中RDD的计算是以分片为单位的,每个RDD都会实现compute函数以达到这个目的。compute函数会对迭代器进行复合,不需要保存每次计算的结果。
#Spark在计算时,是使用分区函数对每一个分区进行计算

3)RDD之间的依赖关系。RDD的每次转换都会生成一个新的RDD,所以RDD之间就会形成类似于流水线一样的前后依赖关系。在部分分区数据丢失时,Spark可以通过这个依赖关系重新计算丢失的分区数据,而不是对RDD的所有分区进行重新计算。
#RDD是计算模型的封装,当需求中需要将多个计算模型进行组合时,就需要将多个RDD建立依赖关系


4)一个Partitioner,即RDD的分片函数。当前Spark中实现了两种类型的分片函数,一个是基于哈希的HashPartitioner,另外一个是基于范围的RangePartitioner。只有对于key-value的RDD,才会有Partitioner,非key-value的RDD的Parititioner的值是None。Partitioner函数不但决定了RDD本身的分片数量,也决定了parent RDD Shuffle输出时的分片数量。
#当数据为KV类型数据时,可以通过设定分区器自定义数据的分区

5)一个列表,存储存取每个Partition的优先位置(preferred location)。对于一个HDFS文件来说,这个列表保存的就是每个Partition所在的块的位置。按照“移动数据不如移动计算”的理念,Spark在进行任务调度的时候,会尽可能地将计算任务分配到其所要处理数据块的存储位置。
#计算数据时,可以根据计算节点的状态选择不同的节点位置进行计算

 注意 : RDD本身是不存储数据,可以看做RDD本身是一个引用数据

2.4 RDD的弹性

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

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

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

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

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

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


总结:
存储的弹性:内存与磁盘的
自动切换容错的弹性:数据丢失可以
自动恢复计算的弹性:计算出错重试机制
分片的弹性:根据需要重新分片

2.5  创建RDD

在Spark中创建RDD的创建方式可以分为四种:

2.5.1  从集合(内存)中创建RDD

从集合中创建RDD,Spark主要提供了两个方法:parallelize和makeRDD

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

/**
 * spark的创建
 */
object Demo03_RddCreate {
  def main(args: Array[String]): Unit = {
    //1、获取spark的context对象,该对象是上下文--->local[*]  *:使用电脑可用cpu核算
    val sc = new SparkContext("local[*]", "spark-wc")

    //2.从集合(内存)中创建RDD --》 这种创建方式多用于测试使用
    //makeRDD和parallelize是可以指定分区数量的,有第二个参数,默认值是2,也可以指定这个值
    val rdd1: RDD[Int] = sc.makeRDD(Array(1, 2, 3, 4, 5, 6))
    val rdd2: RDD[Int] = sc.parallelize(Array(1, 2, 3, 4, 5, 6),3)
    println(rdd1.collect().toBuffer)
    println(rdd2.collect().toBuffer)

    //3、关闭sc对象
    sc.stop()
  }
}

2.5.2  从外部存储(文件)创建RDD

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

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

object CreateSparkRDDDemo {
  def main(args: Array[String]): Unit = {
    //先创建SparkConf和SparkContext对象
    val conf = new SparkConf().setAppName("CreateSparkRDDDemo").setMaster("local")
    val sc = new SparkContext(conf)

    //2.从外部存(文件)创建RDD
    val rdd3: RDD[String] = sc.textFile("hdfs://user1:9820/input")

  }
}

2.5.3  从其他RDD创建

主要是通过一个RDD运算完后,再产生新的RDD

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

object CreateSparkRDDDemo {
  def main(args: Array[String]): Unit = {
    //先创建SparkConf和SparkContext对象
    val conf = new SparkConf().setAppName("CreateSparkRDDDemo").setMaster("local")
    val sc = new SparkContext(conf)
    val rdd3: RDD[String] = sc.textFile("hdfs://user1:9820/word.txt")
    //3.从其他的RDD创建
    val rdd4: RDD[String] = rdd3.flatMap(_.split(" "))
  }
}

2.5.6  直接创建RDD(new)

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

2.6 RDD分类

RDD的具体实现类有几十种(大概60+),介绍下最常见的几种:

源数据RDD:

spark支持读取不同的数据源,如下例子:

  • 支持hdfs文件读取, HadoopRDD
  • 支持jdbc读取数据库,JdbcRDD

MapPartitionsRDD

MapPartitionsRDD对于父RDD的依赖类型只能是OneToOneDependency,代表将函数应用到每一个分区的计算。

相关transformation:map, flatMap, filter, mapPartitions 等

ShuffledRDD

  • 对于父RDD的依赖类型只能是ShuffleDependency,代表需要改变分区方式进行shuffle的计算。
  • 会创建ShuffledRDD的transformation:reduceByKey, sortByKey 等
  • 默认情况下,Spark可以将一个作业切分多个任务后,发送给Executor节点并行计算,而能够并行计算的任务数量我们称之为并行度。这个数量可以在构建RDD时指定。记住,这里的并行执行的任务数量,并不是指的切分任务的数量,不要混淆了。

2.7 Transformation算子概述

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

RDD支持两种操作:转换操作(Transformation)和行动操作(Action),RDD的转换操作是返回一个新的RDD的操作,比如map和 flatMap,而行动操作则是向Driver返回结果或将结果写出到外部存在设备,比如,collect和saveAsTextFile。

列举部分算子:

转换

含义

map(func)

返回一个新的RDD,该RDD由每一个输入元素经过func函数转换后组成

filter(func)

返回一个新的RDD,该RDD由经过func函数计算后返回值为true的输入元素组成

flatMap(func)

类似于map,但是每一个输入元素可以被映射为0或多个输出元素(所以func应该返回一个序列,而不是单一元素)

mapPartitions(func)

类似于map,但独立地在RDD的每一个分片上运行,因此在类型为T的RDD上运行时,func的函数类型必须是Iterator[T] => Iterator[U]

mapPartitionsWithIndex(func)

类似于mapPartitions,但func带有一个整数参数表示分片的索引值,因此在类型为T的RDD上运行时,func的函数类型必须是(Int, Iterator[T]) => Iterator[U]

sample(withReplacement, fraction, seed)

根据fraction指定的比例对数据进行采样,可以选择是否使用随机数进行替换,seed用于指定随机数生成器种子

union(otherDataset)

对源RDD和参数RDD求并集后返回一个新的RDD

intersection(otherDataset)

对源RDD和参数RDD求交集后返回一个新的RDD

distinct([numTasks]))

对源RDD进行去重后返回一个新的RDD

groupByKey([numTasks])

在一个(K,V)的RDD上调用,返回一个(K, Iterator[V])的RDD

reduceByKey(func, [numTasks])

在一个(K,V)的RDD上调用,返回一个(K,V)的RDD,使用指定的reduce函数,将相同key的值聚合到一起,与groupByKey类似,reduce任务的个数可以通过第二个可选的参数来设置

aggregateByKey(zeroValue)(seqOp, combOp, [numTasks])

相同的Key值进行聚合操作,在聚合过程中同样使用了一个中立的初始值zeroValue:中立值,定义返回value的类型,并参与运算seqOp:用来在同一个partition中合并值combOp:用来在不同partiton中合并值

sortByKey([ascending], [numTasks])

在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD

sortBy(func,[ascending], [numTasks])

与sortByKey类似,但是更灵活

join(otherDataset, [numTasks])

在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD

cogroup(otherDataset, [numTasks])

在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable,Iterable))类型的RDD

cartesian(otherDataset)

笛卡尔积

pipe(command, [envVars])

将一些shell命令用于Spark中生成新的RDD

coalesce(numPartitions)

重新分区

repartition(numPartitions)

重新分区

repartitionAndSortWithinPartitions(partitioner)

重新分区和排序

2.8 Action算子概述

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

动作

含义

reduce(func)

通过func函数聚集RDD中的所有元素,这个功能必须是可交换且可并联的

collect()

在驱动程序中,以数组的形式返回数据集的所有元素

count()

返回RDD的元素个数

first()

返回RDD的第一个元素(类似于take(1))

take(n)

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

takeSample(withReplacement,num, [seed])

返回一个数组,该数组由从数据集中随机采样的num个元素组成,可以选择是否用随机数替换不足的部分,seed用于指定随机数生成器种子

takeOrdered(n, [ordering])

takeOrdered和top类似,只不过以和top相反的顺序返回元素

saveAsTextFile(path)

将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本

saveAsSequenceFile(path)

将数据集中的元素以Hadoop sequencefile的格式保存到指定的目录下,可以使HDFS或者其他Hadoop支持的文件系统。

saveAsObjectFile(path)

countByKey()

针对(K,V)类型的RDD,返回一个(K,Int)的map,表示每一个key对应的元素个数。

foreach(func)

在数据集的每一个元素上,运行函数func进行更新。

算子实战练习

C:\Users\80621\Desktop\words\stu.txt内容如下

12 张三 25 男 chinese 50

12 张三 25 男 math 60

12 张三 25 男 english 70

12 李四 20 男 chinese 50

12 李四 20 男 math 50

12 李四 20 男 english 50

12 王芳 19 女 chinese 70

12 王芳 19 女 math 70

12 王芳 19 女 english 70

13 张大三 25 男 chinese 60

13 张大三 25 男 math 60

13 张大三 25 男 english 70

13 李大四 20 男 chinese 50

13 李大四 20 男 math 60

13 李大四 20 男 english 50

13 王小芳 19 女 chinese 70

13 王小芳 19 女 math 80

13 王小芳 19 女 english 70

需求如下:

1. 一共有多少人参加考试?
    一共有多少个女生参加考试?
2. 12班有多少人参加考试?
    13班有多少人参加考试?
3. 语文科目的平均成绩是多少?
    数学科目的平均成绩是多少?
    英语科目的平均成绩是多少?
4. 单个人平均成绩是多少?
5. 12班平均成绩是多少?
    12班男生平均总成绩是多少?
    12班女生平均总成绩是多少?
     同理求13班相关成绩
6. 全校语文成绩最高分是多少?
   12班语文成绩最低分是多少?
   13班数学最高成绩是多少?

问题1代码如下: 

package com.spark_example

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

case class Stu(classID:String,name:String,age:Int,sex:String,subject:String,score:Double)
object StuCount {
  def main(args: Array[String]): Unit = {
    //获取spark的context对象,该对象是上下文--->local[*]  *:使用电脑可用cpu核算
    val sc = new SparkContext("local[*]", "spark-wc")
    val rdd:RDD[String] = sc.textFile("C:\\Users\\80621\\Desktop\\words\\stu.txt")
    val stuRdd = rdd
      .filter(_.length > 0) //过滤掉空行
      .map(line =>{
        val fields = line.split(" ")
        Stu(
          fields(0).trim,
          fields(1).trim,
          fields(2).trim.toInt,
          fields(3).trim,
          fields(4).trim,
          fields(5).trim.toDouble
        )
      })
    //1、一共有多少人参加考试
    val totalStu = stuRdd.map(x => x.name).distinct().count()
    println("一共有:"+totalStu+" 人参加考试")
    //2、一共有多少女生参加考试
    val totalGirlStu = stuRdd.filter(x=>x.sex.equals("女")).map(x => x.name).distinct().count()
    println("一共有:"+totalGirlStu+" 个女生参加考试")

    //关闭sc对象
    sc.stop()
  }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值