Spark內存存儲MemoryStore & 磁盤存儲DiskStore

簡介

BlockManager中block的實際存儲是由MemoryStore和DiskStore來實現的,根據Block要求的存儲級別StorageLevel來決定是存儲在內存還是磁盤,以及是序列化存儲還是非序列化存儲。MemoryStore和DiskStore是在BlockManager中初始化的。

// BlockManager.scala
// Actual storage of where blocks are kept
private[spark] val memoryStore =
  new MemoryStore(conf, blockInfoManager, serializerManager, memoryManager, this)
private[spark] val diskStore = new DiskStore(conf, diskBlockManager, securityManager)
memoryManager.setMemoryStore(memoryStore)

內存存儲MemoryStore

MemoryStore負責將沒有序列化的Java對象數組或者序列化的ByteBuffer存儲到內存中。如果block的存儲同時支持內存和磁盤存儲,則優先存儲到MemoryStore中,內存不夠時再存儲到DiskStore。

內存主要由堆內內存和堆外內存構成,其中堆外內存只能存儲序列化後的值。MemoryStore中主要的成員是entries(即LinkedHashMap[BlockId, MemoryEntry]),用來保存塊存儲的映射。所有塊的添加、獲取和刪除等操作,都是基於該映射結構實現的,因此對其所有的操作都需要加同步語義。

// Note: all changes to memory allocations, notably putting blocks, evicting blocks, and
// acquiring or releasing unroll memory, must be synchronized on `memoryManager`!

private val entries = new LinkedHashMap[BlockId, MemoryEntry[_]](32, 0.75f, true)

// A mapping from taskAttemptId to amount of memory used for unrolling a block (in bytes)
// All accesses of this map are assumed to have manually synchronized on `memoryManager`
private val onHeapUnrollMemoryMap = mutable.HashMap[Long, Long]()
// Note: off-heap unroll memory is only used in putIteratorAsBytes() because off-heap caching
// always stores serialized values.
private val offHeapUnrollMemoryMap = mutable.HashMap[Long, Long]()

// Initial memory to request before unrolling any block
private val unrollMemoryThreshold: Long =
  conf.getLong("spark.storage.unrollMemoryThreshold", 1024 * 1024)

數據存儲方法putBytes

首先檢查entries中是否已經存儲了該blockId,如果已存儲了則拋出IllegalArgumentException異常。然後檢測MemoryStore中是否有足夠的內存,沒有則返回false,有則創建ByteBuffer並將其存儲到entries中。

數據存儲方法putIteratorAsValues & putIteratorAsBytes

putIteratorAsValues用於存儲非序列化的block,而putIteratorAsBytes則是將block對象序列化後存儲。

首先檢查entries中是否已經存儲了該blockId,如果已存儲了則拋出IllegalArgumentException異常。由於迭代器可能太大而無法實現並存儲在內存中。爲避免OOM異常,方法將逐步展開迭代器,同時定期檢查是否有足夠的可用內存。如果成功物化了該block塊,則在實體化期間使用的臨時展開內存將“轉移”到存儲內存中,因此我們獲得的內存不會超過存儲該塊所需的內存。如果成功,則返回存儲的數據的估計大小。如果失敗,則返回包含該塊值的迭代器。返回的迭代器將由部分展開的塊和原始輸入迭代器的其餘元素的組合。調用者必須完全使用此迭代器或對其調用close(),以釋放部分展開的塊佔用的存儲內存。

獲取內存數據方法getBytes & getValues

getBytes和getValues方法都用於從entries中獲取MemoryEntry。但是getBytes方法只可以獲取序列化的block,getValues方法只可以獲取非序列化的block。

磁盤存儲DiskStore

將塊存儲到磁盤。blockSizes是保存塊存儲的主要結構,每個塊以文件的方式存儲到配置好的公共目錄中,而該結構則保存已存儲到磁盤上的塊的大小信息。minMemoryMapBytes是表示如果塊小於該值,則直接一次性讀取到內存中,反之則以ChunkedByteBuffer的方式緩衝讀取。

private val minMemoryMapBytes = conf.getSizeAsBytes("spark.storage.memoryMapThreshold", "2m")
private val blockSizes = new ConcurrentHashMap[String, Long]()

NIO讀取方法getBytes

getBytes方法通過DiskBlockManager的getFile方法獲取文件。然後使用NIO將文件讀取到ByteBuffer中,並以BlockData格式返回。

NIO寫入方法putBytes & put

putBytes方法實際是調用put方法實現的。put方法通過DiskBlockManager的getFile方法獲取文件,然後將數據寫入文件中。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章