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类,它的内部记录所有的写操作的数据,同时给这个类定义一些方法,可能包括两大类:
- 用于操作内部的数据,比如Delete(), Put(), Clear(), Content()等
- 用于查看及操作内部数据的元信息的方法,比如Count()返回内部写操作的个数,Size()返回这些操作写入数据的大小,SetSequence()用于设置此次操作的编号等
然后,在db->Write()方法中,实现具体写入到memtable的逻辑:
- 调用WriteBatch的Content()方法获取需要写入的数据
- 将数据依次写入到memtable中
从功能上看,这样做没有特别大的问题,能够实现我们需要的逻辑。但是leveldb的代码不是这样写的,跟上面这个思路相比,leveldb的实现体现了两个额外的考虑:
- 由于WriteBatch类需要暴露给用户代码使用,最好只提供必需的,核心的方法,即Delete, Put, Clear。其他方法不需要给用户调用,可以隐藏起来。
- 将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方法(只有这两种操作)来处理。这样的话,就把实际写入的逻辑拆分为了两部分:
- 一部分是通用的遍历iterate
- 另外一部分是一个抽象处理接口Handler,可以使用不同的逻辑来实现这个接口
leveldb的单测代码中,实现了另外一个WriteBatch::Handler,用于将数据写入到一个测试用的存储中。定义一个接口,然后在这个接口之下使用不同的实现,这种做法类似策略模式(stragtegy),根据GOF上面的介绍,strategy的意图如下:
定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换。本模式使得算法可以独立使用它们的客户而变化。
可以看出来,leveldb的实现很注意封装和抽象,这正是面向对象设计的核心原则。按照这种方式写的代码,更容易进行维护和升级,也可以极大的避免后续代码变得臃肿冗长,杂乱无章。最重要的是,这样的代码相比一个大几百行的函数,更容易理解。
网友评论