Volatile是JVM中非常重要的关键字,可以帮助我们保证变量的可见性并且禁止重排序。保证可见性我们可以理解,每个线程都有自己的local cache。当需要修改全局的变量时,需要先去工作内存中读取最新的数值并把数值加载到自己的本地工作内存,然后再进行修改。修改完成以后再将数值重新刷写回到工作内存中。因此Volatile关键字保证可见性其实就是在每次变量更新前后,强制去执行内存刷写。那,禁止指令重排是如何实现的呢?
本系列将参考Spark源码设计,尝试解读Spark源码中的几个核心模块,并结合源码分析实现。
接着上一篇,本篇,我们分析一下实现磁盘存储的功能类DiskStore
,这个类相对简单。在正式展开之前,我觉得有必要大概分析一下BlockManager
的背景,或者说它的运行环境,运行的作用范围。Blockmanager
这个类其实在运行时的每个节点都会有一个实例(包括driver和executor进程),因为不论是driver端进行广播变量的创建,还是executor端shuffle过程中写shuffle块,或者是任务运行时结果太大需要通过BlockManager
传输,或者是RDD的缓存,其实在每个运行节点上都会通过Blockmanager
来管理程序内部对于本地的内存和磁盘的读写.所以综上,我想表达的核心意思就是每个进程(driver和executor)都有一个Blockmanager
实例,而这些Blockmanager
实例是通过BlockManagerId
类来进行唯一区分的,BlockManagerId
实际上是对进程物理位置的封装。
本系列将参考Spark源码设计,尝试解读Spark源码中的几个核心模块,并结合源码分析实现。
根据之前的一系列分析,我们对spark作业从创建到调度分发,到执行,最后结果回传driver的过程有了一个大概的了解。但是在分析源码的过程中也留下了大量的问题,最主要的就是涉及到的spark中重要的几个基础模块,我们对这些基础设施的内部细节并不是很了解,之前走读源码时基本只是大概了解每个模块的作用以及对外的主要接口,这些重要的模块包括BlockMananger
, MemoryMananger
, ShuffleManager
, MapOutputTracker
, rpc模块NettyRPCEnv
,以及BroadcastManager
。
而对于调度系统涉及到的几个类包括DAGSchedulerManager
, TaskSchedulerManager
, CoarseGrainedSchedulerBackend
, CoarseGrainedExecutorBackend
, Executor
, TaskRunner
,我们之前已经做了较为详细的分析,因此这几个模块暂告一段落。
上一篇中,我们分析了shuffle在map阶段的写过程。简单回顾一下,主要是将ShuffleMapTask计算的结果数据在内存中按照分区和key进行排序,过程中由于内存限制会溢写出多个磁盘文件,最后会对所有的文件和内存中剩余的数据进行归并排序并溢写到一个文件中,同时会记录每个分区(reduce端分区)的数据在文件中的偏移,并且把分区和偏移的映射关系写到一个索引文件中。
本篇,我们来看一下spark内核中另一个重要的模块,Shuffle管理器ShuffleManager
。shuffle可以说是分布式计算中最重要的一个概念了,数据的join,聚合去重等操作都需要这个步骤。另一方面,spark之所以比mapReduce的性能高其中一个主要的原因就是对shuffle过程的优化,一方面spark的shuffle过程更好地利用内存(也就是我们前面在分析内存管理时所说的执行内存),另一方面对于shuffle过程中溢写的磁盘文件归并排序和引入索引文件。当然,spark性能高的另一个主要原因还有对计算链的优化,把多步map类型的计算chain在一起,大大减少中间过程的落盘,这也是spark显著区别于mr的地方。
上一篇,我们主要分析了一次作业的提交过程,严格说是在driver
端的过程,作业提交之后经过DAGScheduler
根据shuffle依赖关系划分成多个stage,依次提交每个stage,将每个stage创建于分区数相同数量的Task,并包装成一个任务集,交给TaskSchedulerImpl
进行分配。
TaskSchedulerImpl
则会根据SchedulerBackEnd
提供的计算资源(executor),并考虑任务本地性,黑名单,调度池的调度顺序等因素对任务按照round-robin的方式进行分配,并将Task与executor的分配关系包装成TaskDescription
返回给SchedulerBackEnd
。
然后SchedulerBackEnd
就会根据收到的TaskDescription
将任务再次序列化之后发送到对应的executor
上执行。
本篇,我们就来分析一下Task在executor
上的执行过程。
上一篇讲到DAGScheduler根据shuffle依赖对作业的整个计算链划分成多个stage之后,就开始提交最后一个ResultStage,而由于stage之间的依赖关系,实际上最终是循着计算链从上到下依次提交stage的。每提交一个stage,就会将这个stage分成多个Task,并且会计算每个Task的偏向位置,将RDD和ShuffleDependency,TaskMetrics等对象序列化用于远程传输,最后把一个stage的所有Task包装成一个任务集,提交给TaskSchedulerImpl运行。本节就来分析一下这个TaskSchedulerImpl。
本系列将参考Spark源码设计,尝试解读Spark源码中的几个核心模块,并结合源码分析实现。本篇,我会从一次spark作业的运行为切入点,将spark运行过程中涉及到的各个步骤,包括DAG图的划分,任务集的创建,资源分配,任务序列化,任务分发到各个executor,任务执行,任务结果回传driver等等。
本系列将参考Spark源码设计,尝试解读Spark源码中的几个核心模块,并结合源码分析实现。本章内容是基于spark 2.1.0的SparkContext的启动过程。
主要总结可能用到的git命令
tag:
缺失模块。
1、请确保node版本大于6.2
2、在博客根目录(注意不是yilia根目录)执行以下命令:
npm i hexo-generator-json-content --save
3、在根目录_config.yml里添加配置:
jsonContent: meta: false pages: false posts: title: true date: true path: true text: false raw: false content: false slug: false updated: false comments: false link: false permalink: false excerpt: false categories: false tags: true