大数据——Apache Spark分布式计算原理

Spark WordCount运行原理

在这里插入图片描述

RDD的依赖关系(一)

Lineage:血统、遗传

  • RDD最重要的特性之一,保存了RDD的依赖关系
  • RDD实现了基于Lineage的容错机制

依赖关系

  • 宽依赖
  • 窄依赖
    在这里插入图片描述

RDD的依赖关系(二)

宽依赖对比窄依赖

  • 宽依赖对应shuffle操作,需要在运行时将同一个父RDD的分区传入到不同的子RDD分区中,不同的分区可能位于不同的节点,就可能涉及多个节点间数据传输
  • 当RDD分区丢失时,Spark会对数据进行重新计算,对于窄依赖只需重新计算一次子RDD的父RDD分区
    在这里插入图片描述

结论:相比于宽依赖,窄依赖对优化更有利

判断RDD依赖关系

  • map (窄)
  • flatMap (窄)
  • filter (窄)
  • distinct (宽)
  • reduceByKey (宽)
  • groupByKey (宽)
  • sortByKey (宽)
  • union (窄)
  • join (窄)

DAG工作原理

根据RDD之间的依赖关系,形成一个DAG(有向无环图)
DAGScheduler将DAG划分为多个Stage

  • 划分依据:是否发生宽依赖(Shuffle)
  • 划分规则:从后往前,遇到宽依赖切割为新的Stage
  • 每个Stage由一组并行的Task组成

在这里插入图片描述

为什么需要划分Stage

数据本地化

  • 移动计算,而不是移动数据
  • 保证一个Stage内不会发生数据移动

最佳实践

  • 尽量避免Shuffle
  • 提前部分聚合减少数据移动
    在这里插入图片描述

Spark Shuffle过程

在分区之间重新分配数据

  • 父RDD中同一分区中的数据按照算子要求重新进入子RDD的不同分区中
  • 中间结果写入磁盘
  • 由子RDD拉取数据,而不是由父RDD推送
  • 默认情况下,Shuffle不会改变分区数量
    在这里插入图片描述

RDD优化

RDD持久化
RDD共享变量
RDD分区设计
数据倾斜

RDD持久化(一)

RDD缓存机制:缓存数据至内存/磁盘,可大幅度提升Spark应用性能

  • cashe=persist(MEMORY)
  • persist
val u1 = sc.textFile("file:///root/data/users.txt").cache
u1.collect//删除users.txt,再试试
u1.unpersist() 

缓存策略StorageLevel

  • MEMORY_ONLY(默认)
  • MEMORY_AND_DISK
  • DISK_ONLY

示例

package nj.zb.kb09.gaoji

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

object CacheDemo {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("cachedemo")
    val sc: SparkContext = new SparkContext(conf)
    /*val rdd1: RDD[(String, Int)] = sc.parallelize(List(("a",1),("a",2),("b",1),("b",2)))
    rdd1.cache()
    rdd1.collect.foreach(println)
    println(rdd1.count())*/


    val rdd2: RDD[String] = sc.textFile("in/users.csv")
    rdd2.cache()
    //rdd2.count()

    println("第一次不将rdd2缓存后,求读取count时间")
    var start: Long = System.currentTimeMillis()

    println(rdd2.count())
    var ends: Long = System.currentTimeMillis()
    println("读取时间:"+(ends-start))

    println("第二次将rdd2缓存,求读取count时间")
    start = System.currentTimeMillis()

    println(rdd2.count())
    ends= System.currentTimeMillis()
    println("读取时间:"+(ends-start))


    println("第三次将rdd2缓存失效,求读取count时间")
    rdd2.unpersist()
    start = System.currentTimeMillis()

    println(rdd2.count())
    ends= System.currentTimeMillis()
    println("读取时间:"+(ends-start))
    
    /*rdd2.persist(StorageLevel.MEMORY_ONLY)*/
  }
}

结果展示:在这里插入图片描述

RDD持久化(二)

缓存应用场景

  • 从文件加载数据之后,因为重新获取文件成本较高
  • 经过较多的算子交换之后,重新计算成本较高
  • 单个非常消耗资源的算子之后

使用注意事项

  • cache()或persist()后不能再有其他算子
  • cache()或persist()遇到Action(行动)算子后完成后才生效

RDD持久化(三)

检查点:类似于快照

sc.setCheckpointDir("hdfs:/checkpoint0918")
val rdd=sc.parallelize(List(('a',1), ('a',2), ('b',3), ('c',4)))
rdd.checkpoint
rdd.collect //生成快照
rdd.isCheckpointed
rdd.getCheckpointFile

检查点与缓存的区别

  • 检查点会删除RDD Lineage,而缓存不会
  • SparkContext被销毁后,检查点数据不会被删除

示例

package nj.zb.kb09.gaoji

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

object CheckPointDemo {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("CheckPointDemo")
    val sc: SparkContext = new SparkContext(conf)
    sc.setCheckpointDir("file:///D:/KB09checkpoint")
    val rdd1: RDD[(String, Int)] = sc.parallelize(List(("a",1),("a",2),("b",1),("b",2)))
    rdd1.checkpoint()
    rdd1.collect()  //生成快照
    println(rdd1.isCheckpointed)
    println(rdd1.getCheckpointFile)
  }
}

结果展示:在这里插入图片描述

RDD共享变量(一)

广播变量:允许开发者将一个只读变量(Driver端)缓存到每个节点(Executor)上,而不是每个任务传递一个副本

val broadcastVar=sc.broadcast(Array(1,2,3))  //定义广播变量
broadcastVar.value 		//访问方式

注意事项:
1、Driver端变量在每个Executor每个Task保存一个变量副本
2、Driver端广播变量在每个Executor只保存一个变量副本

示例

package nj.zb.kb09.gaoji

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

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

    //广播
    val arr=Array("hello","hi","good afternoon")

    val hei: Broadcast[Array[String]] = sc.broadcast(arr)

    val rdd1: RDD[(Int, String)] = sc.parallelize(List((1,"zhangsan"),(2,"lisi"),(3,"wangwu")))
    val rdd2: RDD[(Int, String)] = rdd1.mapValues(x => {
      /*println(x)
      println(arr.toString())
      arr(2)*/

      println(x)
      println(hei.value.toList)
      hei.value(2)+":"+x
    })
    rdd2.foreach(println)
  }
}

结果展示:在这里插入图片描述

RDD共享变量(二)

累加器:只允许added操作,常用于实现计数

val accum = sc.accumulator(0,"My Accumulator")
sc.parallelize(Array(1,2,3,4)).foreach(x=>accum+=x)
accum.value

示例

package nj.zb.kb09.gaoji

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

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

    val acumValue: Accumulator[Int] = sc.accumulator(0,"aaaa")


    val rdd1: RDD[Int] = sc.makeRDD(List(1,2,3,4))
    rdd1.glom().collect.foreach(x=>println(x.toList))

    println("----------------------------------")
    sc.makeRDD(List(1,2,3,4)).foreach(x=>acumValue+=x)
    println(acumValue.value)
  }
}

结果展示:在这里插入图片描述

RDD分区设计

分区大小限制为2GB

分区太少

  • 不利于开发
  • 更容易受数据倾斜影响
  • groupBy、reduceByKey、sortByKey等内存压力增大

分区过多

  • Shuffle开销越大
  • 创建任务开销越大

经验

  • 每个分区大约128MB
  • 如果分区小于但接近2000,则设置为大于2000

数据倾斜

指分区中的数据分配不均匀,数据集中在少数分区中

  • 严重影响性能
  • 通常发生在groupBy、join等之后
    在这里插入图片描述

解决方案

  • 使用新的Hash值(如对key加盐)重新分区

装载CSV数据源

文件预览
在这里插入图片描述
使用SparkContext

val lines = sc.textFile("file:///home/kgc/data/users.csv")
val fields = lines.mapPartitionsWithIndex((idx, iter) => if (idx == 0) iter.drop(1) else iter).map(l => l.split(","))
val fields = lines.filter(l=>l.startsWith("user_id")==false).map(l=>l.split(","))  //移除首行,效果与上一行相同

使用SparkSession

val df = spark.read.format("csv").option("header", "true").load("file:///home/kgc/data/users.csv")

示例

package nj.zb.kb09.gaoji

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

object CsvDemo {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("CsvDemo")
    val sc: SparkContext = new SparkContext(conf)
    val lines: RDD[String] = sc.textFile("in/users.csv")
    println("------------打印移除首行前的字数统计------------")
    println("lines:"+lines.count())
    val fields: RDD[Array[String]] = lines.mapPartitionsWithIndex((i, v) => {
      if (i == 0)
        v.drop(1)
      else
        v
    }).map(x => x.split(","))

    val fields2: RDD[Array[String]] = lines.filter(v=>v.startsWith("user_id")==false).map(x=>x.split(","))
    println("-----------打印移除首行的字数统计------------------")
    println("fields:"+ fields2.count())


    println("-----------去除首行---------------")
    val spark: SparkSession = SparkSession.builder().config(conf).getOrCreate()
    val df: DataFrame = spark.read.format("csv").option("header","true").load("in/users.csv")
    df.printSchema()
    df.select("user_id","locale","birthyear").show(5)

    println("--------------不去除首行-------------")
    val df2: DataFrame = spark.read.format("csv").option("header","false").load("in/users.csv")
    df2.printSchema()
    df2.show(5)

    println("-------------把_c0改为id,把类型改为long类型-------------")
    val frame = df2.withColumnRenamed("_c0","id")
    frame.printSchema()
    val frame1 = frame.withColumn("id",frame.col("id").cast("long"))

    frame1.printSchema()

  }
}

结果展示:在这里插入图片描述

装载JSON数据源

文件预览

在这里插入图片描述

使用SparkContext

val lines = sc.textFile("file:///home/kgc/data/users.json")
//scala内置的JSON库
import scala.util.parsing.json.JSON
val result=lines.map(l=>JSON.parseFull(l))

使用SparkSession

val df = spark.read.format("json").option("header", "true").load("file:///home/kgc/data/users.json")

示例

package nj.zb.kb09.gaoji

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

object JsonDemo {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("JsonDemo")
    val sc: SparkContext = new SparkContext(conf)
    val lines: RDD[String] = sc.textFile("in/users.json")
    println("----------------读取文件----------------------")
    lines.collect.foreach(println)
    println("---------------使用scala内置JSON库------------")
    import scala.util.parsing.json._
    val rdd: RDD[Option[Any]] = lines.map(x=>JSON.parseFull(x))
    rdd.collect.foreach(println)

    
    val spark: SparkSession = SparkSession.builder().config(conf).getOrCreate()
    val df: DataFrame = spark.read.format("json").option("header","true").load("in/users.json")
    df.printSchema()
    df.show()
    println("-----------------修改列名------------------")
    val df2: DataFrame = df.withColumnRenamed("Age","age")
    df2.printSchema()
    df2.select("age","name").show()

    println("------------------------修改列数据类型------------------")

    println("把Age的long类型改为int类型")
    val df3: DataFrame = df.withColumn("age", df.col("Age").cast("int"))
    df3.printSchema()
    df3.show()

    println("把Age的long类型改为string类型")
    val df4: DataFrame = df.withColumn("age",df.col("Age").cast("String"))
    df4.printSchema()
    df4.show()


    println("把name的string类型改为int类型")
    val df5: DataFrame = df.withColumn("name",df.col("name").cast("int"))
    df5.printSchema()
    df5.show()


    println("把Age的加5赋给age1,把name赋给name1")
    val df6: DataFrame = df.withColumn("age1",df.col("Age")+5).withColumn ("name1",df.col("name"))
    df6.printSchema()
    df6.show()
  }
}

结果展示:在这里插入图片描述

基于RDD的Spark应用程序开发(一)

典型开发步骤
在这里插入图片描述
开发环境

  • IDEA、Maven、Scala

基于RDD的Spark应用程序开发(二)

词频计数

  • 需求:统计HDFS上的某个文件的词频
  • 数据格式:制表符作为分隔符

实现思路

  • 首先需要将文本文件中的每一行转化成单词数组
  • 其次是对每一个出现的单词进行计数
  • 最后把所有相同单词的计数相加
spark-submit 
--class com.kgc.bigdata.spark.core.WordCount
--master spark://hadoop000:7077 
/home/hadoop/lib/spark-1.0.SNAPSHOT.jar  /data/wordcount

示例

  • word2.txt文件
hello java
hello spark
hello scala
hello python
hello mysql
hello hadoop
hello C
hello hive
hello hbse
  • userset.properties文件
loadfile:hdfs://hadoop100:9000/kb09file/word2.txt
outfile:hdfs://hadoop100:9000/kb09file/outfile
  • wordcount代码展示
package nj.zb.kb09.gaoji

import java.io.FileInputStream
import java.util.Properties

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

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

    val properties=new Properties()
    properties.load(new FileInputStream("/opt/kb09file/userset.properties"))
    val loadFilePath: String = properties.get("loadfile").toString
    println(loadFilePath)
    val outFilePath: String = properties.get("outfile").toString
    println(outFilePath)


    val rdd1: RDD[String] = sc.textFile(loadFilePath)
    val rdd2: RDD[(String, Int)] = rdd1.flatMap(x=>x.split(" ")).map((_,1)).reduceByKey(_+_)
    rdd2.collect.foreach(println)
    rdd2.saveAsTextFile(outFilePath)
    sc.stop()
  }
}
  • 把WordCount.scala打成jar包
  • 把jar包、word.txt、serset.properties三个文件放到Linux上的根目录下的opt目录下的kb9file文件下
    在这里插入图片描述
  • 在hdfs上创建文件夹
[root@hadoop100 kb09file]# hdfs dfs -mkdir /kb09file
  • 给文件夹赋权
[root@hadoop100 kb09file]# hdfs dfs -chmod -R 777 /kb09file
  • 把word2.txt文件上传到kb09file文件夹下
[root@hadoop100 kb09file]# hdfs dfs -put word2.txt /kb09file
  • 开启hadoop
[root@hadoop100 ~]# start-all.sh
  • 开启spark
[root@hadoop100 kb09file]# cd /opt/spark245/sbin/
[root@hadoop100 sbin]# ./start-all.sh
  • 把jar包中META-INF下的.DSA文件和.SF文件删掉
[root@hadoop100 kb09file]# zip -d /opt/kb9file/sparkdemo.jar 'META-INF/.SF' 'META-INF/.RSA' 'META-INF/*SF'
  • 提交运行
[root@hadoop100 kb09file]# spark-submit --class nj.zb.kb09.gaoji.WordCter local[*] sparkdemo.jar
  • 结果展示

在这里插入图片描述

在这里插入图片描述

  • 查看文件
[root@hadoop100 opt]# hdfs dfs -cat /kb09file/outfile/part-00000

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值