Spark核心 源码解析

核心

所谓的内核,其实就是Spark的内部核心原理。

1.Spark应用提交

(1) Spark向Yarn提交

(1) Spark向Yarn提交

当使用bin/java执行java程序时,会产生JVM,java的进程

(2) ApplicationMaster, Driver, Executor

ApplicationMaster是一个进程

Driver是一个线程, 但是我们一般会讲SparkContext称之为Driver

Executor是一个计算对象, 但有时我们将ExecutorBackend后台通信对象也称 之为Executor

2.Spark内部组件及通信

(1) 通信原理 - IO - RPC

基本的网络通信:Socket, ServerSocket

通信框架:AKKA(旧), Netty(新)(AIO)

三种IO方式:BIO(阻塞式), NIO(非阻塞式), AIO(异步)

Linux, windows

在Linux系统上,AIO的底层实现仍使用EPOLL,与NIO相同,因此在性能上没有明显的优势;Windows的AIO底层实现良好,但是Netty开发人员并没有把Windows作为主要使用平台考虑。微软的windows系统提供了一种异步IO技术:IOCP(I/O CompletionPort,I/O完成端口);Linux下由于没有这种异步IO技术,所以使用的是epoll(一种多路复用IO技术的实现)对异步IO进行模拟。所以在Linux上不建议使用AIO

(2) 组件内部

Driver - CoarseGrainedSchedulerBackend - DriverEndpoint

Executor - CoarseGrainedExecutorBackend

(3) 组件之间

通信终端共通类:Endpoint

通信终端:RpcEndpoint(receive)

通信终端引用:RpcEndpointRef(send,ask)

源码解析

截屏2020-07-10下午7.11.48

SparkSubmit
SparkSubmit

    -- main
        -- new SparkSubmit
        // 执行提交操作
        -- doSubmit
        
            // 解析参数
            -- parseArguments
            
                // --master yarn => master
                // --deploy-mode cluster => deployMode
                // --class SparkPI(WordCount) => 【mainClass】
                -- parse
                
            -- submit
            
                -- doRunMain
                
                    -- runMain
                    
                        // 准备提交环境
                        // (childArgs, childClasspath, sparkConf, childMainClass)
                        // 【cluster】 : childMainClass = org.apache.spark.deploy.yarn.YarnClusterApplication
                        // 【client 】 : childMainClass = mainClass
                        -- prepareSubmitEnvironment(args)
                        
                        -- Thread.currentThread.setContextClassLoader(loader)
                        
                        // 通过类名加载这个类
                        // Class.forName("xxxxx")
                        -- mainClass = Utils.classForName(childMainClass)
                        
                        // 反射创建类的对象并进行类型转换
                        -- app = mainClass.newInstance().asInstanceOf[SparkApplication]
                        -- app.start
                        
YarnClusterApplication

    -- start
       
       // 封装参数
       // --class
       -- new ClientArguments
       -- new Client
       
          // 创建Yarn客户端对象
          -- yarnClient = YarnClient.createYarnClient
                // ResourceManager
                -- rmClient
          
       -- run
       
            // 提交应用
            -- submitApplication
            
                // 配置JVM的启动参数
                // 封装指令 :【cluster】 command = bin/java org.apache.spark.deploy.yarn.ApplicationMaster
                // 封装指令 :【client 】 command = bin/java org.apache.spark.deploy.yarn.ExecutorLauncher
                -- createContainerLaunchContext
                -- createApplicationSubmissionContext
            
                // 向Yarn提交应用
                -- yarnClient.submitApplication(appContext)
ApplicationMaster
ApplicationMaster

    -- main
    
        // 封装参数
        // --class
        -- ApplicationMasterArguments
        -- new ApplicationMaster
        -- run
        
            // 【cluster】
            -- runDriver
            
                // 启动用户的应用
                -- userClassThread = startUserApplication()
                
                    // 反射加载类,获取类的main方法
                    // --class SparkPI(WordCount)
                    -- userClassLoader.loadClass(args.userClass).getMethod("main", classOf[Array[String]])
                    
                    -- userThread = new Thread
                    
                    -- thread.setName(【Driver】)
                    // 在执行main方法
                    -- mainMethod.invoke
                
                // 线程阻塞,等待对象(SparkContext)的返回
                -- ThreadUtils.awaitResult
                
                // 注册AM
                -- registerAM
                
                -- createAllocator
                
                    -- client.createAllocator
                    
                    // 分配资源
                    -- allocator.allocateResources()
                    
                        // 获取可用的资源列表
                        -- allocateResponse.getAllocatedContainers()
                        // 处理可用的资源
                        -- handleAllocatedContainers
                        
                            // 匹配后的资源可以运行
                            -- runAllocatedContainers
                            
                                -- container => ExecutorRunnable
                                
                                    -- nmClient
                                    // 启动容器
                                    -- startContainer
                                    
                               -- run 
                               
                                    // 封装指令
                                    // command = bin/java org.apache.spark.executor.CoarseGrainedExecutorBackend
                                    -- prepareCommand
                                    
                                    // 让NM启动容器, 启动Executor
                                    -- nmClient.startContainer(container.get, ctx)
            
            // 【client】
            -- runExecutorLauncher

移动数据不如移动计算

涉及到本地化级别

截屏2020-07-10下午7.13.38

CoarseGrainedExecutorBackend
CoarseGrainedExecutorBackend

    -- main
    
        -- run
通信

RPC:通信,RMI,EJB

Endpoint:终端

backend:后台

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JRLJ8lRQ-1595292951976)(/Users/vanas/Library/Application Support/typora-user-images/截屏2020-07-10下午8.39.38.png)]

3.Spark作业的提交(调度)

截屏2020-06-17下午10.55.09

(1) Application

Yarn中会有Application

SparkConf中配置setAppName(“xxxxx”)

SparkContext

(2) 逻辑代码 => RDD

RDD的创建

RDD的转换

RDD的行动

(3) Job => 行动算子(N)

触发作业的执行:sc.runJob

new ActiveJob

(4) Stage的划分 => 转换算子 => 依赖关系

窄依赖,宽依赖

分区的数量

  默认不变

  Shuffle的算子一般都会有改变分区数量的参数

默认分区的数量

​ Local[*]

截屏2020-06-17下午10.55.19

(5) Task的切分(Partition)

任务和阶段的关系

​ 阶段数量 = ResultStage + N * ShuffleDep

​ 阶段类型:ResultStage, ShuffleMapStage

任务和分区的关系

​ 任务的数量 : 当前阶段的分区的数量

任务的总数量

​ 任务的总数量 : 所有阶段的任务总和

任务的类型

​ ShuffleMapState => ShuffleMapTask

​ ResultStage => ResultTask

4.任务的执行

(1) 内容

阶段ID

RDD元数据,依赖

分区

首选位置

(2) 序列化(累加器,KRYO)

默认序列化:Java序列化

Kryo序列化:shuffle的场合下,如果类型为值类型或字符串类型

(3) 调度

Driver

​ TaskSet - Stage

​ TaskSetManager - Scheduler

​ 调度模式:FIFO(默认), FAIR

​ 任务调度池:Pool(FIFO)

Executor

​ 本地化级别(调度)

​ 降级处理:默认等待3s

(4) 计算

任务的编码

向Executor发送Task

Executor接收Task,并进行解码

计算对象执行Task

线程池执行task.run方法, 调用具体Task对象的runTask方法。

(5) Shuffle

Shuffle map(Write)

Shuffle reduce(Read)

管理器:SortShuffleManager

ShuffleWriter

​ BypassMergeSortShuffleWriter

​ UnsafeShuffleWriter

​ SortShuffleWriter

    写磁盘文件时,首先进行内存中的排序,如果内存(5M)不够,会溢写磁盘,生成临时文件,最终将所有的临时文件合并成数据文件和索引文件。

​ 预聚和的原理:在排序时,构造了一种类似于hashtable的结构,所以想同的key就聚合在一起。

排序规则:首先会按照分区进行排序,然后按照key.hashCode进行排序

ShuffleHandle

​ BypassMergeSortShuffleHandle: 没有预聚和功能的算子 & reduce的分区数量 <=阈值(200)

​ SerializedShuffleHandle :Spark的内存优化后的解决方案,对象序列化后不需要反序列化。!(支持序列化重定位 & 支持预聚和 & 分区数量 大于阈值)

​ BaseShuffleHandle :默认的shuffleHandle

(6) 内存(cache)

(7) 累加器

Shuffle

截屏2020-06-17下午10.55.57

截屏2020-06-17下午10.56.06

截屏2020-06-17下午10.56.13

截屏2020-06-17下午10.56.20

截屏2020-06-17下午10.56.26

截屏2020-06-17下午10.56.37

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值