Spark存储原理——数据写入过程

        Spark数据的写入过程的入口点位doPutIterator方法。下面是一些方法的调用关系图:

        在该方法中,根据数据是否缓存到内存中处理。如果不缓存到内存中,则调用BlockManager的putIterator方法直接存储到磁盘中;如果缓存到内存中,则先判断数据存储级别是否对数据进行了反序列化操作:如果设置了反序列化操作,则调用putIteratorAsValues方法,直接操作值类型数据;如果没有反序列化,则调用putIteratorAsBytes方法,操作字节类型数据。在将数据写入到内存中时,还需要判断在内存展开(unroll)该数据的大小是否足够,当内存足够时,写入内存,否则写入到磁盘。写入完成时,一方面将数据的元数据信息发送给Driver终端,请求更新元数据信息,另一方面判断是否需要创建数据副本,如果需要,则调用replicate方法,通过Netty方式写到远程节点上。下面是写数据逻辑的简易逻辑图:

一、写入内存

Spark内存结构图:

        Spark内存大致分为两部分:图中下半部分是已经使用的内存,内存里存放entries中,该entries由不同数据块生成的MemoryEntry构成;图中下半部分为可用内存,这些内存用于尝试展开数据块(展开的动作并不是直接写数据,而是占位置),这里面并不是一下子将所有数据展开到内存中,而是进行每一步操作时,先检查内存大小是否足够,如果内存大小不够,则需要先对内存的某些数据释放,给准备写入的数据提供充足的空间,而释放的数据则被写入到磁盘中。那么选择哪些内存数据进行释放呢?在内存中的数据,是以LinkedHashMap保存,保存了数据的插入顺序,根据FIFO策略进行计算释放,释放的空间足够时,就将内存释放的数据写入到磁盘中。

数据写入内存过程:

  1. 获取信息:在数据块展开前,为该展开线程获取初始化内存,该内存大小为unrollMemoryThreshold,获取完毕后返回是否成功的结果keepUnrolling;
  2. 遍历:如果Iterator[T]存在元素且keepUnrolling为true,则继续向前遍历Iterator[T],内存展开元素的数量elementsUnrolled自增1。如果Iterator[T]到头或者keepUnrolling为false,则调到步骤4;
  3. 检测内存:每当memoryCheckPeriod继16次展开之后,就进行一次内存检测,检测展开的内存大小是否超过当前分配的内存。如果没有超过,则继续展开;如果内存不足,则根据增长因子计算需要增加的内存大小。如果申请成功,则将申请得到的内存加入到CurrentUnrollMemory,而展开线程获取的内存大小为:当前展开大小*内存增长因子;
  4. 展开内存:判断数据块是否展开成功,如果失败,则记录为内存不足,并退出;如果成功,则继续下一步;
  5. 申请内存:先估算该数据块在内存中的存储大小,然后比较数据块展开的内存和数据块在内存中的存储大小,如果数据块展开的内存<=数据块存储大小,说明内存不足,需要申请他们之间的差值大小的内存。如果申请成功,则调用transferUnrollToStorage方法处理;如果数据块展开的内存>数据块存储的大小,那么先释放多余内存,在调用transferUnrollToStorage方法;
  6. 在transferUnrollToStorage方法中释放该数据块在内存展开的空间,然后判断内存是否足够写入数据。如果足够,则将数据块放到内存里,否则,发送写入内存失败的信息。

二、写入磁盘

        写入磁盘的逻辑就相对简单得多,调用DiskStore的put方法,该方法提供了写文件的回调方法writeFunc。该方法先获取写入文件句柄,然后将数据序列化为数据流,最后根据回调方法写入文件中。

猜你喜欢

转载自www.cnblogs.com/SysoCjs/p/11466320.html