本文基于 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-sources.jar; 赠送Maven依赖信息文件:hbase-hadoop2-compat-1.2.12.pom; 包含翻译后的API文档:hbase-hadoop2-compat-1.2.12-javadoc-API文档-中文(简体)版.zip; Maven...
hbase-hbck2-1.1.0-SNAPSHOT.jar
HBase(hbase-2.4.9-bin.tar.gz)是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的Google论文“Bigtable:一个结构化数据的分布式存储系统”。就像Bigtable利用了Google文件系统(File System...
赠送源代码: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...
HBCK是HBase1.x中的命令,到了HBase2.x中,HBCK命令不适用,且它的写功能(-fix)已删除;...其GitHub地址为:https://github.com/apache/hbase-operator-tools.git 附件资源是已经编译好的hbase2.4.4版本的hbck
HBCK2 jar包是这个工具的可执行文件,通常在HBase的lib目录下可以找到,名为`hbase-hbck2-x.x.x.jar`,其中`x.x.x`表示具体的HBase版本号。这个jar包包含了所有执行HBCK2命令所需的功能和类。你可以通过Hadoop的`...
1. **兼容性增强**:此版本的绑定确保了与HBase 1.4的完全兼容性,这意味着它可以充分利用HBase 1.4的新特性和性能优化。 2. **工作负载多样性**:YCSB提供了多种预定义的工作负载模型,如读多写少、写多读少、读写...
赠送源代码: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修复工具 示例情景: Q:缺失hbase.version文件 A:加上选项 -fixVersionFile 解决 Q:如果一个region即不在META表中,又不在hdfs上面,但是在regionserver的online region集合中 A:加上选项 -...
本文将深入探讨这两个技术及其结合体`phoenix-hbase-2.2-5.1.2-bin.tar.gz`的详细内容。 首先,HBase(Hadoop Database)是Apache软件基金会的一个开源项目,它构建于Hadoop之上,是一款面向列的分布式数据库。...
赠送源代码: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-client-2.1.0-cdh6.3.0.jar
赠送源代码: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-sources.jar; 赠送Maven依赖信息文件:hbase-prefix-tree-1.1.3.pom; 包含翻译后的API文档:hbase-prefix-tree-1.1.3-javadoc-API文档-中文(简体)版.zip; Maven坐标:org....
赠送源代码:flink-hbase_2.11-1.10.0-sources.jar; 赠送Maven依赖信息文件:flink-hbase_2.11-1.10.0.pom; 包含翻译后的API文档:flink-hbase_2.11-1.10.0-javadoc-API文档-中文(简体)版.zip; Maven坐标:org....
1. **解压**:首先,我们需要解压`hbase-1.2.0-cdh5.14.2.tar.gz`到一个适当的目录,例如`/usr/local/hbase`。 2. **配置环境变量**:在`~/.bashrc`或`~/.bash_profile`中添加HBase的路径到`PATH`和`HBASE_HOME`。 3...
hbase hbck2修复工具hbase-operator-tools-1.0.0.1.0.0.0-618-bin.tar.gz,hbase1版本的hbck已经不支持修复命令,hbase2.1版本需要用这个新版的工具
赠送源代码: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-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的深度解析:基于phoenix-hbase-1.4-4.16.1-bin的探讨》 Phoenix是一种开源的SQL层,它为Apache HBase提供了高性能的关系型数据库查询能力。在大数据领域,HBase因其分布式、列式存储的特性,常被...