`
半点玻璃心
  • 浏览: 26667 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

hbase MemStoreLAB代码浅析-1

阅读更多
本文基于 hbase 0.98x,如果发现源码与你的副本不符合,请检查代码版本。
首先看看 Memstore 的maybeCloneWithAllocator方法
Memstore#maybeCloneWithAllocator
private KeyValue maybeCloneWithAllocator(KeyValue kv) {
    if (allocator == null) {//如果没有启用 mslab,就返回原始对象。
      return kv;
    }

    int len = kv.getLength();//计算对象长度
    Allocation alloc = allocator.allocateBytes(len);//尝试分配内存
    if (alloc == null) {
      // The allocation was too large, allocator decided
      // not to do anything with it.
      return kv;//分配失败,只能返回原始对象。
    }
    assert alloc.getData() != null;
    System.arraycopy(kv.getBuffer(), kv.getOffset(), alloc.getData(), alloc.getOffset(), len);//分配成功则把原始数据拷贝到 mslab 中
    KeyValue newKv = new KeyValue(alloc.getData(), alloc.getOffset(), len);//创建新的 kv 对象,并添加 mslab 的数据引用指针。
    newKv.setMvccVersion(kv.getMvccVersion());
    return newKv;
  }

allocator就是是一个MemStoreLAB对象,MemStoreLAB以指针碰撞的方式分配连续的内存,每次都以2MB 的大小分配内存(可以通过hbase.hregion.memstore.mslab.chunksize配置),即一个 Chunck。
现在看看他的allocateBytes方法
  public Allocation allocateBytes(int size) {
    Preconditions.checkArgument(size >= 0, "negative size");

    // Callers should satisfy large allocations directly from JVM since they
    // don't cause fragmentation as badly.
    if (size > maxAlloc) {       //这个值由hbase.hregion.memstore.mslab.max.allocation指定,默认256KB
          return null;
    }

    while (true) {
      Chunk c = getOrMakeChunk();//这里用了一个类似于乐观锁

      // Try to allocate from this chunk
      int allocOffset = c.alloc(size);
      if (allocOffset != -1) {
        // We succeeded - this is the common case - small alloc
        // from a big buffer
        return new Allocation(c.data, allocOffset);
      }

      // not enough space!
      // try to retire this chunk
      tryRetireChunk(c);
    }
  }
   /**
     * Get the current chunk, or, if there is no current chunk,
     * allocate a new one from the JVM.
     */
    private Chunk getOrMakeChunk() {
        while (true) {
            // Try to get the chunk
            Chunk c = curChunk.get();
            if (c != null) {
                return c;
            }

            // No current chunk, so we want to allocate one. We race
            // against other allocators to CAS in an uninitialized chunk
            // (which is cheap to allocate)
            //如果有 chunkpool,则尝试先从 chunkpool 中分配,否则先创建一个。chunkpool 的结构与作用这里先按下不表
            c = (chunkPool != null) ? chunkPool.getChunk() : new Chunk(chunkSize);
            if (curChunk.compareAndSet(null, c)) {
                //尝试将当前活跃 chunk 设置为我们获取到的对象,如果成功,这初始化该对象,给对象分配数据内存在这里完成(init 方法),并将这个对象添加到持有队列中
                // we won race - now we need to actually do the expensive
                // allocation step
                c.init();
                this.chunkQueue.add(c);
                return c;
            } else if (chunkPool != null) {//如果设置失败,说明出现了条件竞争。则需要放回到 chunkpool 中等待复用。
                chunkPool.putbackChunk(c);
            }
            // someone else won race - that's fine, we'll try to grab theirs
            // in the next iteration of the loop.
        }
    }

这段代码很简单,就是通过 CAS 的方式,尝试分配一段内存。
public int alloc(int size) {
            while (true) {
                int oldOffset = nextFreeOffset.get();
                if (oldOffset == UNINITIALIZED) {
                    // The chunk doesn't have its data allocated yet.
                    // Since we found this in curChunk, we know that whoever
                    // CAS-ed it there is allocating it right now. So spin-loop
                    // shouldn't spin long!
                    Thread.yield();
                    continue;
                }
                if (oldOffset == OOM) {
                    // doh we ran out of ram. return -1 to chuck this away.
                    return -1;
                }

                if (oldOffset + size > data.length) {
                    return -1; // alloc doesn't fit
                }

                // Try to atomically claim this chunk
                if (nextFreeOffset.compareAndSet(oldOffset, oldOffset + size)) {
                    // we got the alloc
                    allocCount.incrementAndGet();
                    return oldOffset;
                }
                // we raced and lost alloc, try again
            }
        }


上一段getOrMakeChunk的代码中可以看到,先尝试将curChunk指向一个新的 chunk,再进行初始化工作。
因此,如果两个线程在这期间同时拿到了这个 chunk,则可能某一个线程拿到一个还没有初始化完毕的对象,即 init 方法还未来得及执行或未执行完。
因此这里单独考虑了这种情况,并将线程挂起。

这里说一个题外话,既然已经是 CAS,为什么不直接 continue 循环而是将线程挂起呢?大家都知道,一般来说java里面无锁算法更类似于乐观锁,本质上采用cas+ self spinning(即循环,一般装逼叫自旋,下文将启动装逼模式) 的方式。
这样做的好处就是,让线程一直在内核中运行,自旋牺牲一定的 CPU 时间片,但是节省了切换线程上下文的开销。
通常情况下,这样的方式成本远远大于收益。但是,注意这里说的是通常情况下。在极端并发的情况下,通过简单的几次循环无法成功,这样就导致了两个问题:
其一是自旋次数太多,其运行期间消耗的资源可能超过线程上下文切换的开销。其二是自旋理论上来说是死循环,与线程挂起不同,他是消耗 CPU 时间片的,大家都见过死循环导致 CPU 利用率100%的情况了。
这就意味着自旋的线程不会主动释放 CPU 资源,导致其他的线程出现饥饿。
基于上面的分析,在高度或极端并发的情况下,采用自旋实现的无锁,效率会高于使用锁机制。
不要说 sleep,sleep 有两种参数,一个是毫秒,这个就不用说了,1ms 其实已经足够 CPU 完成海量的操作。
另一个是纳秒。很不幸,纳秒的获取精确度依赖操作系统的实现甚至是硬件时钟,最简单的例子,macos 操作系统上调用 System.nantoTimes 返回值总是1000的倍数。
因此这里采用了yield方式,让系统来决定线程的恢复时间。这样做最简单,也最明智。

至此看到了如何获取一个 chunk,以及成功后如何在 chunk上分配内存。下面分析下如果在 chunk 上分配内存失败的代码:

private void tryRetireChunk(Chunk c) {
        curChunk.compareAndSet(c, null);
        // If the CAS succeeds, that means that we won the race
        // to retire the chunk. We could use this opportunity to
        // update metrics on external fragmentation.
        //
        // If the CAS fails, that means that someone else already
        // retired the chunk for us.
    }

代码好简单,就是将当前可分配的 chunk,即 curChunk 置为空。
因此,在整个 chunk 的内存都被释放前,即便这个 chunk 还有能力容纳其他更小的 cell,但是他的内存也无法被使用了,造成了一定的内存浪费。
所以,我们在设计表结构的时候,需要注意两个问题:一是单个 cell 的尺寸不宜太大,或者同一个CF 下不同cell的容量差异过大,当然,千万不要超过256KB(可配置)。其二就是hbase.hregion.memstore.mslab.chunksize配置的大小,尽量取大于 cell 平均大小的整数倍的最小值,当然,这取决于第一点你是否做到了。
分享到:
评论

相关推荐

    hbase-hadoop2-compat-1.2.12-API文档-中文版.zip

    赠送源代码:hbase-hadoop2-compat-1.2.12-sources.jar; 赠送Maven依赖信息文件:hbase-hadoop2-compat-1.2.12.pom; 包含翻译后的API文档:hbase-hadoop2-compat-1.2.12-javadoc-API文档-中文(简体)版.zip; Maven...

    hbase-hadoop2-compat-1.1.3-API文档-中文版.zip

    赠送源代码:hbase-hadoop2-compat-1.1.3-sources.jar; 赠送Maven依赖信息文件:hbase-hadoop2-compat-1.1.3.pom; 包含翻译后的API文档:hbase-hadoop2-compat-1.1.3-javadoc-API文档-中文(简体)版.zip; Maven...

    hbase-hbck2-1.1.0-SNAPSHOT.jar

    hbase-hbck2-1.1.0-SNAPSHOT.jar

    ycsb-hbase14-binding-0.17.0

    ycsb-hbase14-binding-0.17.0

    HBase(hbase-2.4.9-bin.tar.gz)

    HBase(hbase-2.4.9-bin.tar.gz)是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的Google论文“Bigtable:一个结构化数据的分布式存储系统”。就像Bigtable利用了Google文件系统(File System...

    hbase-hadoop2-compat-1.1.3-API文档-中英对照版.zip

    赠送源代码:hbase-hadoop2-compat-1.1.3-sources.jar; 赠送Maven依赖信息文件:hbase-hadoop2-compat-1.1.3.pom; 包含翻译后的API文档:hbase-hadoop2-compat-1.1.3-javadoc-API文档-中文(简体)-英语-对照版.zip...

    hbase的hbase-1.2.0-cdh5.14.2.tar.gz资源包

    hbase的hbase-1.2.0-cdh5.14.2.tar.gz资源包

    hbase-hbck2-1.0.0.jar

    Hbase修复工具 示例情景: Q:缺失hbase.version文件 A:加上选项 -fixVersionFile 解决 Q:如果一个region即不在META表中,又不在hdfs上面,但是在regionserver的online region集合中 A:加上选项 -...

    hbase-hadoop2-compat-1.4.3-API文档-中文版.zip

    赠送源代码:hbase-hadoop2-compat-1.4.3-sources.jar; 赠送Maven依赖信息文件:hbase-hadoop2-compat-1.4.3.pom; 包含翻译后的API文档:hbase-hadoop2-compat-1.4.3-javadoc-API文档-中文(简体)版.zip; Maven...

    hbase-hadoop2-compat-1.2.12-API文档-中英对照版.zip

    赠送源代码:hbase-hadoop2-compat-1.2.12-sources.jar; 赠送Maven依赖信息文件:hbase-hadoop2-compat-1.2.12.pom; 包含翻译后的API文档:hbase-hadoop2-compat-1.2.12-javadoc-API文档-中文(简体)-英语-对照版....

    hbase-prefix-tree-1.1.3-API文档-中文版.zip

    赠送源代码:hbase-prefix-tree-1.1.3-sources.jar; 赠送Maven依赖信息文件:hbase-prefix-tree-1.1.3.pom; 包含翻译后的API文档:hbase-prefix-tree-1.1.3-javadoc-API文档-中文(简体)版.zip; Maven坐标:org....

    hbase2.x-hbck2 jar包及测试命令

    hbase2.x-hbck2 jar包及测试命令

    hbase-hadoop-compat-1.1.3-API文档-中文版.zip

    赠送源代码:hbase-hadoop-compat-1.1.3-sources.jar; 赠送Maven依赖信息文件:hbase-hadoop-compat-1.1.3.pom; 包含翻译后的API文档:hbase-hadoop-compat-1.1.3-javadoc-API文档-中文(简体)版.zip; Maven坐标:...

    hbase-client-2.1.0-cdh6.3.0.jar

    hbase-client-2.1.0-cdh6.3.0.jar

    hbase-metrics-api-1.4.3-API文档-中文版.zip

    赠送源代码:hbase-metrics-api-1.4.3-sources.jar; 赠送Maven依赖信息文件:hbase-metrics-api-1.4.3.pom; 包含翻译后的API文档:hbase-metrics-api-1.4.3-javadoc-API文档-中文(简体)版.zip; Maven坐标:org....

    phoenix-hbase-2.2-5.1.2-bin.tar.gz

    phoenix-hbase-2.2-5.1.2-bin.tar.gz

    hbase-hadoop-compat-1.1.3-API文档-中英对照版.zip

    赠送源代码:hbase-hadoop-compat-1.1.3-sources.jar; 赠送Maven依赖信息文件:hbase-hadoop-compat-1.1.3.pom; 包含翻译后的API文档:hbase-hadoop-compat-1.1.3-javadoc-API文档-中文(简体)-英语-对照版.zip; ...

    hbase-hbck2-1.2.0-SNAPSHOT.jar

    HBCK是HBase1.x中的命令,到了HBase2.x中,HBCK命令不适用,且它的写功能(-fix)已删除;...其GitHub地址为:https://github.com/apache/hbase-operator-tools.git 附件资源是已经编译好的hbase2.4.4版本的hbck

    hbase-prefix-tree-1.4.3-API文档-中文版.zip

    赠送源代码:hbase-prefix-tree-1.4.3-sources.jar; 赠送Maven依赖信息文件:hbase-prefix-tree-1.4.3.pom; 包含翻译后的API文档:hbase-prefix-tree-1.4.3-javadoc-API文档-中文(简体)版.zip; Maven坐标:org....

    hbase-meta-repair-hbase-2.0.2.jar

    HBase 元数据修复工具包。 ①修改 jar 包中的application.properties,重点是 zookeeper.address、zookeeper.nodeParent、hdfs....③开始修复 `java -jar -Drepair.tableName=表名 hbase-meta-repair-hbase-2.0.2.jar`

Global site tag (gtag.js) - Google Analytics