Spark Core进阶知识

小知识:

大数据开发人员/数据分析人员,必须要对自己统计的指标结果负责!!!

结果数据的核对方式:

1- 在离线文件中直接ctrl+F搜索关键内容核对(不常用)

2- 一般原始数据会存放在MySQL/Hive中一份,可以编写和代码逻辑完全一样的SQL来进行核对,可以通过如下方式来提高核对效率(常用):

2.1- 如果是分区表,挑选几个分区进行核对即可

2.2- 可以在SQL的where语句中,添加数据过滤条件,例如:时间范围过滤条件,用户编号过滤条件等,将核对的数据量缩小,提高核对效率.

3- 可以对重点指标的同化,环比进行监控,还可以结合算法的方式对指标统计结果进行检测(不常用).

一.RDD的持久化

 

1.RDD的缓存

当RDD被重复使用,或者计算该RDD比较容易出错,而且需要消耗比较多的资源和时间的时候,我们就可以将该RDD缓存起来.

主要作用:提升Spark程序的计算效率

注意事项:RDD的缓存可以存储在内存或者是磁盘上,甚至可以存储在Executor进程的堆外内存中.主要是放在内存中,因此缓存的数据是不太稳定可靠.

由于是临时存储,可能会存在丢失,所以缓存操作,并不会将RDD之间的依赖关系给截断掉(丢失),因为当缓存失效后,可以全部重新计算.

缓存的API都是Lazy惰性的,如果需要触发缓存操作,推荐调用count算子,因为运行效率高

缓存相关API介绍:

 设置缓存的API:

rdd.cache():将RDD的数据缓存储内存中

rdd.persist(缓存的级别/位置):将RDD的数据存储在指定位置

手动清理缓存API:

rdd.unpersist()

默认情况下,当整个Spark应用程序执行完成后,缓存数据会自动失效,会被自动删除

缓存的级别/位置:

DISK_ONLY:只存储在磁盘

DISK_ONLY_2:只存储在磁盘,并且有2个副本

DISK_ONLY_3:只存储在磁盘,并且有3个副本

MEMORY_ONLY:只存储在内存中

MEMORY_ONLY_2:只存储在内存中,并且有2个副本

MEMORY_AND_DISK:存储在内存和磁盘中,先放在内存,再放在磁盘

MEMORY_AND_DISK_2:存储在内存和磁盘中,先放在内存,再放在磁盘,并且有2个副本

OFF_HEAP:Executor进程的堆外内存

工作中最常用的是:MEMORY_AND_DISK和MEMORY_AND_DISK_2.优先推荐使用MEMORY_AND_DISK

演示缓存的使用操作:

import time

from pyspark import SparkConf, SparkContext, StorageLevel
import os
import jieba

# 绑定指定的Python解释器
os.environ['SPARK_HOME'] = '/export/server/spark'
os.environ['PYSPARK_PYTHON'] = '/root/anaconda3/bin/python3'
os.environ['PYSPARK_DRIVER_PYTHON'] = '/root/anaconda3/bin/python3'

# 需要过滤的关键词黑名单
keyword_black_list = ['+','.','的','com']

# ctrl+alt+M将代码封装成函数/方法
# 3.2- 需求一:统计每个关键词出现了多少次。先提取需要操作的字段并且分词,这一步类似WordCount中的对每行进行切分处理,再仿照WordCount实现。
def top10_keyword():
    keyword_rdd = etl_rdd.flatMap(lambda line_tup: list(jieba.cut(line_tup[2])))
    # print(keyword_rdd.take(10))

    # 数据结构转变。将单词变成元组
    # keyword_map_rdd = keyword_rdd.filter(lambda word:word!='+' or word!='.').map(lambda word:(word,1))
    keyword_map_rdd = keyword_rdd.filter(lambda word: word not in keyword_black_list).map(lambda word: (word, 1))

    # 分组聚合操作
    keyword_result_rdd = keyword_map_rdd.reduceByKey(lambda agg, curr: agg + curr)
    # print(keyword_result_rdd.take(100))

    # 对结果中关键词的次数降序排序,取TOP10
    keyword_result = keyword_result_rdd.top(10, key=lambda tup: tup[1])
    print(keyword_result)


# 3.3- 需求二:统计每个用户每个搜索内容点击的次数
def content():
    """
        hive sql:
            select
                用户,搜索内容,count(1) as cnt
            from table
            group by 用户,搜索内容
    """
    # 从原始的6个字段中,提取出2个字段,得到 (用户,搜索内容)
    new_tup_tmp_rdd = etl_rdd.map(lambda tup: (tup[1], tup[2]))

    # 数据格式转换
    """
            输入:(张三,鸡你太美) -> hello
            输出:((张三,鸡你太美),1) -> (hello,1)
        """
    new_tup_rdd = new_tup_tmp_rdd.map(lambda tup: (tup, 1))
    # new_tup_rdd = new_tup_tmp_rdd.map(lambda tup:(tup[0],tup[1],1))

    # 分组聚合
    content_result = new_tup_rdd.reduceByKey(lambda agg, curr: agg + curr)
    print(content_result.take(10))


if __name__ == '__main__':

    # 1- 创建SparkContext
    conf = SparkConf().setAppName('sogou_demo').setMaster('local[*]')
    sc = SparkContext(conf=conf)

    # 2- 数据输入
    init_rdd = sc.textFile('file:///export/data/gz16_pyspark/01_spark_core/data/SogouQ.sample')

    print("ETL处理前数据条数:",init_rdd.count())

    # 3- 数据处理
    # 3.1- ETL:数据的清洗、转换、加载
    """
        split():默认按照空白字符进行切分。例如:空格、制表符、回车换行符等
        
        map和flatMap的主要区别:flatMap对每一个元素处理以后,会将结果打平/压扁到一个更大的容器当中。
    """
    map_rdd = init_rdd.map(lambda line:line.split())
    # print("调用map算子后的内容:",map_rdd.take(10))

    # flatmap_rdd = init_rdd.flatMap(lambda line: line.split())
    # print("调用flatMap算子后的内容:",flatmap_rdd.take(10))

    # 过滤掉每行中没有6个字段的数据
    filter_rdd = map_rdd.filter(lambda line_list: len(line_list)==6)


    # 数据结构转换(为了演示而演示)
    etl_rdd = filter_rdd.map(lambda line_list:(
        line_list[0],
        line_list[1],
        line_list[2][1:-1], # 省略前后的中括号
        line_list[3],
        line_list[4],
        line_list[5]
    ))

    # 设置缓存。并且调用count算子触发操作
    # etl_rdd.cache().count()
    etl_rdd.persist(storageLevel=StorageLevel.MEMORY_AND_DISK).count()

    print("ETL处理后数据条数:", etl_rdd.count())

    # 3.2- 需求一:统计每个关键词出现了多少次
    # top10_keyword()

    # 3.3- 需求二:统计每个用户每个搜索内容点击的次数
    content()


    time.sleep(20)

    # 手动清理缓存。你对哪个RDD设置了缓存,那么你就对那个RDD清理缓存。也需要调用count算子触发。
    etl_rdd.unpersist().count()

    time.sleep(100)

    # 5- 释放资源
    sc.stop()

2.RDD的checkpoint检查点

RDD缓存主要是将数据存储在内存中,是临时存储,不太稳定,它主要是用来提升程序运行效率的.RDD的checkpoint(检查点)主要是将数据存储在HDFS上,是持久化存储,而HDFS存储数据有3副本的机制,让数据更加安全可靠.

checkpoint认为使用磁盘或者HDFS存储数据之后,数据非常的安全可靠,因此checkpoint会将RDD间的依赖关系给删除/丢弃掉.因此如果checkpoint的数据真的出现了问题,是无法再从头开始计算.

checkpoint主要作用:提升程序的容错率

注意事项:checkpoint可以将数据存储在磁盘或者HDFS上,主要是将数据存储在HDFS上

相关API:

sc.setCheckpointDir(存储路径):设置checkpoint数据存放路径

rdd.checkpoint():对指定RDD启用checkpoint

rdd.count():触发checkpoint

3.缓存和checkpoint的区别

面试题:Spark提供了两种持久化方案。一种为缓存操作,一种为checkpoint方案。请问有什么区别呢?

1- 数据存储位置不同

缓存:存储在内存或者磁盘或者堆外内存中

checkpoint检查点:存储在磁盘或者HDFS上,在集群模式下,仅能保存到HDFS上

2- 数据生命周期

缓存:当程序执行完成后,或者手动调用unpersist缓存都会被删除

checkpoint检查点:即使程序退出后,checkpoint检查点的数据依然是存在的,不会删除,需要手动删除.

3- 血缘关系

缓存:不会截断RDD之间的血缘关系,因为缓存数据有可能是失效,当失效后,需要重新回溯计算操作

checkpoint检查点:会截断掉依赖关系,因为checkpoint将数据保存到更加安全可靠的为止,不会发生数据丢失的问题,当执行失败的时候,也不需要重新回溯执行

4- 主要作用不同

缓存:提高Spark程序的运行效率

checkpoint检查点:提高Spark程序的容错性

思考:既然持久化的方案有两种,那么在生产环境中应该使用哪个方案?

在同一个项目中,推荐缓存和checkpoint(检查点)同时配合使用

使用顺序如下:在代码中先设置缓存,再设置checkpoint检查点,然后再一同使用Action算子触发,推荐使用count算子,因为这个顺序,只有一次IO写的过程.

实际过程如下:程序会优先从缓存中读取数据,如果发现缓存中没有数据.再从checkpoint中读取数据,并且接着将读取到的数据重新在内存中放置一份,后续还是优先从缓存中读取.

二.Spark内核调度

1.内容概述

Spark内核调度的任务:

如何构建DAG执行流程图

如何划分Stage截断

Driver底层是如何运转

确定需要构建多少分区(线程)

Spark内核调度的目的:尽可能用最少的资源高效地完成任务计算

2.RDD的依赖

RDD依赖:一个RDD的形成可能是由一个或者多个RDD得到的,此时这个RDD和之前的RDD之间产生依赖关系.

在Spark中,RDD之间的依赖关系,主要有两种类型:

窄依赖:

作用:能够让Spark程序并行计算,也就是一个分区数据计算出现问题以后,其他分区计算不受到任何影响

特点:父RDD的分区和子RDD的分区关系是一对一的关系,也就是父RDD分区的数据会被整个下游子RDD的分区接收

 

宽依赖:

作用:划分Stage的重要依据,宽依赖也叫Shuffle依赖

特点:父RDD的分区和子RDD的分区关系是一对多的关系,也就是父RDD的分区数据会被分成多份给到下游子RDD的多个分区所接收

注意:如果有宽依赖,shuffle下游的其他操作,必须等待shuffle执行完成之后才能够继续执行,为了避免数据不完整

 说明:在实际使用中,不需要纠结哪些算子会存在shuffle,以需求为目标,虽然shuffle的存在会影响一定的效率,但是以完成任务为准则,改用哪个算子,就使用哪个算子即可,不要过分纠结.

算子中一般以ByKey结尾的会发生Shuffle;另外是重分区算子会发生shuffle

3.DAG和Stage

DAG:有向无环图,主要描述一段执行任务,从开始一直往下走,不允许出现回调操作

Spark应用程序中,遇到一个Action算子,就会触发形成一个Job任务的产生

对于每一个Job的任务,都会产生一个DAG执行流程图,那么这个流程图是如何形成的呢?

层级关系:

1- 一个Spark应用程序 ->遇到一个Action算子,就会触发形成一个Job任务

2- 一个Job任务只有一个DAG有向无环图

3- 一个DAG有向无环图->有多个Stage

4- 一个Stage -> 有多个Task线程

5- 一个RDD -> 有多个分区

6- 一个分区会被一个Task线程所处理

 默认并行度的值确认:

因为是使用textFile读取HDFS上的文件,因此RDD分区数=max(文件的block块的数量,defaultMinPartition),继续需要知道defaultMinPartition的值是多少.

defaultMinPartition = min(spark.default.parallelism,2)取最小值,最终我们确认spark.default.parallelism的参数值就能够最终确认RDD的分区数有多少个

spark.default.parallelism参数值确认过程如下:

1- 如果有父RDD,就取父RDD的最大分区数

2- 如果没有父RDD,根据集群模式进行取值:

2.1- 本地模式:机器最大CPU核数

2.2- Mesos:默认是8

2.3- 其他模式:所有执行节点上的核总数或2,以较大者为准

4、Spark Shuffle

Spark中shuffle的发展历程:

1- 在1.1版本以前,Spark采用Hash shuffle (优化前 和 优化后)

2- 在1.1版本的时候,Spark推出了Sort Shuffle

3- 在1.5版本的时候,Spark引入钨丝计划(优化为主)

4- 在1.6版本的时候,将钨丝计划合并到sortShuffle中

5- 在2.0版本的时候,将Hash Shuffle移除,将Hash shuffle方案移植到Sort Shuffle

优化前的Hash shuffle:

 

存在的问题: 

上游(map端)的每个Task会产生与下游Task个数相等的小文件个数。这种情况会导致上游有非常多的小文件。另外,下游(reduce端)来拉取文件的时候,会有大量的网络IO和磁盘IO过程,因为要打开和读取多个小文件。

优化后的Hash shuffle

变成了由每个Executor进程产生与下游Task个数相等的小文件数,这样可以大量减小小文件的产生,以及降低下游拉取文件时候的网络IO和磁盘IO过程.

Sort shuffle:

 

Sort Shuffle分层了两种:普通机制和bypass机制,具体使用哪种,由Spark底层决定.

普通机制的运行过程:每个上游Task线程处理数据,数据处理完以后,先放在内存中,接着对内存中的数据进行分区,排序.将内存中的数据溢写到磁盘,形成一个个的小文件.溢写完成以后,会将多个小文件合并成一个大的磁盘文件,并且针对每个大的磁盘文件,会提供一个索引文件,接着是下游Task根据索引文件来读取相应的数据.

bypass机制:就是普通机制的基础上,省略了排序的过程

bypass机制的触发条件(两者同时):

1- 上游RDD的分区数量最多不能超过200个

2- 上游不能对数据进行提前聚合操作(因为提前聚合,需要先进行分组操作,而分组的操作实际上是有排序的操作) 

5.Job调度流程

 主要是讨论:在Driver内部,是如何调度任务

1- Driver进程启动后,底层PY4J创建SparkContext顶级对象。在创建该对象的过程中,还会创建另外两个对象,分别是: DAGScheduler和TaskScheduler
    DAGScheduler: DAG调度器。将Job任务形成DAG有向无环图和划分Stage的阶段
    TaskScheduler: Task调度器。将Task线程分配给到具体的Executor执行

2- 一个Spark程序遇到一个Action算子就会触发产生一个Job任务。SparkContext将Job任务给到DAG调度器,拿到Job任务后,会将Job任务形成DAG有向无环图和划分Stage的阶段。并且会确定每个Stage阶段有多少个Task线程,会将众多的Task线程放到TaskSet的集合中。DAG调度器将TaskSet集合给到Task调度器

3- Task调度器拿到TaskSet集合以后,将Task分配给到给到具体的Executor执行。底层是基于SchedulerBackend调度队列来实现的。

4- Executor开始执行任务。并且Driver会监控各个Executor的执行状态,直到所有的Executor执行完成,就认为任务运行结束

5- 后续过程和之前一样

6、Spark RDD并行度

整个Spark应用中,影响并行度的因素有以下两个原因:

  • 1- 资源的并行度: Executor数量 和 CPU核心数 以及 内存的大小

  • 2- 数据的并行度: Task的线程数 和 分区数量

  • 一般将Task线程数设置为CPU核数的2-3倍。另外每个线程分配3-5GB的内存资源。

说明: spark.default.parallelism该参数是SparkCore中的参数。该参数只会影响shuffle以后的分区数量。另外该参数对parallelize并行化本地集合创建的RDD不起作用。

猜你喜欢

转载自blog.csdn.net/MSJ3917/article/details/135431164
今日推荐