共计 2168 个字符,预计需要花费 6 分钟才能阅读完成。
在 SortShuffleWriter 和 UnsafeShuffleWriter 都提到了执行内存的申请,为了阐明内存的申请过程,咱们先看看内存管理器。
内存管理器
Executor 启动的时候,就会创立一个内存管理器 MemoryManager(默认 UnifiedMemoryManager),MemoryManager 对存储体系和内存计算所应用的内存进行治理。内存又分为堆外内存和堆内存,所以 MemoryManager 就有 4 个内存池,别离为堆内存的存储内存池 onHeapStorageMemoryPool、堆外内存的存储内存池 offHeapStorageMemoryPool、堆内存的执行计算内存池 onHeapExecutionMemoryPool、堆外内存的执行计算内存池 offHeapExecutionMemoryPool,每个内存池都记录着内存池的大小和已应用的大小。
下图把堆内和堆外的执行计算的内存和存储的内存并在一起,是因为他们之间并没有一个显著的界线,比如说堆内执行计算的内存不够用了,就会向堆内存储的内存“借”内存,堆内存储的内存也会向堆内执行计算的内存“借”内存。堆外执行计算的内存和存储的内存也是能够互相“借”内存。
内存消费者和工作内存管理器
Task 的资源分配中曾经晓得,Driver 会把 task 封装成 TaskDescription,交给 Executor 去执行。Executor 会把 TaskDescription 封装成 TaskRunner 并放入线程池中,如果须要内部排序的话,最初会用到 UnsafeShuffleWriter 的 ShuffleExternalSorter 或者 SortShuffleWriter 的 ExternalSorter。这两个内部排序器其实都是内存消费者 MemoryConsumer。
内存消费者仅仅定义了内存消费者的标准,实际上对内存的申请、开释是由工作内存管理器 TaskMemoryManager 来治理的。工作内存管理器实际上依赖于 MemoryManager 提供的内存治理能力,所以 Executor 同时有多个 TaskRunner 在执行的时候,就会有多个内存消费者,每个内存消费者都会通过工作内存管理器对内存管理器申请、开释内存。
申请内存
内存消费者为了排序性能的进步,会把 RDD 的数据集事后寄存在内存中,这个内存是须要向内存管理器申请的。
咱们假设内存模式是堆内存,那申请的内存就是堆内存的执行计算内存池,内存池的大小_poolSize 为 100。每个执行内存池中都保护着一个 map,叫做 memoryForTask,key 是工作内存管理器的身份标识 taskAttemptId,value 是工作内存管理器已申请的内存大小。
此时,taskAttemptId 为 1 的工作内存管理器去堆内存的执行计算内存池申请 10 内存,他发现 memoryForTask 里并没有 taskAttemptId 为 1 的 key,于是就把 key 为 1,value 为 0 赋值给 memoryForTask。
每个工作所能申请的内存范畴是 poolSize/2N
到maxPoolSize/N
之间,这个 N 是 memoryForTask 中 key 的数量,poolSize 是以后内存池的大小,maxPoolSize 是以后内存池可应用的最大大小,这个值是包含堆内存的存储内存池的局部甚至全副内存大小,也就是说,如果执行计算内存池不够用了,是能够从存储内存池借的。当然存储内存池也能够向执行计算内存池借内存。
目前执行计算内存池的内存可能满足 taskAttemptId 为 1 所须要的内存,所以间接给分配内存,并更新 memoryForTask 中 taskAttemptId 为 1 的值为 10。
taskAttemptId 为 2 的工作内存管理器去堆内存的执行计算内存池申请 50 内存,流程同上。此时线程池中,可用内存就剩下 100-10-10-50=30 了。
taskAttemptId 为 3 的工作内存管理器去堆内存的执行计算内存池申请 20 内存,流程同上。此时线程池中,可用内存就剩下 100-10-10-50-20=10 了。
taskAttemptId 为 3 的工作内存管理器去堆内存的执行计算内存池持续申请 20 内存,此时内存曾经不够用了,执行计算内存池就会去存储内存池要回借出去的内存(如果有的话),并且向存储内存池借了 10 内存,taskAttemptId 为 3 的值就变成了 20+20=40。此时这 4 个工作内存管理器申请的内存曾经超过了堆内存的执行计算内存池。
taskAttemptId 为 4 的工作内存管理器去堆内存的执行计算内存池申请 20 内存,然而执行计算内存池和存储内存池借来的内存曾经不满足 20,或者比要求的内存最小值还小,以后线程处于期待状态。
内存开释
taskAttemptId 为 2 的工作内存管理器此时开释了 40 内存,此时对应的 value 就变成了 50-40=10,而后唤醒所有阻塞的线程,比方下面的 taskAttemptId 为 4 对应的线程。
taskAttemptId 为 4 对应的线程被唤醒后,发现能够申请 20,于是更新对应的值。
taskAttemptId 为 2 的工作内存管理器此时持续开释了 10 内存,此时对应的 value 就变成了 10-10=0,因为小于等于 0 了,所以这个 key 就删除掉,并且再唤醒所有阻塞的线程。