Spark3.X
需要scala2.12,基于内存的快速、通用、可扩展的大数据分析计算引擎
14年成为Apache顶级项目
采用内存的计算策略,两次MR中间结果不会落盘而是在内存中
一次性的数据计算:各类的框架在处理数据的时候,会从存储的设备中读取数据,进行逻辑操作,然后将处理的结果重新存储到某种介质中
spark是有其生态的存在,包含:core、sql、streaming、MLlib、GraphX
Spark Core
运行环境
模式 | 说明 |
---|---|
Local | 本地模式,idea内并不是本地环境,而是spark-shell模式 |
StandLone | 独立部署模式,需修改slave、env文件 |
Yarn | yarn模式,分为client和cluster |
举例:将spark3.x的tar包上传linux,解压进入bin目录
1 运行spark-shell(本地模式),进行简单的wc
sc.textFile("/disk3/spark-3.0.0-bin-hadoop3.2/data/word.txt").flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).collect
2 用spark-submit方式
bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master local[2] \
./examples/jars/spark-examples_2.12-3.0.0.jar \
10
3 用standlone方式
bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master spark://localhost:7077 \
./examples/jars/spark-examples_2.12-3.0.0.jar \
10
spark-submit相关参数 | 解释 | 举例 |
---|---|---|
–class | 程序入口,包含主函数的类 | |
–master | 运行的环境 | local[*]、spark://localhost:7077、Yarn |
–executor-memory 2G | 每个executor可用内存为2G | |
–total-executor-cores 2 | 所有executor可用cpu核数为2个 | |
application-jar | jar包(带位置),hdfs(hdfs:// )、本地文件(file:// ) | |
application-arguments | 需要传入的参数 |
历史服务
很显然在运行任务结束后,4040也页面就无法访问到,所以需要配置历史服务器
# 注意:需要先启动 hadoop 集群,HDFS 上的 directory 目录需要提前存在。
# 修改spark-default.conf
spark.eventLog.enabled true
spark.eventLog.dir hdfs://xxx1:8020/directory
# 修改spark-env.sh文件
export SPARK_HISTORY_OPTS="
-Dspark.history.ui.port=18080 #WEB UI 访问的端口号为 18080
-Dspark.history.fs.logDirectory=hdfs://xxx1:8020/directory #指定历史服务器日志存储路径
-Dspark.history.retainedApplications=30" #指定保存 Application 历史记录的个数,如果超过这个值,旧的应用程序信息将被删除,这个是内存中的应用数,而不是页面上显示的应用数
配置高可用
采用zookeeper
# 关闭spark集群
# 启动zk
# 修改spark-env文件
注释如下内容:
#SPARK_MASTER_HOST=xxx1
#SPARK_MASTER_PORT=7077
添加如下内容:
#Master 监控页面默认访问端口为 8080,但是可能会和 Zookeeper 冲突,所以改成 8989,也可以自定义,访问 UI 监控页面时请注意
SPARK_MASTER_WEBUI_PORT=8989
export SPARK_DAEMON_JAVA_OPTS="
-Dspark.deploy.recoveryMode=ZOOKEEPER
-Dspark.deploy.zookeeper.url=xxx1,xxx2,xxx3
-Dspark.deploy.zookeeper.dir=/spark"
# 分发集群,启动spark集群
# 提交任务,需要改变master
bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master spark://xxx1:7077,xxx2:7077 \
./examples/jars/spark-examples_2.12-3.0.0.jar \
10
Yarn模式
# 修改spark-env.sh文件
export JAVA_HOME=xxxxxx
YARN_CONF_DIR=xxxx/etc/hadoop
# spark目录内提交
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
运行架构
计算端
driver
:并没有实际的名词叫做driver,而他是作为一种驱动类存在,驱使着整个应用运行的程序
executor
:是集群worker中的一个jvm进程,具体运行具体的任务Task,彼此之前相互独立,生命周期只局限于整个应用的运行,即使有executor节点发生故障,spark应用也会继续进行,会将出错节点任务调度到其他executor节点上;作为执行器,它负责将运行结果返回给driver,它自身包含块管理器(BlockManager)为用户程序中需要缓存的rdd进行内存支持,所以rdd是直接缓存在executor进程内,因而可充分利用缓存数据计算
资源端
当我们运行standlone模式的时候会出现两个进程master和worker,master负责资源的调度和分配,并监控集群,而work就是由master分配资源对数据进行并行的处理和计算
如果单纯让资源和计算直接交互不就使得耦合性很高,所以采用了AM(ApplicationMaster),从而整体的效果计算计算端—>AM—>资源端
核心概念
硬件资源配置
名词 | 解释 |
---|---|
–num-executors | executor的数目 |
–executor-memory | 每个executor的内存大小 |
–executor-cores | 每个executor的cpu核数 |
并行度
Parallelism:多任务并行操作,分布式计算采用的就是并行
DAG
有向无环图:并不是真正的存在此图形,而是spark程序直接映射成数据流的高级抽象模型,更加易于理解
核心编程
相关数据结构
名词 | 解释 |
---|---|
RDD | 弹性分布式数据集 |
累加器 | 共享写变量 |
广播变量 | 共享读变量 |
RDD可以形象理解成是一个计算单元,就是要计算的每个步骤中的其中一个小步骤,我们可以拿word count举例
# 1 进入textfile内
def textFile(
...
{
...
hadoopFile(path,classOf[TextInputFormat],classOf[LongWritable], classOf[Text],minPartitions).map(pair =>pair._2.toString).setName(path)
}
)
# 进入hadoopFile内
def hadoopFile[K, V]{
...
new HadoopRDD(
this,
confBroadcast,
Some(setInputPathsFunc),
inputFormatClass,
keyClass,
valueClass,
minPartitions).setName(path)
}
# 发现HadoopRDD
# 2 进入flatMap内
def flatMap {
...
new MapPartitionsRDD[U, T](this, (_, _, iter) => iter.flatMap(cleanF))
}
# 发现MapPartitionsRDD
......
......
每一个计算的方法都是由RDD组成,RDD数据处理的方式类似于java IO操作,装饰者模式,在io操作中不管是字符流还是字节流都是缓冲流存在,但是在rdd内没有缓冲这一操作,都是流式处理,中间不存储数据
RDD
特点
弹性的、不可变的、可分区的、元素可并行计算的
弹性:
存储的弹性(磁盘、内存):内存不够需要落盘
容错:数据丢失自动恢复,每一个步骤的计算都会记录位置
计算:计算出错,重试的机制
分片:可以根据需要重新分区
分布式:
数据集:RDD只是封装了计算的逻辑,并不保存数据
数据抽象:RDD是一个抽象类,需要子类具体实现
不可变:RDD封装了计算逻辑,不可改变,要想改变结构只能通过其他RDD改变(其他计算逻辑)
可分区、分区计算:
五大属性
* - A list of partitions
* - A function for computing each split
* - A list of dependencies on other RDDs
* - Optionally, a Partitioner for key-value RDDs (e.g. to say that the RDD is hash-partitioned)
* - Optionally, a list of preferred locations to compute each split on (e.g. block locations for
* an HDFS file)
# 1 分区列表
# 2 每个分区有计算的函数
# 3 RDD之间有依赖关系
# 4 分区器:怎么分区的,按什么规则
# 5 首选位置:task发给哪一个executor去执行,移动数据不如移动计算
并行度和分区
# 可以手动指定分区数目,后面接的2就是两个分区
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)
# 这时调用另存文件的时候,就会保存两个文件===>两个分区
rdd.saveAsTextFile("output")
# 注意:默认的并行度是按照线程数
override def defaultParallelism(): Int = scheduler.conf.getInt("spark.default.parallelism", totalCores)
集合作为数据源:数据是怎么被分区的?
# 源码
def positions(length: Long, numSlices: Int): Iterator[(Int, Int)] = {
(0 until numSlices).iterator.map { i =>
val start = ((i * length) / numSlices).toInt
val end = (((i + 1) * length) / numSlices).toInt
(start, end)
}
}
# 采用的上述算法 数据总数目,分区数量 进行计算位置得出具体数据对应哪一个分区
单文件作为数据源:数据是怎么被分区的?
# 默认最小分区数是2,注意这里是默认最小,分区比2小就是不一定就是2
def defaultMinPartitions: Int = math.min(defaultParallelism, 2)
# 分区规则实现:
# 其实我们可以发现,spark是用了Hadoop中mapreduce读取文件方式
# 追溯FileInputFormat,其中有getSplits方法
# 该方法内有totalSize,这里要注意文件长度包含隐藏的字符(比如换行、回车)
totalSize += file.getLen();
# 每个分区存放几个字节
long goalSize = totalSize / (numSplits == 0 ? 1 : numSplits);
# 这里提及Hadoop中读取文件1.1概念,即剩余字节数大于上一个字节数的10%,产生新分区,否则不会产生新分区
# 因为当我们有7个字节文件,默认两个分区,但最后却是3个分区,就是因为7/2=3,还剩1个字节没地方放,又1>3*0.1,所以产生新分区,综上就是3个分区
# 继续解释
# 假设某个文件内容为1234567
# 按照上面理论,三个分区,三个文件,那要怎么读取?每个分区保存的是什么数据呢?
1、spark采用hadoop读文件,那应该是按行读取
2、数据读取应该是以偏移量作为单位
注意:上面计算是每个7/2=3,每个分区3个,按照偏移量排如下,偏移量范围是左开右闭
分区 偏移量范围
0 [0,3]
1 (3,6]
2 (6,7]
虽说这样理论排列,但并非如此,因为是按行,所以在读取刚开始的时候就知道一行数据,就会把一行数据放在一个分区文件,所以最后排列方式是,第一个文件为1234567,其他两个文件为空,其实这里可以想到贪婪的算法,既要考虑偏移量范围还要考虑一行,所以一行数据所在偏移量被读后,该行其他字节就会连着被读的偏移量
多文件作为数据源,分区以文件为单位进行分区