Google的几个开源库

leveldb

leveldb文档里给出了详细的实现机制,这里概括一下最重要的合并(compaction)机制。为了提升写性能,数据先写到log files,此时只会更新memtable,默认到4MB后才写入level-0的sstable,这就把随机写转化成了顺序写。当默认超过4个level-0的sstable后,数据会被归并(merge)到level-1层的sstable(.sst文件默认2MB大小)。同时,leveldb有单独的后台进程对sstable进行compact,条件是level-L层的数据超过(10^L)MB之后。

compact过程产生的sstable都是有序文件,在查询的时候leveldb按照数据的key从低层到高层搜索,可以保证很高的读性能。不过compact本身还是比较耗IO的,因为归并排序需要把整个sstable读入内存,所以在导入数据的时候应该尽量避免写入速度超过compact的速度。

leveldb的性能可以参考其benchmark文档,具体使用的过程中,导入数据的确很快,主流服务器磁盘上几乎可以达到网卡上限;读数据时,并且只要在table cache里的数据延迟只要10ms左右,当然如果不在table cache里,这意味着需要打开sstable文件,最多大概要近1s左右的延迟。

leveldb::Slice

读操作的函数声明

Status Get(const ReadOptions& options, const Slice& key, std::string* value)

注意其中k/v的类型是不同的:

  • key → leveldb::Slice
  • value → std::string

其中Slice有如下三种构造函数:

// Create a slice that refers to d[0,n-1].
Slice(const char* d, size_t n) : data_(d), size_(n) { }

// Create a slice that refers to the contents of "s"
Slice(const std::string& s) : data_(s.data()), size_(s.size()) { }

// Create a slice that refers to s[0,strlen(s)-1]
Slice(const char* s) : data_(s), size_(strlen(s)) { }

【注意】Slice不会分配内存去管理key,而是直接复用你的赋值。

如下的用法是 错误 的(因为string是个临时变量,它的作用域仅在这行赋值语句中):

Slice key(std::string("haha"));
Slice key(boost::lexical_cast<std::string>(12345));

【注意】如果用WriteBatch,则WriteBach::Put会拷贝Slice指向的数据结构,所以可以这样使用:

leveldb::WriteBatch batch;
for (int i = 0; i < 10; ++i) {
    leveldb::Slice key((const char*)&i, sizeof(int));
    leveldb::Slice value((const char*)&i, sizeof(int));
    batch.Put(key, value);
}
status = db->Write(leveldb::WriteOptions(), &batch);

优化配置

配置优化主要从cache和buffer两部分入手。

leveldb的cache分为两种:

  • table cache 缓存的是sstable的索引数据,类似于文件系统中对inode的缓存。

    可以使用options.max_open_files设置,即打开的.sst文件fd的最大数目。需要注意不要超过系统ulimit -n,即单进程最大fd数目的限制,Linux系统默认为1024,可以修改配置文件 /etc/security/limits.conf 或者 运行命令 ulimit -SHn 65535

  • block cache 缓存的block数据,block是sstable文件内组织数据的单位,也是从磁盘中读取和写入的单位。

    sstable是有序文件,因此block里的数据也是按key有序排列,类似于Linux中的page cache。block默认大小为4KB,可以使用options.block_size设置,最小1KB,最大4MB。对于频繁做scan操作的应用,可适当调大此参数,对大量小value随机读取的应用,也可尝试调小该参数;

    block cache默认实现是一个8MB大小的LRU cache,可以使用options.block_cache设置。

跟导入数据性能相关是write buffer的大小,上面提到leveldb是先写logfile,其对应着一个memtable,默认写满4MB会写入到level-0 sstable文件,这就是默认write buffer的大小,可以通过options.write_buffer_size设置。比如,设置到64MB,则写满64MB内存,才会进行IO操作。

另外,还可以通过leveldb::WriteBatch这个类来做批量写入操作,也可以很明显的提升写入性能。

这里有段性能优化的示例:

#include "leveldb/cache.h"
#include "leveldb/filter_policy.h"

leveldb::Options options;
options.write_buffer_size = 128*1024*1024; // write buffer size
options.max_open_files    = 10000;         // 调整table cache大小 ,即打开的sst文件数量
options.block_cache = leveldb::NewLRUCache(100 * 1048576);  // 调整block cache大小
options.filter_policy = leveldb::NewBloomFilterPolicy(10);  // 使用布隆过滤器

delete db;
// 使用完都需要释放资源
delete options.cache;
delete options.filter_policy;

protobuf

三种规则:

  • required, 一旦设置将不能更改,有人建议不使用required,而仅使用optional和repeated
  • optional,可以方便的让协议向下兼容
  • repeated,建议加上 [packed=true] 可以节省编码空间。

数据类型

  • double、float
  • int32、int64、uint32、uint64、sint32、sint64、fixed32、fixed64、sfixed32、sfixed64 如果有负数,应该使用sint32/64
  • bool
  • string 字符串UTF-8编码或者是ASCII码 → std::string
  • bytes 任意二进制字节 → std::string

tutorial示例

一份proto文件示例:

package tutorial;

message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}

运行如下命令会生产相应的目标文件:

$ protoc --cpp_out=DST_DIR --python_out=DST_DIR /path/to/file.proto

比如,生成的c++文件会有相应的函数:

// name
inline bool has_name() const;
inline void clear_name();
inline const ::std::string& name() const;
inline void set_name(const ::std::string& value);
inline void set_name(const char* value);
inline ::std::string* mutable_name();

// id
inline bool has_id() const;
inline void clear_id();
inline int32_t id() const;
inline void set_id(int32_t value);

// email
inline bool has_email() const;
inline void clear_email();
inline const ::std::string& email() const;
inline void set_email(const ::std::string& value);
inline void set_email(const char* value);
inline ::std::string* mutable_email();

// phone
inline int phone_size() const;  // 解包
inline void clear_phone();
inline const ::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >& phone() const;
inline ::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >* mutable_phone();
inline const ::tutorial::Person_PhoneNumber& phone(int index) const;  // 解包
inline ::tutorial::Person_PhoneNumber* mutable_phone(int index);
inline ::tutorial::Person_PhoneNumber* add_phone();  // 封包

管理函数:

bool IsInitialized() const;  // 检查是否所以的required项都设置了
string DebugString() const; // 返回可读的包信息
void CopyFrom(const Person& from); // 覆盖包
void Clear(); // 清除所有项

解包封包:

bool SerializeToString(string* output) const; // message_lite.h
bool ParseFromString(const string& data);
bool SerializeToOstream(ostream* output) const; // message.h
bool ParseFromIstream(istream* input);
bool ParseFromArray(const void* data, int size); // message_lite.h
bool SerializeToArray(void * data, int size) const;
bool AppendToString(string * output) const; // message_lite.h
int ByteSize() const;  // 得到serialize之后的消息长度

在socket中读写protobuf

protobuf没法解析自身的长度ref,所以如果要在TCP流中读写protobuf,需要自己判断一个protobuf的起始和终止位置。可以考虑在protobuf数据包之前先写入protobuf长度,相当于在protobuf序列化的数据外再wrap一层结构。

gflags

gflags的精髓:分散定义,集中解析。

支持这几种类型:bool、int32、int64、uint64、double、std::string。

比如,在需要用到该flag的.cpp文件里定义:

#include <gflags/gflags.h>

DEFINE_bool(big_menu, true, "Include 'advanced' options in the menu listing");
DEFINE_string(languages, "english,french,german", "comma-separated list of languages to offer in the 'lang' menu");

if (FLAGS_consider_made_up_languages) FLAGS_languages += ",klingon";   // implied by --consider_made_up_languages
if (FLAGS_languages.find("finnish") != string::npos) HandleFinnish();

在main函数里集中解析flag:

int main(int argc, char** argv) {
    google::ParseCommandLineFlags(&argc, &argv, true);
}

参考

2010-02-15 17:02
status: part
comments powered by Disqus