Spark核心数据结构RDD(结合Scala使用实战)

512 阅读2分钟

目前可供选择的三种思路

image-20210130172134585

Spark core内存离线分析(把数据加载到内存)比Hive基于磁盘的离线分析快了100倍左右。

放在了这个文件夹

image-20210131190346031

Hive是基于脚本执行的。

Spark core和Spark streaming是基于Ide编译工具执行的,在idea里写好代码,打包上传

image-20210131190536649

使用spark-submit执行spark任务。

针对于当前用户配置环境变量[hadoop@hadoop01 spark-3.0]$ vi ~/.bashrc

image-20210131190735151

基于内存的计算引擎 Spark

spark不负责存储数据。同理,虽然Hive教数仓,但它也不负责数据存储,他们都是数据处理分析引擎。

数据都存储在hdfs里。

一.概述

Spark是用于大规模数据处理的统一分析引擎

生态圈:

image-20210131191820002

关键词

  • 统一:Spark能够一站式的解决不同的计算场景(离线计算【Spark SQL、Spark Core】、实时计算【Spark Streaming】、图计算【GraphX】、机器学习【MLlib】等)

  • 数据处理分析引擎:Spark只涉及到数据的分析计算,不涉及数据的存储

    ​ Spark计算数据来于Hbase、HDFS、Kafka、Flume、关系型数据 库等

特点

  • 速度快 Spark充分的利用内存优势,并且能够重复的利用计算资源(DAG),能够大大的提高数据 处理的速度。(内存够走内存,内存不够走磁盘)
  • 易用 支持多种开发语言(Java,Scala,Python,R,and SQL)

二. 启动hdfs、Spark并测试

启动服务:

  • hdfs:start-dfs.sh

  • spark: $SPARK_HOME/sbin/start-all.sh start-all相当于执行了两个任务:start-master 和start-slave

  • 查看进程服务

    [hadoop@hadoop01 spark-3.0]$ jps
    2576 Master  #属于spark
    2355 SecondaryNameNode
    2727 Jps
    2138 DataNode
    2011 NameNode
    2654 Worker #属于spark
    

Spark Web UI测试

hdfs端口是9870

正常配的话,master节点负责管控,worker节点负责运算

目前我们只有一个节点,master和worker都在一个节点上。

  1. Master主节 点的Web监 测:8080

    image-20210131192904083

    关于节点的核心和内存的配置

    [hadoop@hadoop01 spark-3.0]$ vi conf/spark-env.sh 
    
    image-20210131193213942

    当前的配置是2核4G(是整体集群的核数是2个和内存)

  2. Worker从节 点的Web监 测:8081

    可以直接从上面的Workers点进去进入worker节点

    image-20210131193504970

接下来就是编程实现。

有两种思路:一种是使用spark-shell命令进入spark客户端直接在命令行进行编程

还有一种是在idea里编程然后打包上传。

三.Spark核心数据结构RDD

3.1 RDD概述

  • RDD是弹性分布式数据集(是Spark数据运算、数据分析的基本单元,后期我们操作的就是一个个数据集)

什么意思?

假如说我们有一个比较大的数据

image-20210131194721409

20000多条弹幕数据

数据集的意思就是,你加载一个数据,这个数据可能是在本地、也可能在hdfs里面、也可能在内存里面。但不管怎么说,我们通过spark的特定API把它加载过来,它就是这么一个数据的集合,这个集合就叫做RDD。

R代表弹性:容错性非常好。执行清洗操作,假设清洗到第5次出错了,也不需要从第一次开始执行,因为第四次已经有结果了,直接从第四次的结果开始执行就行了。

3.2 RDD如何创建

基于spark-shell和编程都尝试一下

3.2.1基于内存进行创建

image-20210131195452491

输入 spark-shell后自动产生了两个实例对象。

一个叫做sc(spark context)、还有一个spark【如果写程序的就不会自动生成,需要我们自己创建】

例1

scala> val numRDD = sc.parallelize(1 to 9)
numRDD: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[0] at parallelize at <console>:24

就自动给我们创建了一个数据集RDD。(每个数据占一行,一共9行,每行元素都是Int类型)。

image-20210131200635380

例2

scala> val colorRDD = sc.parallelize(List("red","blue","pink"))
colorRDD: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[1] at parallelize at <console>:24

3.2.2基于文件进行创建【常用】

上传一下数据源

image-20210131201350983 image-20210131203007305

有12435行数据。

[hadoop@hadoop01 data]$ head -3 customers.csv 
"1","Richard","Hernandez","XXXXXXXXX","XXXXXXXXX","6303 Heather Plaza","Brownsville","TX","78521"
"2","Mary","Barrett","XXXXXXXXX","XXXXXXXXX","9526 Noble Embers Ridge","Littleton","CO","80126"
"3","Ann","Smith","XXXXXXXXX","XXXXXXXXX","3422 Blue Pioneer Bend","Caguas","PR","00725"

#编号、名、姓。。。
(1)加载本地文件创建RDD

【本地文件地址】/opt/data/customers.csv

基于内存是sc.parallelize()

基于本地文件是 sc.textFile()

scala> val customerRDD = sc.textFile("file:///opt/data/customers.csv")
customerRDD: org.apache.spark.rdd.RDD[String] = file:///opt/data/customers.csv MapPartitionsRDD[3] at textFile at <console>:24

这样就加载到RDD里面了,一条数据是一行

image-20210131203821465

加载到RDD里面就能分析了

(2)加载HDFS文件创建RDD

前期准备:将customers.csv 上传到hdfs的 /input/customer目录下

[hadoop@hadoop01 data]$ hdfs dfs -mkdir -p /input/customer
[hadoop@hadoop01 data]$ hdfs dfs -put customers.csv /input/customer
2021-01-31 20:43:20,708 INFO sasl.SaslDataTransferClient: SASL encryption trust check: localHostTrusted = false, remoteHostTrusted = false

image-20210131204433153

加载hdfs文件生成一个RDD数据集

本地文件协议 file:

hdfs协议:hdfs:

scala> val customHdfsRDD = sc.textFile("hdfs:///input/customer/customers.csv")
customHdfsRDD: org.apache.spark.rdd.RDD[String] = hdfs:///input/customer/customers.csv MapPartitionsRDD[5] at textFile at <console>:24

scala> customHdfsRDD.take(2) //得到前两条数据
res2: Array[String] = Array("1","Richard","Hernandez","XXXXXXXXX","XXXXXXXXX","6303 Heather Plaza","Brownsville","TX","78521", "2","Mary","Barrett","XXXXXXXXX","XXXXXXXXX","9526 Noble Embers Ridge","Littleton","CO","80126")

3.3 RDD如何转换

经过3.2我们现在已经有了RDD了

接下来要经过算子操作对RDD进行转换

RDD里面的元素都是基于行级别进行存储的。map算子就是对行执行相同的函数操作,对每个元素进行处理,并用处理完的结果覆盖之前的值。

3.3.1map算子

  • map算子:对RDD中的每个元素都执行相同的函数进行处理,会生成1个新的RDD,原始的数据不能改,需要一个新的变量接收

就是利用map 给一个规则,对原来的值进行映射

scala> val rdd1 = sc.parallelize(1 to 5)
rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[6] at parallelize at <console>:24

scala> rdd1.collect
res3: Array[Int] = Array(1, 2, 3, 4, 5)

scala> val rdd2 = rdd1.map(color => (color,1))//具体如何映射根据map里面的表达式决定
rdd2: org.apache.spark.rdd.RDD[(Int, Int)] = MapPartitionsRDD[7] at map at <console>:25

scala> rdd2.collect
res4: Array[(Int, Int)] = Array((1,1), (2,1), (3,1), (4,1), (5,1))

3.3.2 filter算子

  • // filter算子对RDD中的每个元素进行过滤:
  • //过滤的原则: 对每个元素应用指定函数,返回值为 true的元素,保留在新的RDD中

例1

scala>  val rdd1 = sc.parallelize(1 to 10)
rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[8] at parallelize at <console>:24

scala> val rdd2 = rdd1.filter(data => data%2 ==0)
rdd2: org.apache.spark.rdd.RDD[Int] = MapPartitionsRDD[9] at filter at <console>:25

scala>  rdd2.collect
res5: Array[Int] = Array(2, 4, 6, 8, 10)

例2

scala> val rdd = sc.parallelize(List("1001,张三,80","1002,李四,100","1003,王
五,59"))
rdd: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[12] at parallelize
at <console>:24

scala> rdd.collect
res8: Array[String] = Array(1001,张三,80, 1002,李四,100, 1003,王五,59)

scala> rdd.count
res9: Long = 3

scala> val rdd2 = rdd.map(stu => stu.split(","))
rdd2: org.apache.spark.rdd.RDD[Array[String]] = MapPartitionsRDD[13] at map at
<console>:25

scala> rdd2.collect
res10: Array[Array[String]] = Array(Array(1001, 张三, 80), Array(1002, 李四,
100), Array(1003, 王五, 59))
#注意 import spark.implicits._
scala> val rdd3 =rdd2.filter(arr => arr(2).toInt>60) #得到分数>60的学生信息

3.3.3 reduceByKey算子

规约操作

  • 先按照key进行分组操作,然后对于各组执行运算
  • 实现词频统计
scala> val wordsRDD = sc.parallelize(List("hadoop","spark","storm","hadoop","hadoop","spark"))//声明数据源
wordsRDD: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[0] at parallelize at <console>:24

scala> wordsRDD.count
res0: Long = 6                                                                  

scala> val wordsRDD2 = wordsRDD.map(word => (word,1))//进行转换
wordsRDD2: org.apache.spark.rdd.RDD[(String, Int)] = MapPartitionsRDD[1] at map at <console>:25

scala> wordsRDD2.collect//查看转换后的结果
res1: Array[(String, Int)] = Array((hadoop,1), (spark,1), (storm,1), (hadoop,1), (hadoop,1), (spark,1))

reduceByKey。我们把原始的值变成了(word,1)这种元组的方式对应于(key,value)。

分组之后会变成这样

image-20210201115253201
scala> val result =wordsRDD2.reduceByKey( (num1,num2) => num1+num2)
result: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[2] at reduceByKey at <console>:25

scala> result.collect
res2: Array[(String, Int)] = Array((spark,2), (hadoop,3), (storm,1))

同名的key进行分组,累加

方式二

【方式2】
scala> val wordsRDD =
sc.parallelize(List("hadoop","spark","storm","flink","hadoop","hadoop","flink"))
wordsRDD: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[17] at
parallelize at <console>:24
scala> wordsRDD.map(word=>(word,1)).reduceByKey(_+_).collect
res14: Array[(String, Int)] = Array((spark,1), (hadoop,3), (flink,2), (storm,1))

3.3.4 sortByKey算子

scala> result.sortByKey(false).collect //false则为降序,默认是升序 是按key的首字母进行排序的
res4: Array[(String, Int)] = Array((storm,1), (spark,2), (hadoop,3))

3.4RDD的行动操作

行动操作:立即执行

上面的转换操作是lazy操作,不是立即执行的

3.4.1 count
  • 返回数据集中的元素的个数

    wordsRDD.count()//括号可以省略
    
3.4.2 take
  • 返回前n个数据

     wordsRDD.take(3)
    
3.4.3 saveAsTextFile
  • 把数据集保存在hdfs里

    [hadoop@hadoop01 ~]$ hdfs dfs -mkdir -p /output/worldResult
    
scala> result.collect
res20: Array[(String, Int)] = Array((spark,1), (hadoop,3), (flink,2), (storm,1))
scala> result.saveAsTextFile("hdfs:///output/wordResult")

image-20210201141542996

[hadoop@hadoop01 ~]$ hdfs dfs -cat /output/wordResult/*
2021-02-01 14:18:29,823 INFO sasl.SaslDataTransferClient: SASL encryption trust check: localHostTrusted = false, remoteHostTrusted = false
(spark,2)
(hadoop,3)
(storm,1)
[hadoop@hadoop01 ~]$