Spark调度与程序执行

本文深入探讨了Spark与MapReduce的区别,强调Spark在内存计算和任务调度上的优势。介绍了Spark的基本概念,如RDD的特性、转换与触发算子,并详细阐述了Spark应用的提交执行流程。同时,文章详细分析了Spark的任务调度、RDD的依赖关系以及shuffle过程,展示了Spark在大数据处理中的高效性能。
摘要由CSDN通过智能技术生成


前言

Hadoop丰富了大数据处理思想,将分而治之形式散布出去,但由于MR繁琐的shuffle过程、频繁的I/O操作,导致计算过程特别慢。基于此,hive实现sql编写代替mr程序编写,presto实现中间结果存储内存中,spark则全都具备,且将task任务在线程中处理。


一、Spark概念

1.MapReduce框架

分布式并行计算框架,处理数据,思想:分而治之(先分后合)
map:分
将处理大规模数据划分为一部分一部分数据,每部分数据启动任务MapTask处理
10GB -> 100 份 -> 100 MapTask 处理

reduce:合
合并所有map处理结果,合并数据启动任务:ReduceTask,可以有多个合并任务

2.Spark框架

Apache 顶级项目,类似Hadoop、Hive、Zookeeper
http://spark.apache.org

官方定义:Unified engine for large-scale data analytics

第1点:类似MapReduce大数据计算引擎,处理计算数据分布式计算引擎
第2点:统一分析引擎,表示Spark 框架可以针对任意类型数据分析需求进行,如:
离线分析
实时计算
图形计算,比如A地方 -> B 地方,最短最优路径,类似高德导航
机器学习,比如推荐系统
科学计算,比如Pandas,R语言
第3点:处理大规模数据,也可以分析小数据(比如pandas分析数据,mysql数据库分析数据)
第4点:并行计算引擎,也可以单机分析数据
RDD:封装数据结构,将其当做集合,类似Python中列表list或字典dic,处理HDFS上数据,首先将HDFS上数据封装到RDD集合中,默认情况下:
1个block对应1partition
logs.data: 100 block -> RDD: 100 个partition -> 100 Task任务
在这里插入图片描述

3.两者之间的比较

1、计算的中间结果,MR写入磁盘,spark储存在内存中。
2、job的调度方式,spark以DAG图进行job的调度,每个job有多个stage,每个stage有多个task,task是以线程执行,省略了进程的申请和销毁过程。
3、mr直接读取文件,spark将文件封装成结构化数据集RDD形式,便于计算。

比较方面MapRedue 计算引擎Spark 计算引擎
1、Job 程序结构1 个Map Stage + 1个 Reduce Stage构架DAG图,多个Stage
多个Map Stage + 多个Redue Stage
2、中间结果存储本地磁盘Disk没有Shuffle时,存储内存Memory
3、Task 运行方式进程Process:MapTask 和Reduce Task线程Thread:Task,无需频繁启动和销毁
4、程序编程模型直接读取文件数据,map + reduce文件数据封装:RDD,调用函数处理

4.spark应用执行组成

包含两部分
1、driver program:申请资源运行executor调度job执行,相当于yarn中的appMaster,运行jvm process,运行程序的main函数,必须创建sparkcontext对象
2、executor:缓存RDD数据,执行task任务,相当于线程池,运行jvm process每个任务对应一个线程,最少需要1core,线程数等于cpu core数
在这里插入图片描述

5、spark standalone集群

类似于YARN集群,分布式有两个节点
1、master,集群的资源管理和调度,类似于RM
2、workers,集群的真正资源,类似于NM
在这里插入图片描述
整个集群存在主节点的单点故障,可利用zookeeper的注册机制实现高可用。

二、词频统计与spark应用运行

1.Hive实现词频统计

1、加载文件数据到数据库

LOAD DATA LOCAL INPATH '/root/words.txt' INTO TABLE db_test.tbl_lines;

2、格式化字段

select split(line,' ') from tbl_lines;

3、表生成函数,单行转多行

select explode(spliit(line,' ')) from tb_lines;

4、子查询、group by 、order by,完整sql

select word,count(word)as num from (select explode(split(line,' '))as word from tbl_lines)t group by word order by num desc;

2.spark实现词频统计

1、构建context对象

sc = Sparkcontext(conf=conf)

2、加载数据源

sc.textFiile('../datas/words.txt')

3、数据格式化、转化成元组、根据key聚合

output_rdd = input_rdd \
        .flatMap(lambda line: str(line).split(' ')) \
        .map(lambda word: (word, 1)) \
        .reduceByKey(lambda tmp, item: tmp + item)

spark RDD模式,也可以使用spark sql模式
1、创建context对象
2、注册临时视图
3、编写sql

createOrReplaceTempView('view_tmp_lines')

spark.sql(
WITH tmp AS (
          select explode(split(value, ' ')) AS word from view_tmp_lines
        )
        SELECT word, COUNT(1) AS total FROM tmp GROUP BY word ORDER BY total DESC LIMIT 10
)

在spark sql使用过程中最主要的是dataframe的构建,有三种方式,后续介绍。dataframe类似于pandas中的dataframe。

3.spark-submit提交应用执行

在这里插入图片描述

spark-submit提交应用执行主要有三种参数:
1、基本参数
–master 【】 此参数代表程序运行在哪里,包括本地:local[N] 集群:standalone yarn mesos
–driver-mode 此参数代表driver program 运行在哪里,默认client,包括提交应用的客户端:client 集群的从节点:cluster
–conf 【】 此参数可设置其他可选项,如task任务数

2、Driver Program 参数
–driver-memory 512M 此参数设置driver启动内存,默认1G
–driver-cores 1 此参数设置运行核数
–supervise 此参数设置运行在standalone中的driver是否自动失败重启

3、Executor 参数配置
–executor-memory 512M 此参数设置启动executor线程池内存大小
–executor-cores 此参数设置启动executor线程池的核数默认1
–num-executors 此参数设置启动的executor个数,默认2
–queue 此参数设置executor队列,在standalone集群中,默认调度策略为先进先出
在这里插入图片描述
具体参数设置,参考官方文档

4.spark-submit提交应用运行模式

–deploymode 参数最大的设置就是driver programe运行在哪里,是在client即提交客户端上,还是cluster集群从节点中。这里又分为standalone集群和yarn集群,通常都是部署在yarn集群上,一方面是yarn集群更好的契合hadoop资源和HDFS,另一方面是yarn集群的资源调度策略更为成熟。
在这里插入图片描述

当运行在standalone上时,driver program 运行在worker节点中完成job调度和executor启动、注册,当运行在yarn集群中,driver program与appMaster合为一,完成任务的调度和资源申请。
在这里插入图片描述

5.Spark程序运行YARN集群流程

yarn-client:
1、Driver在任务提交的本地机器上运行,Driver启动后会和ResourceManager通讯申请启动ApplicationMaster;
2、随后ResourceManager分配Container,在合适的NodeManager上启动ApplicationMaster,此时的ApplicationMaster的功能相当于一个ExecutorLaucher,只负责向ResourceManager申请Executor内存;
3、ResourceManager接到ApplicationMaster的资源申请后会分配Container,然后ApplicationMaster在资源分配指定的NodeManager上启动Executor进程;
4、Executor进程启动后会向Driver反向注册,Executor全部注册完成后,Driver开始执行main函数;
5、之后执行到Action算子时,触发一个Job,并根据宽依赖开始划Stage,每个Stage生成对应的TaskSet,之后将Task分发到各个Executor上执行。
在这里插入图片描述
yarn-cluster:
1、任务提交后会和ResourceManager通讯申请启动ApplicationMaster;
2、随后ResourceManager分配Container,在合适的NodeManager上启动ApplicationMaster,此时的ApplicationMaster就是Driver;
3、Driver启动后向ResourceManager申请Executor内存,ResourceManager接到ApplicationMaster的资源申请后会分配Container,然后在合适的NodeManager上启动Executor进程;
4、Executor进程启动后会向Driver反向注册;
5、Executor全部注册完成后Driver开始执行main函数,之后执行到Action算子时,触发一个job,并根据宽依赖开始划分stage,每个stage生成对应的taskSet,之后将task分发到各个Executor上执行

三、RDD算子

1、概念和特性

RDD:弹性分布式数据集,数据的抽象封装,可调用转换算子和触发算子进行数据处理。
主要特质:
不可变:immutable,相当于元组
分区的:partitioned,大数据分而治之思想,类似数据逻辑切片
并行计算:parallel,并行计算

主要特性:
1、a list of partitions
数据为分区形式的列表
2、A function for computing each split
分区计算,分而治之
3、A list of dependencies on other RDDs
RDD之间具有依赖性
4、Optionally, a Partitioner for key-value RDDs
可选,key/value形式分区计算
5、Optionally, a list of preferred locations to compute each split
可选,程序移动,数据不动

2、RDD创建方式

1、并行化集合
2、读取储存系统文件

    # 创建SparkConf实例,设置应用属性,比如名称和master
    spark_conf = SparkConf().setAppName("SparkContext Test").setMaster("local[2]")

    # 创建SparkContext对象,传递SparkConf实例
    sc = SparkContext(conf=spark_conf)
    # 读取数据源
    # 1、并行化集合,设置分区数
    input_rdd = sc.parallel([1,2,3,4,5,6],numSlices=2)
    # 2、读取存储系统数据,设置分区
    input_rdd_text = sc.textFile('../datas/words.txt', minPartitions=2)

3、小文件处理
①、在文件系统中合并小文件
②、利用wholeTextFiles方法加载小文件

input_rdd = sc.wholeTextFiles('../datas/rating', minPartitions=2)

3、RDD常用算子

算子即封装的特定方法,RDD算子主要分为两类,一种是转换算子,其返回值是一个RDD;一种是触发算子,其返回值,触发job执行。
常用的算子有:
在这里插入图片描述
1、常用转换算子:map、filter、flatMap

	# TODO: map转化算子操作
	result_map = map_rdd.map(lambda items: items * items)
	
	# TODO: filter 算子,对集合中每条数据进行过滤,返回值为true保存,否则删除
    result_filter = map_rdd.filter(lambda a: a % 2 != 0)
    
	# TODO: flatMap 算子,对集合中每条数据进行处理,类似map算子,但是要求处理每条数据结果为集合,将自动将集合数据扁平化(explode)
    rdd = sc.parallelize(['爱情 / 犯罪', '剧情 / 灾难 / 动作'])
    result_flatmap = rdd.flatMap(lambda a: str(a).split(' / '))

2、常用触发算子:count、foreach、saveAsTextFile

	# TODO: count算子统计集合中元素的个数
    print(input_rdd.count())
    
	# TODO: foreach 算子,遍历集合中每个元素,进行输出操作
    input_rdd.foreach(lambda a: print(a))
    
	# TODO: saveAsTextFiles 算子,将集合数据保存到文本文件,一个分区数据保存一个文件
    input_rdd.saveAsTextFile('../datas/output')

3、基本转换算子:union、distinct、groupByKey、reduceByKey

	# TODO: union算子,合并两个rdd,相当于union all
    rdd_union = rdd1.union(rdd2)
    
	# TODO: distinct对rdd进行去重
    rdd_distinct = rdd_union.distinct()
    
	# TODO: groupByKey对rdd进行分组,相同key的value放在list
    rdd_groupByKey = rdd3.groupByKey()
    print(rdd_groupByKey.collect())
    rdd_groupByKey.foreach(lambda tuple: print(tuple[0], list(tuple[1])))

	# TODO: reduceByKey算子,将集合中数据,先按照Key分组,再使用定义reduce函数进行组内聚合
    rdd_reduceByKey = rdd3.reduceByKey(lambda tmp, a: tmp + a)
    print(rdd_reduceByKey.collect())
    rdd_reduceByKey.foreach(lambda a: print(a))

4、基本触发算子:first、take、collect、reduce

	# TODO: first算子获取集合中第一个元素
    print(input_rdd.first())

    # TODO: take获取集合中前几个元素,返回一个列表
    print(input_rdd.take(4))

    # TODO: collect获取集合中所有的元素,存储在内存中数据量大时不建议用
    print(input_rdd.collect())

    # TODO: reduce对集合中的元素进行聚合操作
    print(input_rdd.reduce(lambda a, b: a + b))

5、数据排序算子:sortBy、sortByKey、top、takeOrdered

	input_rdd = sc.parallelize(
        [("spark", 10), ("mapreduce", 3), ("hive", 6), ("flink", 4), ("python", 5)],
        numSlices=2
    )
    
    # TODO: sortBy 分区排序
    rdd1 = input_rdd.sortBy(keyfunc=(lambda a: a[1]), ascending=False)
    rdd1.foreach(lambda a: print(a))

    # TODO: sortByKey 分区排序,对象为dict形式
    rdd2 = input_rdd.map(lambda a: (a[1], a[0])).sortByKey(ascending=False)
    rdd2.foreach(lambda a: print(a))

    # TODO: top 获得集合中最大的前N个数据,触发算子返回的是list
    rdd3 = input_rdd.top(3)
    print(rdd3)
    for i in rdd3:
        print(i)

    # TODO: takeOrdered 获得集合中最小的前N个数据,触发算子返回的是list
    rdd4 = input_rdd.takeOrdered(4)
    print(rdd4)
    for i in rdd4:
        print(i)

6、调整分区算子:repartition、coalesce

	# TODO: 加载数据源
    input_rdd = sc.textFile('../datas/words.txt', minPartitions=2)
    print(input_rdd.getNumPartitions())

    # TODO: repartition 增加partition会产生shuffle
    rdd1 = input_rdd.repartition(4)
    print(rdd1.getNumPartitions())

    # TODO: coalesce 减少partition 不会产生shuffle
    rdd2 = input_rdd.coalesce(1)
    print(rdd2.getNumPartitions())

4、RDD其他算子

RDD算子运算除了常用的算子还有些针对特定处理的算子,包括聚合、join、优化等等。
1、数据聚合算子:reduce、fold、aggregate
分布式聚合的思想是先局部集合,再全局聚合。三者之间的区别是起始值的设置,fold和aggregate可设置起始值,并且aggregate可进一步设置全局聚合函数

    # TODO: 加载并行化数据源
    input_rdd = sc.parallelize([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], numSlices=2)

    # TODO: reduce算子
    rd_rdd = input_rdd.reduce(lambda tmp, a: tmp + a)

    # TODO: fold算子计算
    fd_rdd = input_rdd.fold(0, lambda tmp, a: tmp + a)

    # TODO: aggregate算子计算
    ag_rdd = input_rdd.aggregate(0, lambda tmp, a: tmp + a, lambda tmp, a: tmp + a)
    ag_rdd2 = input_rdd.aggregate(0, lambda tmp, a: tmp + a, lambda tmp, a: tmp * a)

2、Key/Value类型算子:keys/values、mapValues、collectAsMap
相当于字典方法

    input_rdd = sc.parallelize(
        [("spark", 10), ("mapreduce", 3), ("hive", 6), ("flink", 4)],
        numSlices=2
    )

    # TODO: keys/values 算子使用
    input_rdd.keys().foreach(lambda a: print(a))
    input_rdd.values().foreach(lambda a: print(a))

    # TODO: mapValues 算子使用,对values值进行计算
    input_rdd.mapValues(lambda a: a * a).foreach(lambda a: print(a))

    # TODO: collectAsMap 将rdd转化成字典
    print(input_rdd.collectAsMap())

3、join关联算子,join、left join、right join、 full outer join
类似sql中的join

    emp_rdd = sc.parallelize(
        [(1001, "zhangsan"), (1002, "lisi"), (1003, "wangwu"), (1004, "zhaoliu")]
    )
    dept_rdd = sc.parallelize(
        [(1001, "sales"), (1002, "tech")]
    )
    # (1001, ('zhangsan', 'sales'))
    # (1002, ('lisi', 'tech'))

    # TODO:关联算子
    result_rdd = emp_rdd.join(dept_rdd)
    result_rdd.foreach(lambda a: print(a))

4、分区处理算子
针对分区数据与外部建立对象连接时,将整个分区创建一个对象,减少对象的创建和销毁过程

    input_rdd = sc.parallelize(list(range(1, 11)), numSlices=2)

    # TODO: map 算子处理rdd中的元素
    result_rdd = input_rdd.map(lambda a: a * a)


    # TODO: mapPartitions 算子处理分区迭代对象,返回迭代对象
    def mp_func(iter):
        for item in iter:
            yield item * item


    mp_rdd = input_rdd.mapPartitions(mp_func)


    # TODO: foreachPartition 算子,表示对RDD集合中每个分区数据输出
    def fp_func(iter):
        for item in iter:
            print(item)


    input_rdd.foreachPartition(fp_func)

5、jieba分词模块
根据特定的方法将字符串切割

    # 定义一个字符串
    line = '我来到北京清华大学'

    # TODO:全模式分词
    seg_list = jieba.cut(line, cut_all=True)
    print(",".join(seg_list))
    # 我,来到,北京,清华,清华大学,华大,大学

    # TODO: 精确模式
    seg_list_2 = jieba.cut(line, cut_all=False)
    print(",".join(seg_list_2))
    # 我,来到,北京,清华大学

    # TODO: 搜索引擎模式
    seg_list_3 = jieba.cut_for_search(line)
    print(",".join(seg_list_3))
    # 我,来到,北京,清华,华大,大学,清华大学

4、Spark高级特性

1、RDD持久化
某些RDD的计算或转换可能会比较耗费时间,如果这些RDD后续还会频繁的被使用到,可以将这些RDD进行持久化/缓存,这样下次再使用到的时候就不用再重新计算了,提高了程序运行的效率。
但由于内存的大小是有限的,所以应选择合适的缓存级别,常用MEMORY_AND_DISK 和MEMORY_AND_DISK2,和转换算子一样时lazy操作需要action算子触发,常用count()
缓存函数:
cache、persist

使用情景:某个RDD被多次使用、某个RDD计算困难

    input_rdd = sc.textFile('../datas/words.txt', minPartitions=2)

    # TODO: 可以直接将数据放到内存中
    # input_rdd.cache()
    # input_rdd.persist()

    # TODO: 默认缓存级别MEMORY_ONLY
    input_rdd.cache()

    # TODO: 缓存RDD数据,设置缓存级别,先放内存不足放磁盘
    input_rdd.persist(storageLevel=StorageLevel.MEMORY_AND_DISK)
    # 使用count()算子触发
    input_rdd.count()

    # 当再次使用缓存数据时,直接从缓存读取
    print(input_rdd.count())

    # TODO: 当缓存RDD数据不在被使用时,一定记住需要释放资源
    input_rdd.unpersist()

2、RDD checkpoint
将RDD保存至可靠储存系统如:HDFS,与持久化的区别时程序结束时不丢失;不保存RDD之间依赖关系
在这里插入图片描述

	# TODO: step1、设置Checkpoint保存目录
    sc.setCheckpointDir('../datas/ckpt')

    input_rdd = sc.textFile('../datas/words.txt', minPartitions=2)

    # TODO: step2、将RDD进行Checkpoint
    input_rdd.checkpoint()
    input_rdd.count()

    # TODO: 当RDD进行Checkpoint以后,再次使用RDD数据时,直接从Checkpoint读取数据
    print(input_rdd.count())

3、Spark 累加器
spark提供两种共享变量,广播变量和累加器
1)、广播变量Broadcast Variables:将变量在所有节点的内存之间进行共享,在每个机器上缓存一个只读的变量,而不是为机器上每个任务都生成一个副本。
2)累加器 Accamulators:支持在所有不同节点之间进行累加计算(比如技术或者求和)
Spark内置三种类型Accumulator:LongAccumulator累加整数型,DoubleAccumulator累加浮点型,CollectionAccumulator累加集合元素,SparkCore提供接口,允许用户自定义累加器Accamulator

    # TODO: 第1步、定义累加器
    counter = sc.accumulator(0)

    # 2. 加载数据源-source
    input_rdd = sc.textFile('../datas/words.txt', minPartitions=2)

    # 3. 数据转换处理-transformation
    def map_func(line):
        # TODO:第2步、使用累加器
        counter.add(1)
        return line

    output_rdd = input_rdd.map(map_func)

    # 4. 处理结果输出-sink
    print(output_rdd.count())

    # TODO: 第3步、获取累加器值
    print("counter =", counter.value)

4、广播变量
广播变量(Broadcast Variables)允许开发人员在每个节点(Worker or Executor)缓存只读变量,而不是在Task之间传递这些变量

    # 字典信息
    dic = {1: "北京", 2: "上海", 3: "深圳"}
    # TODO:step1、将小表数据(变量)进行广播
    broadcast_dic = sc.broadcast(dic)

    # 2. 加载数据源-source
    input_rdd = sc.parallelize([
        ("张三", 1), ("李四", 2), ("王五", 3), ("赵六", 1), ("田七", 2)
    ])


    # 3. 数据转换处理-transformation
    def map_func(tuple):
        # TODO: step2、使用广播变量
        city_dic = broadcast_dic.value
        # 依据城市ID获取城市名称
        city_id = tuple[1]
        city_name = city_dic[city_id]
        # 返回
        return (tuple[0], city_name)


    output_rdd = input_rdd.map(map_func)

四、Spark内核调度

1、spark任务调度

在这里插入图片描述

1、任务提交后会和ResourceManager通讯申请启动ApplicationMaster;
2、随后ResourceManager分配Container,在合适的NodeManager上启动ApplicationMaster,此时的ApplicationMaster就是Driver;
3、Driver启动后向ResourceManager申请Executor内存,ResourceManager接到ApplicationMaster的资源申请后会分配Container,然后在合适的NodeManager上启动Executor进程;
4、Executor进程启动后会向Driver反向注册;
5、Executor全部注册完成后Driver开始执行main函数,之后执行到Action算子时,触发一个job
6、(DAGScheduler)首先构建Job中RDD依赖对应DAG图,划分为Stage阶段:利用回溯法,RDD之间的依赖类型为宽依赖进行划分(Shuffle依赖)划分
7、(TaskScheduler)确定每个Stage阶段的任务数,依次调度到executor中执行,每个Stage中的Task数目=Stage中最后一个RDD的分区数目,shuffle阶段根据stage调度进行shuffle write和shuffleread。一个job中有多个stage,按照向后顺序执行stage中的task任务,只有前面的stage的task任务完成,才能执行后面的stage中task任务

2、spark RDD依赖

宽依赖:
1、shuffle依赖,父RDD一个分区数据给子RDD多个分区
2、产生shuffle,类似MR shuffle,如果子RDD的某个分区数据丢失,必须重构父RDD的所有分区
3、划分DAG图stage的依据
4、2个RDD之间依赖,使用S曲线有向箭头表示

窄依赖:
1、一个父RDD分区数据直给子RDD一个分区
2、都是转换算子,lazy执行
3、不产生Shuffle,如果子RDD的某个分区数据丢失,重构父RDD的对应分区
4、两个RDD之间用有向箭头表示

产生原因:
1、从数据血脉恢复角度来说:
如果宽依赖:
子RDD某个分区的数据丢失,必须重新计算整个父RDD的所有分区
如果窄依赖:
子RDD某个分区的数据丢失,只需要计算父RDD对应分区的数据即可
2、从性能的角度来考虑:
需要经过shuffle:使用宽依赖
不需要经过shuffle:使用窄依赖

在这里插入图片描述

3、spark shuffle

Shuffle过程:

  • Shuffle Write:

    • Shuffle 的前半部分输出叫做 Shuffle Write,类似MapReduce Shuffle中Map Shuffle;
    • 将处理数据先写入内存,后写入磁盘。
  • Shuffle Read:

    • Shuffle 的后半部分输出叫做 Shuffle Read,类似MapReduce Shuffle中Reduce Shuffle;
    • 拉取ShuffleWrite写入磁盘的数据,进行处理
      在这里插入图片描述

Shuffle 机制:

总结

spark核心与hadoop中的MR类似,又因为解决MR慢的问题使用了内存储存中间结果、线程并行运算,所以在程序调度过程与MR产生了区别,同时,弹性分布式数据集和dataframe的使用,极大降低了数据分析的开发过程。
时光如水,人生逆旅矣。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值