Spark - 内存管理器与执行内存

SortShuffleWriterUnsafeShuffleWriter都提到了执行内存的申请,为了说明内存的申请过程,我们先看看内存管理器。

内存管理器

Executor启动的时候,就会创建一个内存管理器MemoryManager(默认UnifiedMemoryManager),MemoryManager对存储体系和内存计算所使用的内存进行管理。内存又分为堆外内存和堆内存,所以MemoryManager就有4个内存池,分别为堆内存的存储内存池onHeapStorageMemoryPool、堆外内存的存储内存池offHeapStorageMemoryPool、堆内存的执行计算内存池onHeapExecutionMemoryPool、堆外内存的执行计算内存池offHeapExecutionMemoryPool,每个内存池都记录着内存池的大小和已使用的大小。
下图把堆内和堆外的执行计算的内存和存储的内存并在一起,是因为他们之间并没有一个明显的界限,比如说堆内执行计算的内存不够用了,就会向堆内存储的内存“借”内存,堆内存储的内存也会向堆内执行计算的内存“借”内存。堆外执行计算的内存和存储的内存也是可以相互“借”内存。
Spark - 内存管理器与执行内存_第1张图片

内存消费者和任务内存管理器

Task的资源分配中已经知道,Driver会把task封装成TaskDescription,交给Executor去执行。Executor会把TaskDescription封装成TaskRunner并放入线程池中,如果需要外部排序的话,最后会用到UnsafeShuffleWriter的ShuffleExternalSorter或者SortShuffleWriter的ExternalSorter。这两个外部排序器其实都是内存消费者MemoryConsumer。
Spark - 内存管理器与执行内存_第2张图片
内存消费者仅仅定义了内存消费者的规范,实际上对内存的申请、释放是由任务内存管理器TaskMemoryManager来管理的。任务内存管理器实际上依赖于MemoryManager提供的内存管理能力,所以Executor同时有多个TaskRunner在执行的时候,就会有多个内存消费者,每个内存消费者都会通过任务内存管理器对内存管理器申请、释放内存。
Spark - 内存管理器与执行内存_第3张图片

申请内存

内存消费者为了排序性能的提高,会把RDD的数据集预先存放在内存中,这个内存是需要向内存管理器申请的。
我们假定内存模式是堆内存,那申请的内存就是堆内存的执行计算内存池,内存池的大小_poolSize为100。每个执行内存池中都维护着一个map,叫做memoryForTask,key是任务内存管理器的身份标识taskAttemptId,value是任务内存管理器已申请的内存大小。
此时,taskAttemptId为1的任务内存管理器去堆内存的执行计算内存池申请10内存,他发现memoryForTask里并没有taskAttemptId为1的key,于是就把key为1,value为0赋值给memoryForTask。
Spark - 内存管理器与执行内存_第4张图片
每个任务所能申请的内存范围是poolSize/2NmaxPoolSize/N之间,这个N是memoryForTask中key的数量,poolSize是当前内存池的大小,maxPoolSize是当前内存池可使用的最大大小,这个值是包括堆内存的存储内存池的部分甚至全部内存大小,也就是说,如果执行计算内存池不够用了,是可以从存储内存池借的。当然存储内存池也可以向执行计算内存池借内存。
目前执行计算内存池的内存能够满足taskAttemptId为1所需要的内存,所以直接给分配内存,并更新memoryForTask中taskAttemptId为1的值为10。
Spark - 内存管理器与执行内存_第5张图片
taskAttemptId为2的任务内存管理器去堆内存的执行计算内存池申请50内存,流程同上。此时线程池中,可用内存就剩下100-10-10-50=30了。
Spark - 内存管理器与执行内存_第6张图片
taskAttemptId为3的任务内存管理器去堆内存的执行计算内存池申请20内存,流程同上。此时线程池中,可用内存就剩下100-10-10-50-20=10了。
Spark - 内存管理器与执行内存_第7张图片
taskAttemptId为3的任务内存管理器去堆内存的执行计算内存池继续申请20内存,此时内存已经不够用了,执行计算内存池就会去存储内存池要回借出去的内存(如果有的话),并且向存储内存池借了10内存,taskAttemptId为3的值就变成了20+20=40。此时这4个任务内存管理器申请的内存已经超过了堆内存的执行计算内存池。
Spark - 内存管理器与执行内存_第8张图片
taskAttemptId为4的任务内存管理器去堆内存的执行计算内存池申请20内存,但是执行计算内存池和存储内存池借来的内存已经不满足20,或者比要求的内存最小值还小,当前线程处于等待状态。
Spark - 内存管理器与执行内存_第9张图片

内存释放

taskAttemptId为2的任务内存管理器此时释放了40内存,此时对应的value就变成了50-40=10,然后唤醒所有阻塞的线程,比如上面的taskAttemptId为4对应的线程。
Spark - 内存管理器与执行内存_第10张图片
taskAttemptId为4对应的线程被唤醒后,发现可以申请20,于是更新对应的值。
Spark - 内存管理器与执行内存_第11张图片
taskAttemptId为2的任务内存管理器此时继续释放了10内存,此时对应的value就变成了10-10=0,由于小于等于0了,所以这个key就删除掉,并且再唤醒所有阻塞的线程。
Spark - 内存管理器与执行内存_第12张图片

你可能感兴趣的:(spark)