从leveldb中学编码技巧

2019-05-23  本文已影响0人  wangjie_yy

leveldb是一个C++写的kv存储引擎, 作者是google的大神程序员Jeff Dean。leveldb的源码写的非常漂亮,干净整洁,通俗易懂。可以从中学到很多有用coding的技巧,这篇笔记通过讲解批量更新(WriteBatch)的实现来学习一下大神的代码设计思路。

leveldb支持多个write操作的原子更新,本质上是一个批量操作,使用起来大概是这样的:

#include "leveldb/write_batch.h"
...
leveldb::WriteBatch batch;
batch.Delete(key1);
batch.Put(key2, value);
s = db->Write(leveldb::WriteOptions(), &batch);

简单地说,就是将一批数据写入到内存中的memtable中。如果要实现这个批量操作,很容易想到的思路是:定义一个WriteBatch类,它的内部记录所有的写操作的数据,同时给这个类定义一些方法,可能包括两大类:

然后,在db->Write()方法中,实现具体写入到memtable的逻辑:

从功能上看,这样做没有特别大的问题,能够实现我们需要的逻辑。但是leveldb的代码不是这样写的,跟上面这个思路相比,leveldb的实现体现了两个额外的考虑:

  1. 由于WriteBatch类需要暴露给用户代码使用,最好只提供必需的,核心的方法,即Delete, Put, Clear。其他方法不需要给用户调用,可以隐藏起来。
  2. 将WriteBatch的数据写入到memtable中,这个逻辑可以抽象出来,做成容易替换的模式。这样如果后续需要将WriteBatch的数据写入到其他存储中,可以比较方便的改动代码。

看下leveldb是怎么做的:针对第一点,WriteBatch只提供了三个公有方法Put(), Delete(), Clear(),给用户代码使用。同时额外定义了一个WriteBatchInternal类,这个类的说明如下:

// WriteBatchInternal provides static methods for manipulating a
// WriteBatch that we don't want in the public WriteBatch interface.

对于第二点,在WriteBatch内部又定义了一个类,以及一个Iterate()方法,如下:

class WriteBatch {
public:
    ...
    class Handler {
    public:
        virtual ~Handler();
        virtual void Put(const Slice& key, const Slice& value) = 0;
        virtual void Delete(const Slice& key) = 0;    
    }
    Status Iterate(Handler* handler) const;
};

Iterate(handler)的逻辑是,遍历WriteBatch内部的数据,对每一条数据,调用Handler的Put或者Delete方法(只有这两种操作)来处理。这样的话,就把实际写入的逻辑拆分为了两部分:

leveldb的单测代码中,实现了另外一个WriteBatch::Handler,用于将数据写入到一个测试用的存储中。定义一个接口,然后在这个接口之下使用不同的实现,这种做法类似策略模式(stragtegy),根据GOF上面的介绍,strategy的意图如下:

定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换。本模式使得算法可以独立使用它们的客户而变化。

可以看出来,leveldb的实现很注意封装和抽象,这正是面向对象设计的核心原则。按照这种方式写的代码,更容易进行维护和升级,也可以极大的避免后续代码变得臃肿冗长,杂乱无章。最重要的是,这样的代码相比一个大几百行的函数,更容易理解。

上一篇 下一篇

猜你喜欢

热点阅读