Spark基础及架构

Spark简介

Spark 是加州大学伯克利分校 AMP(Algorithms,Machines,People)实验室开发的通用内存并行计算框架。

发展历程

  • 2009年诞生于加州大学伯克利分校AMP实验室
  • 2010年正式开源
  • 2013年6月正式成为Apache孵化项目
  • 2014年2月成为Apache顶级项目
  • 2014年5月正式发布Spark 1.0版本
  • 2014年10月Spark打破MapReduce保持的排序记录
  • 2015年发布了1.3、1.4、1.5版本
  • 2016年发布了1.6、2.x版本

为什么使用Spark

  • MapReduce编程模型的局限性
    ①繁杂:虽然操作只有map和reduce,但是复杂的逻辑需要大量的样例代码
    ②处理效率低:map中间结果要写入磁盘,reduce结果要写入hdfs,多个map要通过hdfs交换数据;任务调度与启动的开销大
    ③不适合迭代处理、交互式处理和流式处理
  • Spark是类Hadoop MapReduce的通用并行框架
    ①spark中job的中间输出结果可以保存在内存,不需要读写hdfs
    ②比MapReduce平均快10倍以上

Spark优势

  • 速度快
    ①基于内存数据处理,比mapreduce快100个数量级以上
    ②基于硬盘数据处理,比mapreduce快10个数量级以上

  • 易用性
    ①支持Java、Scala、Python、R语言
    ②交互式shell方便开发

  • 通用性
    一栈式解决方案:批处理、交互式查询、实时流处理、图计算及机器学习

  • 随处运行
    支持多种运行模式:YARN、Local、Standalone、Mesos、EC2、Kubernetes

Spark技术栈

在这里插入图片描述

  • Spark Core
    核心组件,分布式计算引擎

  • Spark SQL
    高性能的基于Hadoop的SQL解决方案

  • Spark Streaming
    可以实现高吞吐量、具备容错机制的准实时流处理系统

  • Spark GraphX
    分布式图处理框架

  • Spark MLlib
    构建在Spark上的分布式机器学习库

Spark常用三种运行模式

  • Local模式
    Local 模式是最简单的一种Spark运行方式,它采用单节点多线程)方式运行,local模式是一种OOTB(开箱即用)的方式,只需要在spark-env.sh配置JAVA_HOME,无需其他任何配置即可使用,常用于开发和学习
    方式:spark-shell - -master local[n] ,其中n代表线程数(核数)

  • Standalone模式
    Spark可以通过部署与Yarn的架构类似的框架来提供自己的集群模式,该集群模式的架构设计与HDFS和Yarn相似,都是由一个主节点多个从节点组成,在Spark 的Standalone模式中,主为:master,从为:worker

// spark-env.sh中配置如下
SPARK_MASTER_HOST=192.168.233.133 //配置Master节点
SPARK_WORKER_CORES=2 //配置应用程序允许使用的核数(默认是所有的core)
SPARK_WORKER_MEMORY=2g  //配置应用程序允许使用的内存(默认是一个G)

// slaves配置如下,添加所有worker的ip地址
ip地址1
ip地址2
ip地址n
  • Spark on Yarn
    Spark on Yarn 模式就是将Spark应用程序跑在Yarn集群之上,通过Yarn资源调度将Executor启动在Container中,从而完成Driver端分发给Executor的各个任务。将Spark作业跑在Yarn上,首先需要启动Yarn集群,然后通过spark-shell或spark-submit的方式将作业提交到Yarn上运行。
// 需要在spark-env.sh中配置HADOOP_CONF_DIR
 HADOOP_CONF_DIR=/opt/hadoop260/etc/hadoop

Yarn的两种模式
① client
② cluster
可以通过- -deploy-mode 进行指定,也可以直接在 - -master 后面使用 yarn-client和yarn-cluster进行指定
两种模式的区别
在于driver端启动在本地(client),还是在Yarn集群内部的AM中(cluster)

Spark架构设计

  • 运行架构
    ①在驱动程序中,通过SparkContext主导应用的执行
    ②SparkContext可以连接不同类型的Cluster Manager(Standalone、YARN、Mesos),连接后,获得集群节点上的Executor
    ③一个Worker节点默认一个Executor,可通过SPARK_WORKER_INSTANCES调整
    ④每个应用获取自己的Executor
    ⑤每个Task处理一个RDD分区

在这里插入图片描述

Spark架构核心组件

术语说明
Application建立在Spark上的用户程序,包括Driver代码和运行在集群各节点Executor中的代码
Driver program驱动程序。Application中的main函数并创建SparkContext
Cluster Manager在集群(Standalone、Mesos、YARN)上获取资源的外部服务
Worker Node集群中任何可以运行Application代码的节点
Executor某个Application运行在worker节点上的一个进程
Task被送到某个Executor上的工作单元
Job包含多个Task组成的并行计算,往往由Spark Action触发生成,一个Application中往往会产生多个Job
Stage每个Job会被拆分成多组Task,作为一个TaskSet,其名称为Stage

Spark API

  • Maven项目需要导入的依赖
<dependency>
  <groupId>org.scala-lang</groupId>
  <artifactId>scala-library</artifactId>
  <version>2.11.8</version>
</dependency>

<dependency>
  <groupId>org.apache.spark</groupId>
  <artifactId>spark-core_2.11</artifactId>
  <version>2.1.1</version>
</dependency>

<dependency>
  <groupId>org.apache.spark</groupId>
  <artifactId>spark-sql_2.11</artifactId>
  <version>2.1.1</version>
</dependency>
  • SparkContext
    ①连接Driver与Spark Cluster(Workers)
    ②Spark的主入口
    ③每个JVM仅能有一个活跃的SparkContext
    ④SparkContext对象的获取
// 导包
import org.apache.spark.{SparkConf, SparkContext}
// 配置
val conf=new SparkConf().setMaster("local[2]").setAppName("HelloSpark")
// 获取SparkContext对象
val sc=SparkContext.getOrCreate(conf)
  • SparkSession
    Spark 2.0+应用程序的主入口:包含了SparkContext、SQLContext、HiveContext以及StreamingContext
  • RDD
    Spark核心,最基本的数据抽象
  • Dataset
    从Spark1.6开始引入的新的抽象,特定领域对象中的强类型集合,它可以使用函数或者相关操作并行地进行转换等操作
  • DataFrame
    DataFrame是特殊的Dataset

Spark核心:RDD

RDD概念

RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变、可分区且里面的元素可并行计算的集合。RDD具有数据流模型的特点:自动容错、位置感知性调度和可伸缩性。RDD允许用户在执行多个查询时显式地将工作集缓存在内存中,后续的查询能够重用工作集,这极大地提升了查询速度。

RDD的特性

  • A list of partitions
    一组分区,分区是spark中数据集的最小单位。即spark当中数据是以分区为单位存储的,不同的分区被存储在不同的节点上,这也是分布式计算的基础
  • A function for computing each split
    一个应用在各个分区上的计算任务。在spark中数据和执行的操作是分开的,并且spark基于懒计算的机制,在真正触发计算的行动操作出现之前,spark会将对哪些数据执行哪些计算存储起来。数据和计算之间的映射关系就存储在RDD中
  • A list of dependencies on other RDDs
    RDD之间的一组依赖关系。RDD之间存在转化关系,一个RDD可以通过转化操作转化成其他RDD,这些转化操作都会被记录下来,当部分数据丢失的时候,spark可以通过记录的依赖关系重新计算丢失部分的数据,而不是重新计算所有数据
  • Optionally, a Partitioner for key-value RDDs
    一个分区的方法,也就是计算分区的函数。spark当中支持基于hash的hash分区方法和基于范围的range分区方法
  • Optionally, a list of preferred locations to compute each split on
    一个列表,存储的内容是存储每个分区的优先存储的位置。

如上所述,可以看出spark一个重要的理念:即移动数据不如移动计算。在spark运行调度的时候,会倾向于将计算分发到节点,而不是将节点的数据搜集起来计算,RDD正是基于这一理念而生的

RDD编程流程

在这里插入图片描述

RDD创建

  • 使用集合创建RDD
    该种方式可使用parallelizemakeRDD两种函数
import org.apache.spark.rdd.RDD
import org.apache.spark.{Partition, SparkConf, SparkContext}
object WorldCount {
  def main(args: Array[String]): Unit = {
  	// 获取配置
    val conf:SparkConf = new SparkConf().setMaster("local[*]").setAppName("worldCount")
    // 获取SparkContext对象
    val sc:SparkContext = SparkContext.getOrCreate(conf)
	
	// 使用parallelize函数
    val rdd1:RDD[String] = sc.parallelize(List("hi scala","hi java","hi world"))
    println("--------parallelize--------")
    // worldCount
	rdd1.flatMap(x=>x.split(" ")).map((_,1)).reduceByKey(_+_).collect.foreach(println)
	println("--------makeRDD--------")
    val rdd2:RDD[String] = sc.makeRDD(List("hello scala","hello java","hello world"))
    rdd2.flatMap(x=>x.split(" ")).map((_,1)).reduceByKey(_+_).collect.foreach(println)
  }
}

在这里插入图片描述
注意*:
①Spark默认会根据集群的情况来设置分区的数量,也可以通过parallelize()第二参数来指定
②Spark会为每一个分区运行一个任务进行处理
③makeRDD底层实际调用的还是parallelize()

  • 通过加载文件产生RDD
import org.apache.spark.rdd.RDD
import org.apache.spark.{Partition, SparkConf, SparkContext}
object WorldCount2 {
  def main(args: Array[String]): Unit = {
  	// 获取配置
    val conf:SparkConf = new SparkConf().setMaster("local[*]").setAppName("worldCount")
    // 获取SparkContext对象
    val sc:SparkContext = SparkContext.getOrCreate(conf)
	// 使用本地文件系统,windows、linux均可
    val rdd3:RDD[String] = sc.textFile("file:///f:/a.txt")
    println("--------本地文件--------")
    // worldCount
	rdd3.flatMap(x=>x.split(" ")).map((_,1)).reduceByKey(_+_).collect.foreach(println)
	println("--------hdfs--------")
    // hdfs分布式文件系统
    val rdd4:RDD[String] = sc.textFile("hdfs://hadoop4:9000/user/a.txt")
    rdd4.flatMap(x=>x.split(" ")).map((_,1)).reduceByKey(_+_).collect.foreach(println)
  }
}

在这里插入图片描述

  • 其他方法
    SparkContext.wholeTextFiles():可以针对一个目录中的大量小文件返回<filename,fileContent>作为PairRDD
    SparkContext.sequenceFile[K,V]():Hadoop SequenceFile的读写支持
    SparkContext.hadoopRDD()newAPIHadoopRDD():从Hadoop接口API创建
    SparkContext.objectFile():RDD.saveAsObjectFile()的逆操作

RDD分区和RDD操作

RDD分区
分区是指RDD拆分后,被并发送到节点的不同块之一

  • 拥有的分区越多,得到的并行性就越强
  • 每个分区都是被分发到不同Worker Node的候选者
  • 每个分区对应一个Task
  • 只有Key-Value类型的RDD才有分区的,非Key-Value类型的RDD分区的值是None
  • 每个RDD的分区ID范围:0~numPartitions-1,决定这个值是属于哪个分区

在这里插入图片描述
RDD操作
RDD的操作分为lazy和non-lazy两种

  • Transformation(lazy):也称为转换操作、转换算子
  • Actions(non-lazy):立即执行,也称为动作操作、动作算子

RDD转换算子

对于转换操作,仅记录作用于RDD上的操作,当遇到动作算子才会真正计算
常用RDD转换算子–map算子

  • 对RDD中的每个元素都执行一个指定的函数来产生一个新的RDD
  • 任何原RDD中的元素在新RDD中都有且只有一个元素与之对应
  • 输入分区与输出分区一一对应
def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[*]").setAppName("mapDemo")
    val sc = SparkContext.getOrCreate(conf)
    val rdd1 = sc.parallelize(1 to 10)
    val rdd2 = rdd1.map(_*2)
    // map把普通RDD变成PairRDD,RDD类型为<k,v>形式
    val rdd3 = rdd1.map(x=>(x,1)) 
    rdd2.collect.foreach(println)
    rdd3.collect.foreach(println)
}

常用RDD转换算子–mapValue算子

  • 原RDD中的Key保持不变,与新的Value一起组成新的RDD中的元素,仅适用于PairRDD
val rdd1 = sc.makeRDD(List("tiger","dog","lion","cat","panther","eagle"))
    val rdd2 = rdd1.map(x=>(x.length,x))
    rdd2.collect().foreach(println)
    val rdd3 = rdd2.mapValues(x=>"_"+x+"_").collect().foreach(println)

在这里插入图片描述
常用RDD转换算子–filter算子
对元素进行过滤,对每个元素应用指定函数,返回值为true的元素保留在新的RDD中

    val rdd1 = sc.makeRDD(1 to 10)
    rdd1.filter(_%2==0).collect.foreach(println)
    rdd1.filter(_%2==1).collect.foreach(println)

在这里插入图片描述
常用RDD转换算子–distinct算子

 	val rdd1 = sc.parallelize(List(1,1,2,3,4,4,5,6,8,9,8,8))
    val rdd2 = rdd1.distinct
    // 指定分区数
    val rdd3 = rdd1.distinct(2)
    println("rdd1的分区数:"+rdd1.partitions.length)
    println("rdd2的分区数:"+rdd2.partitions.length)
    println("rdd3的分区数:"+rdd3.partitions.length)
    rdd2.collect.foreach(print)

在这里插入图片描述

常用RDD转换算子–reduceByKey、groupByKey、sortByKey算子

 val rdd1 = sc.makeRDD(List("tiger","dog","lion","cat","panther","eagle"))
    val rdd2 = rdd1.map(x=>(x.length,x)).reduceByKey(_+_).collect.foreach(println)
    println("-----------groupByKey------")
    val rdd3 = rdd1.map(x=>(x.length,x)).groupByKey().collect.foreach(println)
    println("---------sortByKey---------")
    val rdd4 = rdd1.map(x=>(x.length,x)).sortByKey().collect.foreach(println)
    println("---------sortByKey逆序---------")
    val rdd5 = rdd1.map(x=>(x.length,x)).sortByKey(false).collect.foreach(println)

在这里插入图片描述
常用RDD转换算子–union、intersection算子

	val rdd1 = sc.parallelize(1 to 3)
    val rdd2 = sc.parallelize(3 to 4)
    println("----------union------------")
    // 并集不去重
    rdd1.union(rdd2).collect.foreach(println)
    println("----------++------------")
    (rdd1 ++ rdd2).collect.foreach(println)
    println("----------intersection------------")
    // 取交集
    rdd1.intersection(rdd2).collect.foreach(println)

在这里插入图片描述
常用RDD转换算子–join、leftOuterJoin、rightOuterJoin算子

	val rdd3 = sc.parallelize(List("a","b","c")).map((_,1))
    val rdd4 = sc.parallelize(List("c","d","e","f")).map((_,1))
    println("----------join------------")
    rdd3.join(rdd4).collect.foreach(println)
    println("----------leftOuterJoin------------")
    rdd3.leftOuterJoin(rdd4).collect.foreach(println)
    println("----------rightOuterJoin------------")
    rdd3.rightOuterJoin(rdd4).collect.foreach(println)

在这里插入图片描述

RDD动作算子

本质上动作算子通过SparkContext执行提交作业操作,触发RDD DAG(有向无环图)的执行。所有的动作算子都是急迫型(non-lazy),RDD遇到Action就会立即计算
RDD常用动作算子–count算子

  • 返回的是数据集中的元素的个数
val rdd=sc.parallelize(List(1,2,3,4,5,6))
rdd.count // 6

RDD常用动作算子–collect算子

  • 以Array返回RDD的所有元素。一般在过滤或者处理足够小的结果的时候使用
val rdd=sc.parallelize(List(1,2,3,4,5,6))
rdd.collect

RDD常用动作算子–take算子

  • 返回前n个元素
val rdd=sc.parallelize(List(1,2,3,4,5,6))
rdd.take(3)

RDD常用动作算子–first算子

  • 返回RDD第一个元素
val rdd=sc.parallelize(List(1,2,3,4,5,6))
rdd.first

RDD常用动作算子–reduce算子

  • 根据指定函数,对RDD中的元素进行两两计算,返回计算结果
    val rdd1 = sc.makeRDD(1 to 5)
    rdd1.reduce((x,y)=>{println(x,y);x+y})
    rdd1.reduce(_+_)

RDD常用动作算子–foreach算子

  • 对RDD中的每个元素都使用指定函数,无返回值
val rdd=sc.parallelize(1 to 100)
rdd.foreach(println)

RDD常用动作算子–lookup算子

  • 用于PairRDD,返回K对应的所有V值
val rdd=sc.parallelize(List(('a',1), ('b',2), ('b',3), ('c',4)))
rdd.lookup('b')		// WrappedArray(2, 3)

RDD常用动作算子–max、min算子

  • 求最大最小值
val rdd=sc.parallelize(1 to 30)
rdd.max	//求最大值
rdd.min	//求最小值

RDD常用动作算子–saveAsTextFile算子

  • 保存RDD数据至文件系统
val rdd1 = sc.makeRDD(1 to 5)
// 存储到hdfs文件系统
rdd1.saveAsTextFile("hdfs://192.168.233.133:9000/user/rdd1.txt")
// 本地文件系统
rdd1.saveAsTextFile("file:///f:/user/rdd1.txt")

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值