zoukankan      html  css  js  c++  java
  • 继续聊聊HDFS BlockManager扩展性问题

    前言


    最近一段时间,笔者在业余时间继续对HDFS的扩展性内容进行学习。一句话来概括,越往里深入,越发现里面可以讲的东西越多,涉及到的点也越多。社区在这个方面确实做了很多的讨论。本文所讲述的主题源自于社区JIRA HDFS-7844(Create an off-heap hash table implementation),大意是利用堆外内存(off-heap)来进行元数据的存储。之前我们总是提到的NameNode内存空间使用过大,指的是JVM中的堆内存。而堆外内存是不受JVM管理的,它由操作系统来管理。如果将Block、INode中的数据从堆内移到堆外存储,无疑将会巨大减轻NameNode压力。在下文中,笔者会花一定篇幅介绍此内容,并同时介绍HDFS块优化相关的内容。

    堆外内存概述


    堆外内存,通俗地理解,就是不受JVM控制的内存。堆外内存在使用上能支持更大空间的内存存储。还有一点好处是堆外内存可以在JVM间共享,减少对象的复制。

    在现有JDK中,有2种可以对堆外内存进行使用的API。

    第一种,nio包中的ByteBuffer相关的API。如下代码示例:

    ByteBuffer buffer = ByteBuffer.allocateDirect(10 * 1024 * 1024);

    第二种,调用Unsafe方法进行堆外内存的使用,示例代码如下:

    Unsafe.allocateMemory(1024 * 1024 * 20);

    以上2种方法的使用类似于C语言中的malloc方法。

    关于堆外内存更多的资料大家可以自行查找资料进行学习。

    HDFS堆外内存哈希存储的设计


    在了解完堆外存储的基本概念之后,我们重新回到本文的主题。在HDFS-7844中,以off-heap的方式实现了一个哈希存储表。作者在设计此存储表的目的是为了将来存Block块数据、INode文件数据做使用的。

    在原设计中,作者并不是直接在哈希表上对内存做直接管理,而是先定义内存管理器类,此内存管理器类是可配的,在这里它有可能就是off-heap的。在内存管理器内部,才是对数据进行直接存入、取出的操作

    相比于off-heap的内存管理器,on-heap的内存管理器显得更复杂一些,它的内部定义了这么几个变量:

    1.当前分配空间的起始地址(此地址会随着空间不断分配出去而后移)。
    2.最大分配地址。此处我们基本可以认定为无上限,除非我们想要额外指定。
    3.buffer map数据。此map存储了起始存储地址到缓存数据的映射。

    通过如上3个变量的定义,我们大致可以勾画出下面的一张结构图。



    图1-1 内存管理结构图

    上图可理解为在一大片的存储空间内,被分割为了各个小的buffer缓冲,这些buffer缓冲数据由他们的起始存储地址作为key进行定位查找。

    而off-heap的内存管理则显得简单许多,下面列举部分利用堆外内存进行内存分配管理的代码:

    下面首先是定义以及初始化方法,

    /**
      * NativeMemoryManager is a memory manager which uses sun.misc.Unsafe to
      * allocate memory off-heap.  This memory will be allocated using the current
      * platform's equivalent of malloc().
      */
     @Private
     @Unstable
     public class NativeMemoryManager implements MemoryManager {
      static final Logger LOG =
          LoggerFactory.getLogger(NativeMemoryManager.class);
    
       private final static Unsafe unsafe;
    
       private final static String loadingFailureReason;
    
       static {
         Unsafe myUnsafe = null;
         String myLoadingFailureReason = null;
         try {
           Field f = Unsafe.class.getDeclaredField("theUnsafe");
           f.setAccessible(true);
           myUnsafe = (Unsafe)f.get(null);
         } catch (Throwable e) {
           myLoadingFailureReason = e.getMessage();
         } finally {
           unsafe = myUnsafe;
           loadingFailureReason = myLoadingFailureReason;
         }
       }

    用Unsafe方法分配地址空间时,会在方法结束后返回一个起始地址,于是这边就不需要人工地进行地址的累加。

       public long allocate(long size) {
         return unsafe.allocateMemory(size);
       }

    然后是数据存取方法,

       @Override
       public byte getByte(long addr) {
         return unsafe.getByte(null, addr);
       }
    
       @Override
       public void putByte(long addr, byte val) {
         unsafe.putByte(null, addr, val);
       }

    如果不使用此空间,则可以进行空间的释放。

        public void free(long addr) {
         unsafe.freeMemory(addr);
       }

    假设目前我们已经构建完这块哈希存储表了,我们如何将它有效的运用到目前的Block块数据或者INode文件中呢?首先它可以很好的适配当前DataNode的块组织结构。之前笔者写过一个篇文章介绍过HDFS内部块的组织结构,Block块在DataNode内部被组织为若干个超级大的“链表”,此“链表”并不是由于JDK自带的LinkedList相关类实现的,而是HDFS内部自身实现了一套。如果本文提到的哈希存储表未来能够成功应用到HDFS上,这些链表上的数据将会很好的迁移到哈希表上。

    而对于哈希表的数目,我们当然不能仅限只有一个,因为这会带来一定的锁竞争。我们可以维护多个哈希存储表。每次有相应存储表的块数据报告上来的时候,我们就单独锁住需要更新的哈希表即可。

    其它优化点


    在HDFS-7836的设计文档中,还提到了2点关于块汇报相关的优化点。

    第一点,块汇报的拆分。在比较早些时候的块报告的实现中,DataNode每次心跳发送块报告的时候会将所有块信息放在一个message中,一次性发送给NameNode。这会带来很大弊端,如果数据多了,这个信息将会非常大,不仅会导致信息发送慢,还会导致NameNode处理慢。一种更好的做法是将这些storage report进行拆分,每次发送一小部分,分多次发送。在目前HDFS中,已经有类似的配置功能,配置项dfs.blockreport.split.threshold。此配置的意思是给定一个阈值,如果DataNode上总的块数小于此阈值,则块数据分一次性发送,否则按照每个存储目录划分,分多次汇报。

    第二点,块汇报信息的压缩。块信息的压缩处理看起来是一个不错的主意。而且目前已经有许多性能较好的压缩算法。如果我们对这些块数据进行了压缩发送,首先它能帮助我们减少很大一部分的网络IO。唯一不足之处可能就是它需要在NameNode端进行一次解压动作。但是以目前计算机处理的速度,这点处理对NameNode本身性能的影响应该是可以忽略的。但比较可惜的一点,据笔者对于此方面的了解,目前HDFS还没实现对于汇报块的压缩功能,大家也慢慢期待吧。

    参考资料


    [1].https://issues.apache.org/jira/browse/HDFS-7844
    [2].https://issues.apache.org/jira/browse/HDFS-7836
    [3].https://issues.apache.org/jira/secure/attachment/12700628/BlockManagerScalabilityImprovementsDesign.pdf

  • 相关阅读:
    unnitest简单场景应用
    接口基础之request
    docker常用命令
    管理之心理学
    管理团队挑战和提升
    如何留下核心成员
    管理之面试技巧
    复杂接口请求怎样写http请求
    gitlab使用(一)
    不使用AutoLayout快速兼容适配iPhone6/6 Plus
  • 原文地址:https://www.cnblogs.com/bianqi/p/12183696.html
Copyright © 2011-2022 走看看