这是我参与「第四届青训营 」笔记创作活动的的第7天
大数据处理链路
下面是大数据处理的主要流程:
我们从数据源获取数据并将其存储到系统,这里常见的分布式存储系统有
- Kafka:分布式消息队列系统
- HDFS:分布式文件系统
- HBase:分布式k-v存储系统
- S3/TOS:分布式对象存储
- ......
数据要展示到应用层需要经过计算,其中读取数据就可以通过上面的存储系统,也可以是MySQL这些,计算则应用到了计算引擎,其中Spark,Hadoop一般用于批计算;Flink用于流式计算;ClickHouse,presto等则用于OLAP场景,最后将计算完成的数据输出到数据应用,就完成了大数据的处理。
Spark作为批计算的主流,我们今天就来了解一下Spark
Spark
Spark运行架构
Spark是以Master-Slave为基础构建的,其中主要的节点对应的功能如下:
- Cluster Manager:是master的集群管理器,负责控制整个集群、监控Worker Node、资源的调度和管理,同时支持多种多样的模式。
- Worker Node:负责控制计算节点,并启动执行器(Executor)和Task任务
- Driver Program:相当与AppMaster,是应用管理者,负责作业的任务调度,也就是JVM进程,他创建SparkContext来控制上下层。
核心组件SparkCore
用户提交任务后,经过SparkCore的处理流程:
用户提交后,会先生成AppMaster,分配资源并创建Executor执行器,在Yarn模式下也可以相当与Driver,而Driver则进行Task管理和分配。
而这个流程的输入和输出全是基于RDD算子的基础上进行的。
RDD
RDD是一个容错的,可以并发运行的数据集,是Spark中最基本的数据处理模型和单元。 RDD的5大特性:
- A list of partitions,每个RDD都会被分区,这些分区运行站在不同的节点上,而每一个分区可以对应一个Task来处理。
- A function for computing each split,每个RDD分区都有对应的计算函数,对具体的partition进行计算。
- a list of dependencies on other RDDS,每个RDD会依赖其他RDD,RDD转换后都会生成一个新的RDD
- a Partitioner for key- alue RDDS,实现了两种分区函数,只有key-value的RDD才会有分区,其他的则为空。
- a list of preferred locations to compute each split on,每个分区都有一个优先位置列表,它会存储每个partition的优先位置。
RDD依赖
RDD依赖有两种,分为窄依赖(Narrow deps)和宽依赖(Wide deps)
- 窄依赖:父RDD只能对应一个子RDD分区,子RDD可能有多个父RDD分区。
- 宽依赖:父RDD可以对应多个子RDD分区,对RDD做groupBy或者join都会产生宽依赖。
内存机制
执行器的内存主要有两类,Storage Memory(存储内存)和Execution Memory(执行内存),它们之间可以动态调整,相互借用。
当存储内存空闲时,执行内存可以借用存储内存来使用,反之,执行内存空闲时,存储内存也可以借用。但当Execution需要内存时,可以驱逐Storage使用的内存,Storage需要将内存里的数据存储到本地硬盘,再将内存归还给Execution,而被Execution借用的内存不能被Storage像上面这样驱逐。
SparkSQL
SparkSQL处理流程:
上面的流程基本都是在Spark中的Catalyst进行的
Catalyst优化器
优化策略:
- RBO
- 主要有列裁剪,常量累加,谓词下推,动态过滤裁剪,简化表达式等
- CBO
- 计算代价,得出一个最优的结果。
在计算过程中可能会使用到延迟数据产生负面影响,于是在3.0版本引入的自适应查询AQE。
AQE
主要有三个优化场景:
- 动态合并shuffle分区
我们会根据前面运行完的Stage获取实际partition的大小,这样我们就可以把相邻的几个较小的partition动态合并,这样就减少了Task的处理。
- 动态调整状态策略
右表在执行过程中,我们发现经过Filter过滤原本25MB的数据最后只剩下了8MB,不超过10MB的数据大小可以通过BROADCAST合并,这样我们就及时可以改变原来的合并策略,从SMJ调整到BHJ,带来性能的提升。
- Skew Join优化
Join的某个任务出现了倾斜的问题,可以看到A0的执行时间明显要大于其他的partition,AQE就可以检测这个倾斜数据,将它拆分成多个较小的数据,这样虽然会增加Task,但在执行时间上来看,无疑是会带来优化的。