Spark On Yarn(2)

第二章 Spark On Yarn

1 Spark On Yarn环境配置

1.1 Spark On Yarn的本质

本质:将Spark程序运行在Yarn集群上,由Yarn负责资源调度。【Spark专注于计算,Yarn负责资源管理】

1.2 查看日志

将编写的WordCount的代码提交到Yarn平台运行

  • 注意1:【setMaster需要删除,或者改成yarn。推荐删除】
  • 注意2:【文件路径需要改成分布式文件系统,这里就是需要改成HDFS文件】。如果是本地文件路径,可能Yarn在运行的时候,无法找到对应的文件, 需要将所有的节点本地都得有这个文件

日志用途:主要用于排查问题
准备:执行程序测试查看日志:在cd /export/server/spark/bin/目录下执行

[root@node1 bin]# ../spark-submit \
--master yarn \
--conf "spark.pyspark.driver.python=/root/anaconda3/bin/python3" \
--conf "spark.pyspark.python=/root/anaconda3/bin/python3" \
/export/server/spark/examples/src/main/python/pi.py 20

① 通过Spark提供的18080日志服务器查看,这个日志包含提交到yarn和local应用的日志 推荐使用

## 启动MRHistoryServer服务,在node1执行命令
[root@node1 bin]# mr-jobhistory-daemon.sh start historyserver
WARNING: Use of this script to start the MR JobHistory daemon is deprecated.
WARNING: Attempting to execute replacement "mapred --daemon start" instead.

## 启动Spark HistoryServer服务,,在node1执行命令
[root@node1 bin]# /export/server/spark/sbin/start-history-server.sh
starting org.apache.spark.deploy.history.HistoryServer

命令假设MRHistoryServer和Spark HistoryServer已正确配置和安装在指定的路径中。确保命令为正确的路径和文件名,并具有适当的执行权限
Spark提供的18080

在这里插入图片描述

1- 发现日志很少: 在配置spark on yarn的时候,日志的级别更改为warn警告日志后基础日志(INFO级别)就不会被记录下来
2- 发现Driver没有日志: 【和部署模式有关】
3- spark的18080日志服务器是依赖于Yarn提供的job history日志服务器,如果没有这个日志服务,18080也无法查看到具体的日志

查看对应线程(Task)的日志
在这里插入图片描述

② 还可以通过Yarn的8088界面查看日志,这里的spark日志只会有提交到yarn集群的任务的日志
yarn的8088端口

在这里插入图片描述

1.3 Spark On Yarn两种部署方式

① Client模式: 【Driver运行在提交应用程序的节点】,应用是在哪里进行提交的,Driver就会运行在哪里。默认值

优势: 测试比较方便,直接通过Driver看到最终返回的结果
弊端: 由于不是在一起的(Driver 和 executor不是在一个环境中),受网络影响因素会比较大,导致执行效率比较低
使用: 【一般用于开发和测试】

文件准备:pyspark_word_count.py文件如下:

import os
from pyspark import SparkContext ,SparkConf
# 配置环境变量
os.environ['SPARK_HOME'] = '/export/server/spark'
os.environ['PYSPARK_PYTHON'] = '/root/anaconda3/envs/pyspark_env/bin/python'
os.environ['PYSPARK_DRIVER_PYTHON'] = '/root/anaconda3/envs/pyspark_env/bin/python'
os.environ['JAVA_HOME'] = '/export/server/jdk1.8.0_241'
if __name__ == '__main__':
    # 1、创建spark运行环境
    # sparkConf =SparkConf().setAppName('wordcount')
    sparkConf =SparkConf().setAppName('wordcount').setMaster('local[*]')
    sc =SparkContext(conf=sparkConf)
    # 2、链式调用
   # result=sc.textFile("file:///root/pybigdata/day01/hello.txt")\
    result=sc.textFile("hdfs://node1:8020/hello.txt")\
        .flatMap(lambda line:line.split())\
        .map(lambda word:(word,1)).\
        reduceByKey(lambda agg,crr:agg+crr).map(lambda w:(w[1],w[0])).\
        sortByKey(False).map(lambda w:(w[1],w[0]))
    print(result.collect())
    # sc.textFile("file:///root/pybigdata/day01/hello.txt")\
    #     .flatMap(lambda line:line.split()) \
    #     .map(lambda word: (word, 1))\
    #     .reduceByKey(lambda acc,cur:acc+cur)\
    #     .sortBy(lambda tup:tup[1])\
    #     .saveAsTextFile("hdfs://node1:8020/resultday02_1")
    # 释放资源
    sc.stop()

eg:deploy-mode client,测试比较方便,直接通过Driver看到最终返回的结果

[root@node1 bin]# ./spark-submit \
> --master yarn \
> --deploy-mode client \
> --conf "spark.pyspark.driver.python=/root/anaconda3/bin/python3" \
> --conf "spark.pyspark.python=/root/anaconda3/bin/python3" \
> /root/pybigdata/day02/pyspark_word_count.py
21/05/07 15:53:31 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
[('spark', 3), ('python', 1), ('mysql', 1), ('linux', 1), ('hive', 1), ('hadoop', 1), ('flink', 2)]

② Cluster模式: 【Driver运行在集群中某个Worker从节点中】

优势: 提升Driver程序和executor程序之间的传输效率,从而提升整体的运行效率
弊端: 由于Driver运行在集群里面,导致无法直接看到运行的结果,如果想要看到结果,就必须查看运行的日志
使用: 【推荐在生产环境使用】

eg:deploy-mode cluster,代码中读取的文件需上传到HDFS文件集群上,否则无法读取到文件
无法直接看到运行的结果,如果想要看到结果需查看运行的日志

[root@node1 bin]# ./spark-submit 
--master yarn\
--deploy-mode cluster\
--conf "spark.pyspark.driver.python=/root/anaconda3/bin/python3" \
--conf "spark.pyspark.python=/root/anaconda3/bin/python3" \
/root/pybigdata/day02/word_count.py
21/05/07 15:08:14 WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
[root@node1 bin]#

日志文件中查看结果:
在这里插入图片描述
异常记录:
ERROR ApplicationMaster: Uncaught exception: java.util.concurrent.TimeoutExc
原因:代码里设置的master与yarn提交冲突
解决方式:把本地测试配置master的local[ n ] 注掉

2 spark-submit命令

spark-submit是用于提交Spark应用程序的命令行工具。它将应用程序的代码和配置打包成一个JAR文件,并将其提交给Spark集群来执行。可以使用spark-submit --help查看帮助手册

[root@node1 bin]# ./spark-submit --help
Usage: spark-submit [options] <app jar | python file | R file> [app arguments]
Usage: spark-submit --kill [submission ID] --master [spark://...]
Usage: spark-submit --status [submission ID] --master [spark://...]
Usage: spark-submit run-example [options] example-class [example args]

Options:
  --master MASTER_URL         spark://host:port, mesos://host:port, yarn,
                              k8s://https://host:port, or local (Default: local[*]).
  --deploy-mode DEPLOY_MODE   是否在本地(“client”)或在集群(“cluster”)中的工作机器之一(默认:client)上启动驱动程序。
  --class CLASS_NAME          Your application's main class (for Java / Scala apps).
  --name NAME                 A name of your application.
  --jars JARS                 Comma-separated list of jars to include on the driver
                              and executor classpaths.
  --conf, -c PROP=VALUE       Arbitrary Spark configuration property.
  ...

3 PySpark程序与Spark交互流程!

3.1 client on Spark集群

1 在哪个节点上提交应用,就会在哪个节点上【启动Driver程序】
2 执行main函数,首先会创建SparkContext对象。底层是【基于PY4J】,根据python的构建方式,映射为Java代码
3 Driver程序连接到Spark集群的主节点Master。申请资源,用于启动Executor
4-Master主节点接收到资源申请,根据资源配置,分配资源。底层分配方案是【FIFO】。将分配方案返回给Driver程序
executor1:node1 2个cpu,2GB
executor2:node3 2个cpu,2GB
5 Driver程序连接到对应的Worker节点,占用相应资源,通知Worker启动Executor。启动以后,会反向注册回Driver程序
6 Driver开始处理代码
6.1 Driver加载RDD相关的算子(API/函数/方法),根据算子间的依赖关系,绘制【DAG有向无环图和划分Stage阶段】,并且确定每个阶段要多少个线程,每个线程要分配给哪个Executor来执行。(任务分配)
DAG有向无环图
6.2 Driver程序通知对应的Executor来执行具体任务
6.3 Executor接收到任务之后,开始执行。RDD的代码中,有大量的Python函数,Executor是JVM程序,无法执行Python函数。因此会【调用Python解释器】,得到返回结果,并且将结果返回给Executor
6.4 Executor在运行过程中,判断是否要将结果返回给Driver程序。如果需要,就直接返回;如果不需要,就直接输出,结束即可
6.5 Driver程序监控Executor的任务执行状态,如果所有的Executor都执行完成,那么就认为任务运行完成
7-任务运行完后,Driver执行【sc.stop()】代码,通知Master。Master释放资源,Driver程序退出

Client on Spark集权

3.2 cluster on Spark集群

区别点 : 【Driver程序就不是运行在提交程序的节点上了,而是在Spark集群中随机找一个从节点Worker启动Driver程序】

1-程序提交到Spark集群的主节点Master
2-主节点Master接收到任务之后,会根据Driver资源配置,在集群中随机选择某个从节点,占用相应资源,启动Driver程序
3-Driver程序执行main函数,首先会创建SparkContext对象。底层是基于PY4J,根据python的构建方式,映射为Java代码。创建成功后,会注册到Master主节点
4-Driver程序连接到Spark集群的主节点。申请资源,用于启动Executor
5-Master主节点接收到资源申请,根据资源配置,分配资源。底层分配方案是FIFO先进先出。将分配方案返回给Driver程序
executor1:node1 2个cpu,2GB
executor2:node3 2个cpu,2GB
6-Driver程序连接到对应的Worker节点,占用相应资源,通知Worker启动Executor。启动以后,会反向注册回Driver程序
7-Driver开始处理代码
7-1 Driver加载RDD相关的算子(API/函数),根据算子间的依赖关系,绘制DAG执行流程图和划分Stage阶段,并且确定每个阶段要多少个线程,每个线程要分配给哪个Executor来执行。(任务分配)
7-2 Driver程序通知对应的Executor来执行具体任务
7-3 Executor接收到任务之后,开始执行。RDD的代码中,有大量的Python函数,Executor是JVM程序,无法执行Python函数。因此会【调用Python解释器】,得到返回结果,并且将结果返回给Executor
7-4 Executor在运行过程中,判断是否要将结果返回给Driver程序。如果需要,就直接返回;如果不需要,就直接输出,结束即可
7-5 Driver程序监控Executor的任务执行状态,如果所有的Executor都执行完成,那么就认为任务运行完成
8-任务运行完后,Driver执行sc.stop()代码,通知Master。Master释放资源,Driver程序退出

3.3 client on Yarn集群

区别点: 【Driver将资源申请的工作移交给了ApplicationMaster来负责,Driver只负责任务分配、任务管理等工作】

1- 首先会在提交的的节点上启动一个Driver程序
2- Driver启动后,执行mian函数,首先创建SparkContext对象(底层是基于PY4J,识别python的构建方式, 将其映射为Java代码)
3- 连接Yarn集群的主节点ResourceManager,将需要申请的资源封装成一个任务,提交给Yarn的主节点ResourceManager。主节点ResourceManager收到任务后,首先随机的选择一个nodemanager启动ApplicationMaster
4- 当ApplicationMaster启动后,会和yarn的主节点建立心跳机制,告知已经启动成功了。启动成功后,就进行资源的申请工作,将需要申请的资源通过心跳包的形式发送给ResourceManager。ResourceManager收到资源申请后,开始进行资源分配工作,底层是基于资源调度器来实现(默认为Capacity容量调度)。当ResourceManager将资源分配OK后,等待ApplicationMaster进拉取操作。ApplicationMaster会定时的通过心跳的方式询问ResourceManager是否已经准备好资源,一旦发现准备好,立即拉取对应资源信息
5- ApplicationMaster根据获取到的资源信息连接对应的节点,通知对应nodemanager启动executor,并占用相应资源,nodemanage对应executor启动完成后,反向注册回给Driver程序(已经启动完成)
6- Driver开始处理代码:
6.1 首先会加载所有的RDD相关的API(算子),基于算子之间的依赖关系,形成DAG执行流程图,划分stage阶段,并且确定每个阶段应该运行多少个线程以及每个线程应该交给哪个executor来运行(任务分配)
6.2 Driver程序通知对应的executor程序,来执行具体的任务
6.3 Executor接收到任务信息后,启动线程,开始执行处理即可。executor在执行的时候,由于RDD代码中有大量的Python的函数,Executor是一个JVM程序,无法解析Python函数,此时会调用Python解析器,执行函数, 并将函数结果返回给Executor
6.4 Executor在运行过程中,如果发现最终的结果需要返回给Driver,直接返回Driver,如果不需要返回, 直接输出 结束即可
6.5 Driver程序监听这个executor执行的状态信息,当Executor都执行完成后,Driver认为任务运行完成了,同时ApplicationMaster也会接收到各个节点执行完成状态,然后通知ResourceManager。任务执行完成,ResourceManager回收资源,关闭ApplicationMaster,并通知Driver程序
7- Driver执行sc.stop(),Driver程序退出即可

3.4 cluster on Yarn集群

区别点: 【Driver和ApplicationMaster合二为一,ApplicationMaster就是Driver,Driver就是ApplicationMaster】

1- 首先会将任务提交给Yarn集群的主节点(ResourceManager)
2- ResourceManager接收到任务信息后,根据Driver(ApplicationMaster)的资源配置信息要求,选择一个nodeManager节点(有资源的,如果都有随机)来启动Driver(ApplicationMaster)程序,并且占用相对应资源
3- Driver(ApplicationMaster)启动后,执行main函数。首先创建SparkContext对象(底层是基于PY4J,识别python的构建方式,将其映射为Java代码)。创建成功后,会向ResourceManager进行建立心跳机制,告知已经启动成功了
4- 根据executor的资源配置要求,向ResourceManager通过心跳的方式申请资源,用于启动executor(提交的任务的时候,可以自定义资源信息)
5- ResourceManager接收到资源申请后,根据申请要求,进行分配资源。底层是基于资源调度器来资源分配(默认为Capacity容量调度)。然后将分配好的资源准备好,等待Driver(ApplicationMaster)拉取操作
executor1: node1 2个CPU 2GB内存
executor2: node3 2个CPU 2GB内存
6- Driver(ApplicationMaster)会定时询问是否准备好资源,一旦准备好,立即获取。根据资源信息连接对应的节点,通知nodeManager启动executor,并占用相应资源。nodeManager对应的executor启动完成后,反向注册回给Driver(ApplicationMaster)程序(已经启动完成)
7- Driver(ApplicationMaster)开始处理代码:
7.1 首先会加载所有的RDD相关的API(算子),基于算子之间的依赖关系,形成DAG执行流程图,划分stage阶段,并且确定每个阶段应该运行多少个线程以及每个线程应该交给哪个executor来运行(任务分配)
7.2 Driver(ApplicationMaster)程序通知对应的executor程序, 来执行具体的任务
7.3 Executor接收到任务信息后, 启动线程, 开始执行处理即可: executor在执行的时候, 由于RDD代码中有大量的Python的函数,Executor是一个JVM程序 ,无法解析Python函数, 此时会调用Python解析器,执行函数, 并将函数结果返回给Executor
7.4 Executor在运行过程中,如果发现最终的结果需要返回给Driver(ApplicationMaster),直接返回Driver(ApplicationMaster),如果不需要返回,直接输出 结束即可
7.5 Driver(ApplicationMaster)程序监听这个executor执行的状态信息,当Executor都执行完成后,Driver(ApplicationMaster)认为任务运行完成了
8- 当任务执行完成后,Driver执行sc.stop()通知ResourceManager执行完成,ResourceManager回收资源,Driver程序退出即可

Cluster on YARN集群

4 RDD的基本介绍!

4.1 什么是RDD

RDD:英文全称Resilient Distributed Dataset,叫做弹性分布式数据集,是Spark中最基本的数据抽象,代表一个不可变、可分区(保证负载均衡,提高效率)、里面的元素可并行计算的集合。

  • Resilient弹性:【RDD的数据可以保存到内存或者磁盘】
  • Distributed分布式:【RDD的数据可以分布式存储在集群中的节点,用于分布式计算】
  • Dataset数据集:【一个用于存放数据的集合】

4.2 RDD的五大特性

官方:

1 A list of partitions
2 A function for computing each split
3 A list of dependencies on other RDDs
4 Optionally, a Partitioner for key-value RDDs (e.g. to say that the RDD is hash-partitioned)
5 Optionally, a list of preferred locations to compute each split on (e.g. block locations foran HDFS file)

直译:

1、(必须的)RDD是有一系列分区组成的
2、(必须的)对RDD做计算,相当于对RDD的每个split或分区做计算
3、(必须的)RDD之间存在着依赖关系,宽依赖和窄依赖
4、(可选的)对于KV类型的RDD,我们可以进行自定义分区方案
5、(可选的)移动数据不如移动计算,让计算程序离数据越近越好

4.3 RDD的五大特点

1、分区:RDD逻辑上是分区的,仅仅是定义分区的规则,并不是直接对数据进行分区操作,因为RDD本身不存储数据。
2、只读:RDD是只读的,要想改变RDD中的数据,只能在现有的RDD基础上创建新的RDD。
3、依赖:RDD之间存在着依赖关系,宽依赖和窄依赖
4、缓存:如果在应用程序中多次使用同一个RDD,可以将该RDD缓存起来cache,该RDD只有在第一次计算的时候会根据血缘关系得到分区的数据
5、checkpoint:与缓存类似的,都是可以将中间某一个RDD的结果保存起来,只不过checkpoint支持持久化保存

5 如何构建RDD!

1、通过 textFile(data): 通过读取外部文件的方式来初始化RDD对象,经常使用。
2、通过 parallelize(data): 通过自定义列表的方式初始化RDD对象。(一般用于测试)

5.1 并行化本地集合方式

相关的API
parallelize(参数1,参数2):使用本地数据构建RDD。
参数1:本地数据列表;参数2:可选的,表示有多少个分区
getNumPartitions:查看RDD的分区数量
glom:查看每个分区的数据内容

if __name__ == '__main__':
    # 1、创建spark运行环境
    sparkConf =SparkConf().setAppName('xxx').setMaster('local[*]')
    sc =SparkContext(conf=sparkConf)
    # 2、创建RDD
    rdd=sc.parallelize([0,1,2,3,4,5,6,7,8,9],numSlices=5)
    # 3. 查看分区个数
    print(rdd.getNumPartitions())    # 5
    # 4.查看每个分区具体内容
    print(rdd.glom().collect())      # [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]
    # 常看rdd内容
    print(rdd.collect())             # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

通过本地列表的方式构建RDD, 其对应分区数量如何确定的:

1- 默认与setMaster设置的线程数量一致: 比如local[3],表示默认有3个分区。但是如果集群运行模式,默认为2个分区
2- local[*]: * 对应的数值是与当前节点的CPU核数保持一致
3- 如果想要改变RDD的分区数量
方式一: 直接修改setMaster中local的N即可。但是仅限于本地模式
方式二: 可以修改parallelize的参数2的位置。设置多少,那么就有多少个分区。优先级比setMaster中的高

到底有多少个分区,以getNumPartitions结果为准
1- 分区数量与setMaster关系不大。但是当设置为1的时候有用,往大设置没用
2- 可以在textFile中设置最小分区数量,但是实际的分区数量可能会 >= 设置最小分区数量
3- 默认情况下,通过wholeTextFiles读取多个文件的时候,有多少个文件,一般会产生多少个分区

5.2 读取外部数据源方式

通过读取外部数据源的方式构建RDD, 其对应分区数量如何确定的:
到底有多少个分区,以getNumPartitions结果为准
1- 分区数量与setMaster关系不大。但是当设置为1的时候有用,往大设置没用
2- 可以在textFile中设置最小分区数量,但是实际的分区数量可能会 >= 设置最小分区数量
3- 默认情况下,通过wholeTextFiles读取多个文件的时候,有多少个文件,一般会产生多少个分区

5.3 RDD分区数量如何确定

RDD分区数量如何确定

1- RDD分区数量(线程数量)一般设置为【CPU核数的2-3倍】
2- RDD的分区数量取决于多个因素: 调用任务时候设置CPU核数、对应API设置分区数量,以及本身读取文件的分区数等等因素
3- 当初始化SparkContext的时候, 其实我们确定了一个基本的并行度参数【spark.default.parallelism】,默认为CPU核数。如果是集群,至少为2;如果local模式,取决于local[N] N为多少,并行度就是多少,即分区数就是多少
4- 通过parallelize来构建RDD: 如果没有指定分区数,那么分区数量取决spark.default.parallelism;如果设置了分区数量,取决于设置的分区数
5- 通过textFile(path,minPartition)来构建RDD:
5-1 首先取决于defaultMinPartition的值。如果没有指定minPartition,defaultMinPartition等于min(spark.default.parallelism,2);如果指定了minPartition,defaultMinPartition等于minPartition
5-2 读取本地文件: 【RDD分区数=max(本地文件分片数,defaultMinPartition)】
5-3 读取HDFS文件: 【RDD分区数=max(文件的block块,defaultMinPartition)】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值