HBase相对复杂,想要快速啃下来比较困难。而MiniBase吸收了HBase最核心的引擎部分的精华,希望可以通过学习MiniBase以小见大,能够对自己理解HBase这个庞然大物有所帮助。
MiniBase是一个标准的LSM树索引结构,分内存部分和磁盘部分。
从源码不难看出,其实就是2个ConcurrentSkipListMap kvMap和snapshot,flush的时候将kvMap赋值给snapshot,然后启动一个新的ConcurrentSkipListMap
DiskFile由3种类型的数据块组成,分别是DataBlock、IndexBlock、MetaBlock。
IndexBlock:一个DiskFile内有且仅有一个IndexBlock,它主要存储多个DataBlock的索引数据。每个索引数据又包含4个字段:
一个DiskFile中有且仅有一个MetaBlock;同时MetaBlock是定长的,因此可以直接通过定位diskf ile.f ilesize - len(MetaBlock)来读取MetaBlock,而无需任何索引.
假设用户需要读取指定DiskFile中key='abc'对应的value数据,那么可以按照如下流程进行IO读取
在MiniBase中,只容许两种更新操作:
private KeyValue(byte[] key, byte[] value, Op op, long sequenceId) { assert key != null; assert value != null; assert op != null; assert sequenceId >= 0; this.key = key; this.value = value; this.op = op; this.sequenceId = sequenceId; }
KeyValue在MemStore和DiskFile中都是有序存放的,所以需要为KeyValue实现Comparable接口,如下所示:
@Override public int compareTo(KeyValue kv) { if (kv == null) { throw new IllegalArgumentException("kv to compare should not be null"); } int ret = Bytes.compare(this.key, kv.key); if (ret != 0) { return ret; } if (this.sequenceId != kv.sequenceId) { return this.sequenceId > kv.sequenceId ? -1 : 1; } if (this.op != kv.op) { return this.op.getCode() > kv.op.getCode() ? -1 : 1; } return 0; }
写入过程需要构造一个kv结构(put/delete),并加上一个自增的sequenceId.详见MStore#put
@Override public void put(byte[] key, byte[] value) throws IOException { this.memStore.add(KeyValue.createPut(key, value, sequenceId.incrementAndGet())); }
(MiniBase是本地生成,HBase应该要由服务端生成?)
kv数据是写到kvMap中,其中kvMap就是ConcurrentSkipListMap结构。这里我们需要关注:
这里详见:MemStore#add
public void add(KeyValue kv) throws IOException { // add前需要阻塞flush flushIfNeeded(true); updateLock.readLock().lock(); try { KeyValue prevKeyValue; // ConcurrentSkipListMap特性 put后的返回值 the old value, or null if newly inserted if ((prevKeyValue = kvMap.put(kv, kv)) == null) { // 之前kv不存在则直接加 dataSize.addAndGet(kv.getSerializeSize()); } else { // 之前kv存在,需要计算差值(可能有更新) dataSize.addAndGet(kv.getSerializeSize() - prevKeyValue.getSerializeSize()); } } finally { updateLock.readLock().unlock(); } flushIfNeeded(false); }
写入MemStore后,当数据量达到一定阈值就要将flush到DiskStore
读取流程相对要复杂很多。
我们需要从多个有序集合中读取数据:
在Scan的时候,需要把多个有序集合通过多路归并算法合并成一个有序集合,然后过滤掉不符合条件的版本,将正确的KV返回给用户。
以上图为例,我们要将上面7个KV数据再次处理得到最终的结果。
对于同一个Key的不同版本,我们只关心最新的版本。假设用户读取时能看到的sequenceld≤101的数据,那么读取流程逻辑如下:
对于全表扫描的scan操作,MiniBase将返回(B,Put,101)和(C,Put,95)这两个KeyValue给用户。
详情见: MStore#ScanIter
原创声明,本文系作者授权云+社区发表,未经许可,不得转载。
如有侵权,请联系 yunjia_community@tencent.com 删除。
我来说两句