levelDB Log

分析完KV在内存中的存储,接下来就是操作日志。所有的写操作都必须先成功的append到操作日志中,然后再更新内存memtable。这样做有两个有点:1可以将随机的写IO变成append,极大的提高写磁盘速度;2防止在节点down机导致内存数据丢失,造成数据丢失,这对系统来说是个灾难。

在各种高效的存储系统中,这已经是口水技术了。

5.1 格式

在源码下的文档doc/log_format.txt中,作者详细描述了log格式:
The log file contents are a sequence of 32KB blocks.  The only exception is that the tail of thefile may contain a partial block.
Each block consists of a sequence of records:
    block:= record* trailer?
    record :=
    checksum: uint32    // crc32c of type and data[] ; little-endian
    length: uint16       // little-endian
    type: uint8          // One of FULL,FIRST, MIDDLE, LAST
    data: uint8[length]
A record never starts within the last six bytes of a block (since it won‘tfit).  Any leftover bytes here form thetrailer, which must consist entirely of zero bytes and must be skipped byreaders.
翻译过来就是:
Leveldb把日志文件切分成了大小为32KB的连续block块,block由连续的log record组成,log record的格式为:

,注意:CRC32, Length都是little-endian的。
Log Type有4种:FULL = 1、FIRST = 2、MIDDLE = 3、LAST = 4。FULL类型表明该log record包含了完整的user record;而user record可能内容很多,超过了block的可用大小,就需要分成几条log record,第一条类型为FIRST,中间的为MIDDLE,最后一条为LAST。也就是:
> FULL,说明该log record包含一个完整的user record;
> FIRST,说明是user record的第一条log record
> MIDDLE,说明是user record中间的log record
> LAST,说明是user record最后的一条log record
翻一下文档上的例子,考虑到如下序列的user records:
   A: length 1000
   B: length 97270
   C: length 8000
A作为FULL类型的record存储在第一个block中;B将被拆分成3条log record,分别存储在第1、2、3个block中,这时block3还剩6byte,将被填充为0;C将作为FULL类型的record存储在block 4中。如图5.1-1所示。

图5.1-1
由于一条logrecord长度最短为7,如果一个block的剩余空间<=6byte,那么将被填充为空字符串,另外长度为7的log record是不包括任何用户数据的。

写日志

写比读简单,而且写入决定了读,所以从写开始分析。

有意思的是在写文件时,Leveldb使用了内存映射文件,内存映射文件的读写效率比普通文件要高,关于内存映射文件为何更高效,这篇文章写的不错:

http://blog.csdn.net/mg0832058/article/details/5890688

图5.2-1

注意Write类的成员type_crc_数组,这里存放的为Record Type预先计算的CRC32值,因为Record Type是固定的几种,为了效率。

Writer类只有一个接口,就是AddRecord(),传入Slice参数,下面来看函数实现。

首先取出slice的字符串指针和长度,初始化begin=true,表明是第一条log record。

const char* ptr = slice.data();

size_t left = slice.size();

bool begin = true;

然后进入一个do{}while循环,直到写入出错,或者成功写入全部数据,如下:

S1 首先查看当前block是否<7,如果<7则补位,并重置block偏移。

dest_->Append(Slice("\x00\x00\x00\x00\x00\x00",leftover));

block_offset_ = 0;

S2 计算block剩余大小,以及本次log record可写入数据长度

const size_t avail =kBlockSize - block_offset_ - kHeaderSize;

const size_t fragment_length = (left <avail) ? left : avail;

S3 根据两个值,判断log type

RecordType type;

const bool end = (left ==fragment_length); // 两者相等,表明写完

if (begin && end)  type = kFullType;

else if (begin)     type = kFirstType;

else if (end)       type = kLastType;

else             type = kMiddleType;

S4 调用EmitPhysicalRecord函数,append日志;并更新指针、剩余长度和begin标记。

s = EmitPhysicalRecord(type, ptr,fragment_length);

ptr += fragment_length;

left -= fragment_length;

begin = false;

接下来看看EmitPhysicalRecord函数,这是实际写入的地方,涉及到log的存储格式。函数声明为:StatusWriter::EmitPhysicalRecord(RecordType t, const char* ptr, size_t n)

参数ptr为用户record数据,参数n为record长度,不包含log header。

S1 计算header,并Append到log文件,共7byte格式为:

| CRC32 (4 byte) | payload length lower + high (2 byte) | type (1byte)|

char buf[kHeaderSize];

buf[4] = static_cast<char>(n& 0xff);

buf[5] =static_cast<char>(n >> 8);

buf[6] =static_cast<char>(t);

// 计算record type和payload的CRC校验值

uint32_t crc = crc32c::Extend(type_crc_[t], ptr, n);

crc = crc32c::Mask(crc);        // 空间调整

EncodeFixed32(buf, crc);

dest_->Append(Slice(buf,kHeaderSize));

S2 写入payload,并Flush,更新block的当前偏移

s =dest_->Append(Slice(ptr, n));

s = dest_->Flush();

block_offset_ += kHeaderSize +n;

以上就是写日志的逻辑,很直观。

[cpp] view plaincopy

  1. <span style="font-size:18px;">// 记录类型
  2. enum RecordType {
  3. // Zero is reserved for preallocated files
  4. kZeroType = 0,
  5. kFullType = 1,
  6. // For fragments
  7. kFirstType = 2,
  8. kMiddleType = 3,
  9. kLastType = 4
  10. };
  11. static const int kBlockSize = 32768;        // 32k Block
  12. // recored header is checksum (4 bytes), length (2 bytes), type (1 byte).
  13. static const int kHeaderSize = 4 + 2 + 1;</span>

写日志类Writer:

[cpp] view plaincopy

    1. <span style="font-size:18px;">  namespace log {
    2. class Writer {
    3. public:
    4. // Create a writer that will append data to "*dest".
    5. // "*dest" must be initially empty.
    6. // "*dest" must remain live while this Writer is in use.
    7. explicit Writer(WritableFile* dest);
    8. ~Writer(){}
    9. Status AddRecord(const Slice& slice);                       // 添加一个记录
    10. private:
    11. WritableFile* dest_;                                        // class WritableFile;为写文件类
    12. int block_offset_;       // Current offset in block
    13. // crc32c values for all supported record types.  These are
    14. // pre-computed to reduce the overhead of computing the crc of the
    15. // record type stored in the header.
    16. uint32_t type_crc_[kMaxRecordType + 1];                     // 每种type都预先计算出CRC,kMaxRecordType = kLastType;
    17. Status EmitPhysicalRecord(RecordType type, const char* ptr, size_t length);// 写入一个Record
    18. // No copying allowed
    19. Writer(const Writer&);                                  // 禁止拷贝构造函数及赋值运算符重载
    20. void operator=(const Writer&);
    21. };
    22. }
    23. Writer::Writer(WritableFile* dest)                              // 构造函数,参数:写文件句柄
    24. : dest_(dest),
    25. block_offset_(0) {
    26. for (int i = 0; i <= kMaxRecordType; i++) {
    27. char t = static_cast<char>(i);
    28. type_crc_[i] = crc32c::Value(&t, 1);                        // 首先计算每个Type对应的CRC
    29. }
    30. }
    31. Status Writer::AddRecord(const Slice& slice) {                  // 添加一个记录
    32. const char* ptr = slice.data();
    33. size_t left = slice.size();
    34. // Fragment the record if necessary and emit it.  Note that if slice
    35. // is empty, we still want to iterate once to emit a single     // 如果Slice为空,则增加一个zero-length的记录
    36. // zero-length record
    37. Status s;
    38. bool begin = true;
    39. do {
    40. const int leftover = kBlockSize - block_offset_;            // 当前Block剩余容量
    41. assert(leftover >= 0);
    42. if (leftover < kHeaderSize) {                            // 剩余容量比kHeaderSize还小,则填充trailer
    43. // Switch to a new block
    44. if (leftover > 0) {
    45. // Fill the trailer (literal below relies on kHeaderSize being 7)
    46. assert(kHeaderSize == 7);
    47. dest_->Append(Slice("\x00\x00\x00\x00\x00\x00", leftover));  // leftover<7, dest_追加leftover个0
    48. }
    49. block_offset_ = 0;
    50. }
    51. // Invariant: we never leave < kHeaderSize bytes in a block.
    52. assert(kBlockSize - block_offset_ - kHeaderSize >= 0);
    53. const size_t avail = kBlockSize - block_offset_ - kHeaderSize; // 当前block剩余可用大小(除去kHeaderSize)
    54. const size_t fragment_length = (left < avail) ? left : avail;  // 分片
    55. RecordType type;
    56. const bool end = (left == fragment_length);                    // 是否为最后一个
    57. if (begin && end) {                                   // 开始 && 结束,则type为FullType
    58. type = kFullType;
    59. } else if (begin) {                                   // 开始 && 非结束,则type为kFirstType
    60. type = kFirstType;
    61. } else if (end) {                                         // 非开始 && 结束,则type为kLastType
    62. type = kLastType;
    63. } else {                                              // 其它为kMiddleType
    64. type = kMiddleType;
    65. }
    66. s = EmitPhysicalRecord(type, ptr, fragment_length);           // 保存一条fragment_length字节长度的数据到log文件,类型为type,开始地址为ptr
    67. if(!s.ok()){                                          // 写入失败,则跳出循环
    68. break ;
    69. }
    70. ptr += fragment_length;
    71. left -= fragment_length;
    72. begin = false;
    73. } while (/*s.ok() &&*/ left > 0);
    74. return s;
    75. }
    76. // 保存一条n字节长度的记录,记录类型为t,记录数据开始地址为ptr
    77. Status Writer::EmitPhysicalRecord(RecordType t, const char* ptr, size_t n)
    78. {
    79. assert(n <= 0xffff);  // Must fit in two bytes
    80. assert(block_offset_ + kHeaderSize + n <= kBlockSize);
    81. // Format the header
    82. char buf[kHeaderSize];                              // 7bytes: CheckSum(4) + 记录长度(2) + Type(1)
    83. buf[4] = static_cast<char>(n & 0xff);
    84. buf[5] = static_cast<char>(n >> 8 & 0xff);              // 长度高位在后
    85. buf[6] = static_cast<char>(t);
    86. // Compute the crc of the record type and the payload.
    87. uint32_t crc = crc32c::Extend(type_crc_[t], ptr, n);    // 计算CRC
    88. crc = crc32c::Mask(crc);                            // Adjust for storage
    89. EncodeFixed32(buf, crc);                                // 将CRC放入header前4字节
    90. // Write the header and the payload
    91. Status s = dest_->Append(Slice(buf, kHeaderSize));       // header写入文件
    92. if (s.ok()) {                                           // header写入成功
    93. s = dest_->Append(Slice(ptr, n));                    // 将记录数据写入文件
    94. if (s.ok()) {
    95. s = dest_->Flush();                              // flush到文件
    96. }
    97. }
    98. block_offset_ += kHeaderSize + n;                       // Block offset移动
    99. return s;
    100. }
    101. </span>

levelDB Log,布布扣,bubuko.com

时间: 2025-01-01 08:31:36

levelDB Log的相关文章

LevelDB Log文件

[LevelDB Log文件] log文件在LevelDb中的主要作用是系统故障恢复时,能够保证不会丢失数据.因为在将记录写入内存的Memtable之前,会先写入Log文件,这样即使系统发生故障,Memtable中的数据没有来得及Dump到磁盘的SSTable文件,LevelDB也可以根据log文件恢复内存的Memtable数据结构内容,不会造成系统丢失数据,在这点上LevelDb和Bigtable是一致的. LevelDb对于一个log文件,会把它切割成以32K为单位的物理Block,每次读取

Windows Caffe中MNIST数据格式转换实现

Caffe源码中src/caffe/caffe/examples/mnist/convert_mnist_data.cpp提供的实现代码并不能直接在Windows下运行,这里在源码的基础上进行了改写,使其可以直接在Windows 64位上直接运行,改写代码如下: #include "stdafx.h" #include <gflags/gflags.h> #include <glog/logging.h> #include <google/protobuf

SSTable and Log Structured Storage: LevelDB

If Protocol Buffers is the lingua franca of individual data record at Google, then the Sorted String Table (SSTable) is one of the most popular outputs for storing, processing, and exchanging datasets. As the name itself implies, an SSTable is a simp

LevelDB源码之四LOG文件

“LOG文件在LevelDb中的主要作用是系统故障恢复时,能够保证不会丢失数据.因为在将记录写入内存的Memtable之前,会先写入Log文件,这样即使系统发生故障,Memtable中的数据没有来得及Dump到磁盘的SSTable文件,LevelDB也可以根据log文件恢复内存的Memtable数据结构内容,不会造成系统丢失数据,在这点上LevelDb和Bigtable是一致的.” (http://www.cnblogs.com/haippy/archive/2011/12/04/2276064

深入leveldb-初步认识leveldb

文章参考http://blog.chinaunix.net/uid-26575352-id-3245476.html 1. leveldb简介 leveldb是google两位工程师实现的单机版k-v存储系统,具有以下几个特点 1. key和value都是任意的字节数组,支持内存和持久化存储 2. 数据都是按照key排序 3. 用户可以重写排序函数 4. 包含基本的数据操作接口,Put(key,value),Get(key),Delete(key) 5. 多操作可以当成一次原子操作 6. 用户可

Tair ldb(leveldb存储引擎)实现介绍

简介 tair 是淘宝自己开发的一个分布式 key/value 存储引擎. tair 分为持久化和非持久化两种使用方式. 非持久化的 tair 可以看成是一个分布式缓存. 持久化的 tair 将数据存放于磁盘中. 为了解决磁盘损坏导致数据丢失, tair 可以配置数据的备份数目, tair 自动将一份数据的不同备份放到不同的主机上, 当有主机发生异常, 无法正常提供服务的时候, 其于的备份会继续提供服务. tair 的总体结构 tair 作为一个分布式系统, 是由一个中心控制节点和一系列的服务节

leveldb - 并发写入处理

在并发写入的时候,leveldb巧妙地利用一个时间窗口做batch写入,这部分代码值得一读: Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) { // A begin Writer w(&mutex_); w.batch = my_batch; w.sync = options.sync; w.done = false; // A end // B begin MutexLock l(&

LevelDB Cache实现机制分析

几天前淘宝量子恒道在博客上分析了HBase的Cache机制,本篇文章,结合LevelDB 1.7.0版本的源码,分析下LevelDB的Cache机制. 概述 LevelDB是Google开源的持久化KV单机存储引擎,据称是HBase的鼻祖Bigtable的重要组件tablet的开源实现.针对存储面对的普遍随机IO问题,LevelDB采用merge-dump的方式,将逻辑场景的随机写请求转换成顺序写log和写memtable的操作,由后台线程根据策略将memtable持久化成分层的sstable.

LevelDB 整体架构

[LevelDB 整体架构]     从图中可以看出,构成LevelDb静态结构的包括六个主要部分:内存中的MemTable和Immutable MemTable以及磁盘上的几种主要文件:Current文件,Manifest文件,log文件以及SSTable文件.当然,LevelDb除了这六个主要部分还有一些辅助的文件,但是以上六个文件和数据结构是LevelDb的主体构成元素. LevelDb的Log文件和Memtable与Bigtable论文中介绍的是一致的,当应用写入一条Key:Value记