Spark之BlockManager内核解析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34993631/article/details/87463106

Spark之BlockManager内核解析

BlockManager的基本结构与流程

BlockManager这一套解决方案主要是为了提供Spark运行过程中数据的读取与存储。这一套解决方案的整体架构如下:

①在Driver端的DAGScheduler中会有BlockManagerMaster它的任务就是管理各个节点上BlockManager上的元数据。BlockManagerInfo就是一个BlockManager的信息,它里面会封装一个数据块的状态也就是BlockStatus。

②在BlockManager中也有一些组件,比如说不同持久化级别的写相关的组件如DiskStore与MemoryStore。比如说我们在写数据的时候如果内存都用的话我们就将数据写入内存,否则就将数据写入磁盘。

由于我们需要的数据不一定在本地,所以BlockManager也经常去远程拉取数据这时候就会用到与远程机器连接的组件ConnectionManager。也会使用BlockTransferService从远程读写数据。除此之外BlockTransferService也可以用来做数据的备份,因为它的本质就是数据的远程写入。

③当BlockManager启动之后会向BlockManagerMaster进行注册。这时BlockManagerMaster就会为当前这个BlockManager创建一个BlockManagerInfo属于这个BlockManager的元信息,而BlockManager中会有每个数据块的状态信息BlockStatus。每当BlockManager中的数据发生变化,对应的BlockManagerInfo的信息也会改变。

BlockManager内核关键节点

BlockManager的注册机制

  • 首先在BlockManagerMaster中有两个核心的映射ExecutorId与BlockManagerId,BlockManagerId与BlockManagerInfoId。同时指出凡是要用到数据读取的地方都会用到BlockManager,这也就说明了一个BolckManager会和一个Drvier或者是Executor有关联。实务上Spark是通过一个节点上的一个ExecutorId来标识一个BlockManager的。
  • 每当新的BlockManager向BlockManagerMaster注册的时候,也就是BlockManager在向BlockManagerMasterActor发送注册消息。这时Spark就会检查这个BlockManager对应的编号在映射中是否存在,在这些映射中是否有不合法的映射如果有则会将对应的映射删除。如果注册消息通过了安全监测这时就会为这个BlockManager创建对应的映射,在blockManagerIdByExecutor中保存一份ExecutorId—blockManagerId;在blockManagerInfo中保存一份blockManagerId—blockManagerInfo。

更新机制

具体来说就是在BlockManager的block发生变化时就会调用updateBlock请求来BlockManagerMaster这里进行BlockInfo的更新。而每一个block可能会出现在好几个BlockManager中,实务上一个blockId会对应一个locations的set集合里面存放着block的位置信息。同时BlockManager的BlockManagerInfo的updateBlockInfo()方法更新block的信息。

BlockManager的远程存取

BlockManager运行在各个节点上,driver与Executor都有一份主要提供了在本地或者远程存取数据的功能另一方面支持内存、磁盘、堆外存储。当我们要获取远程的数据时我们就要使用BlockTransferService组件。具体的我们需要一个坐标去定位数据host、port、executorId、blockId最后方法会返回blockResult,最后将获取到的数据进行反序列化。

扫描二维码关注公众号,回复: 5175048 查看本文章

BlockManager的数据读取

  • 读取本地数据getLocal()

在BlockManager内部维护着一个映射blockId—BlockInfo。其中BlockInfo封装着这个Block对应的真实数据的锁。所以当我们要对block进行操作的时候会首先得到它的锁才行这样才能避免线程安全问题。当有线程在读取Block的时候其它的线程都会等待BlockInfo的排他锁。如果这个线程始终没有获取到数据则会返回false。

当我们拿到锁的时候接下来就会正式的去读取数据。首先会获取这个block的持久化级别。如果持久化级别是内存,那么就会在MemoryStore中的entries(并发的LinkedHashMap)读取数据,这个集合里面装着真实的数据。最后BlockManager会将数据进行反序列化。如果是磁盘级别那么它的底层将会使用NIO与随机存取文件来读取数据。

  • 获得远程数据getRemote()

首先BlockManagerMaster上获取每个BlockId对应的BlockManager的信息。

遍历每一个BlockManager使用BlockTransferService进行异步的网络将block的数据传输回来。具体远程存取过程见上文。

 

BlockManager的数据写入

写数据的过程大致相同只是在持久化级别不同的地方需要加以描述。

  • 当要将一个Block写入时首先创建一个blockInfo放到Map集合中。并且对BlockInfo加锁实现同步。
  • 选择一种持久化级别MemoryStore或者是DiskStore等。
  • 根据持久化级别和数据类型将数据放入store中。这里涉及到判满,如果满足条件就按照持久化级别写入。
  • 获取到一个Block对应的BlockStatus,调用reportBlockStatus()方法,将写入的block数据发送到BlockManagerMasterActor以便于进行元数据的维护。

 

判满方法与写入操作

 

如果是Disk级别那么直接通过NIO机制写入磁盘中。否则(也就是Memory级别)这里会有一个线程安全的判满方法ensureFreeSpace()去判断协调存储空间。这里的同步很重要,因为判满是很严格的,当内存到达临界值的时候仍然会由于线程安全的问题“放入”多个线程去判满。这样的话就可能会发生OOM异常。

那么如果没有内存空间了怎么办?首先Spark会从entries中移除部分的数据,将最老旧的数据进行出队列操作(这里只是计算没有真实的去删除)。若计算之后block能够放入的话那么就会调用dropFromMemory方法尝试将“出队列”的数据写入磁盘,但如果“出队列”的数据没有磁盘的持久化权限的话那么就会直接删除数据。然后创建一份MemoryEntry将内存数据放入entries中。

猜你喜欢

转载自blog.csdn.net/qq_34993631/article/details/87463106