levelDB WriteBatch源码解析

Posted 叶长风

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了levelDB WriteBatch源码解析相关的知识,希望对你有一定的参考价值。

levelDB WriteBatch源码解析

上一篇文章中讲了leveldb中WriteBatch、Snapshot使用,但是leveldb毕竟只是一个基础的存储引擎,没有太多的特性或者api使用可以讲,因此便直接从源码上开始,我这就从WriteBatch开始吧。有一些遗漏的以后补充进来,以后再慢慢进行整理。

WriteBatch源码


我们先看下WriteBatchWritebatch本身只是一个接口,只提供了两个方法的定义,如下:

package org.iq80.leveldb;

import java.io.Closeable;

/**
 * @author <a href="http://hiramchirino.com">Hiram Chirino</a>
 */
public interface WriteBatch
        extends Closeable

    WriteBatch put(byte[] key, byte[] value);

    WriteBatch delete(byte[] key);

WriteBatch的实现类为WriteBatchImpl,leveldb在java源码实现中还有另外一个实现,是leveldb-jni,对应的实现类为JniWriteBatch,简单的看了下,感觉要比当前源码简单一些,有兴趣的可以了解一下。

接下来看WriteBatchImpl类的源码,代码不多,如下:

public class WriteBatchImpl
        implements WriteBatch

    private final List<Entry<Slice, Slice>> batch = new ArrayList<>();
    private int approximateSize;

    public int getApproximateSize()
    
        return approximateSize;
    

    public int size()
    
        return batch.size();
    

    @Override
    public WriteBatchImpl put(byte[] key, byte[] value)
    
        requireNonNull(key, "key is null");
        requireNonNull(value, "value is null");
        batch.add(Maps.immutableEntry(Slices.wrappedBuffer(key), Slices.wrappedBuffer(value)));
        approximateSize += 12 + key.length + value.length;
        return this;
    

    public WriteBatchImpl put(Slice key, Slice value)
    
        requireNonNull(key, "key is null");
        requireNonNull(value, "value is null");
        batch.add(Maps.immutableEntry(key, value));
        approximateSize += 12 + key.length() + value.length();
        return this;
    

    @Override
    public WriteBatchImpl delete(byte[] key)
    
        requireNonNull(key, "key is null");
        batch.add(Maps.immutableEntry(Slices.wrappedBuffer(key), (Slice) null));
        approximateSize += 6 + key.length;
        return this;
    

    public WriteBatchImpl delete(Slice key)
    
        requireNonNull(key, "key is null");
        batch.add(Maps.immutableEntry(key, (Slice) null));
        approximateSize += 6 + key.length();
        return this;
    

    @Override
    public void close()
    
    

    public void forEach(Handler handler)
    
        for (Entry<Slice, Slice> entry : batch) 
            Slice key = entry.getKey();
            Slice value = entry.getValue();
            if (value != null) 
                handler.put(key, value);
            
            else 
                handler.delete(key);
            
        
    

    public interface Handler
    
        void put(Slice key, Slice value);

        void delete(Slice key);
    

首先可以看到WriteBatch中有一个变量为batch用来保存每一次的操作,还有一个变量为approximateSize,用来保存key和value的大小。

private final List<Entry<Slice, Slice>> batch = new ArrayList<>();
    private int approximateSize;

实现的这些方法中,我们首先看第一个put方法,put方法如下:

@Override
    public WriteBatchImpl put(byte[] key, byte[] value)
    
        requireNonNull(key, "key is null");
        requireNonNull(value, "value is null");
        batch.add(Maps.immutableEntry(Slices.wrappedBuffer(key), Slices.wrappedBuffer(value)));
        approximateSize += 12 + key.length + value.length;
        return this;
    

这里batch add了一个不可变的Entry,同时将key和value进行了一层Slice的包装,但在approximateSize值不仅增加了key和value的length,同时增加了12,这个最开始我以为需要看下Slices的源码,应该是和Slice的方法有关系,wrappedBuffer源码如下:

public static Slice wrappedBuffer(byte[] array)
    
        if (array.length == 0) 
            return EMPTY_SLICE;
        
        return new Slice(array);
    

Slice的源码为:

private final byte[] data;
    private final int offset;
    private final int length;

    private int hash;

    public Slice(byte[] data)
    
        requireNonNull(data, "array is null");
        this.data = data;
        this.offset = 0;
        this.length = data.length;
    

如上,Slice包装byte数组后,同时增加一个offset记载偏移量与增加length记录数组长度,并增加hash记录hash值,但是此处在WriteBatch保存kv时与这些应该是无关的,因此应该与这无关,最后上网查询、查看原c++的源码,里面在存储当前字符串时,需要有一个8字节的序列号和4字节的记录数作为头,因此在申请空间存放的时候要多加上12个字节的大小,因此对于插入的记录:

由kTypeValue+key长度+key+value长度+value组成

delete方法与put方法类似,在这就不再详述了,再说一下最后的foreach和Handler接口,foreach为:

public void forEach(Handler handler)
    
        for (Entry<Slice, Slice> entry : batch) 
            Slice key = entry.getKey();
            Slice value = entry.getValue();
            if (value != null) 
                handler.put(key, value);
            
            else 
                handler.delete(key);
            
        
    

就是for循环依次处理add进来的key与value。

Handler为:

public interface Handler
    
        void put(Slice key, Slice value);

        void delete(Slice key);
    

我们在查询源码,可知在DbImpl中使用上述foreach与Handler,源码如下:

 private Slice writeWriteBatch(WriteBatchImpl updates, long sequenceBegin)
    
        Slice record = Slices.allocate(SIZE_OF_LONG + SIZE_OF_INT + updates.getApproximateSize());
        final SliceOutput sliceOutput = record.output();
        sliceOutput.writeLong(sequenceBegin);
        sliceOutput.writeInt(updates.size());
        updates.forEach(new Handler()
        
            @Override
            public void put(Slice key, Slice value)
            
                sliceOutput.writeByte(VALUE.getPersistentId());
                writeLengthPrefixedBytes(sliceOutput, key);
                writeLengthPrefixedBytes(sliceOutput, value);
            

            @Override
            public void delete(Slice key)
            
                sliceOutput.writeByte(DELETION.getPersistentId());
                writeLengthPrefixedBytes(sliceOutput, key);
            
        );
        return record.slice(0, sliceOutput.size());
    

这个方法就是对之前的数据进行批量的操作,持久化存储或者删除等操作。


WriteBatch的demo演示就不再写了,之前文章中写过一回,对WriteBatch的源码解析就到这,下一篇文章讲Snapshot。

以上是关于levelDB WriteBatch源码解析的主要内容,如果未能解决你的问题,请参考以下文章

[LevelDB] LevelDB之WriteBatch

LevelDB源码分析-Write

LSM-Tree - LevelDb 源码解析

leveldb - 并发写入处理

[LevelDB] 写批处理过程详解

leveldb中WriteBatchSnapshot使用