分析完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;

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

写日志类Writer:

    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-writer的更多相关文章

  1. LevelDB Log文件

    [LevelDB Log文件] log文件在LevelDb中的主要作用是系统故障恢复时,能够保证不会丢失数据.因为在将记录写入内存的Memtable之前,会先写入Log文件,这样即使系统发生故障,Me ...

  2. leveldb - log格式

    log文件在LevelDb中的主要作用是系统故障恢复时,能够保证不会丢失数据.因为在将记录写入内存的Memtable之前,会先写入Log文件,这样即使系统发生故障,Memtable中的数据没有来得及D ...

  3. leveldb 学习记录(四)Log文件

    前文记录 leveldb 学习记录(一) skiplistleveldb 学习记录(二) Sliceleveldb 学习记录(三) MemTable 与 Immutable Memtablelevel ...

  4. leveldb源码分析--SSTable之Compaction

    对于compaction是leveldb中体量最大的一部分,也应该是最为复杂的部分,为了便于理解我们首先从一些基本的概念开始.下面是一些从doc/impl.html中翻译和整理的内容: Level 0 ...

  5. LevelDB源码之五Current文件\Manifest文件\版本信息

    版本信息有什么用?先来简要说明三个类的具体用途: Version:代表了某一时刻的数据库版本信息,版本信息的主要内容是当前各个Level的SSTable数据文件列表. VersionSet:维护了一份 ...

  6. LevelDB的源码阅读(二) Open操作

    在Linux上leveldb的安装和使用中我们写了一个测试代码,内容如下: #include "leveldb/db.h" #include <cassert> #in ...

  7. LevelDB的源码阅读(三) Put操作

    在Linux上leveldb的安装和使用中我们写了这么一段测试代码,内容以及输出结果如下: #include <iostream> #include <string> #inc ...

  8. LevelDB的源码阅读(四) Compaction操作

    leveldb的数据存储采用LSM的思想,将随机写入变为顺序写入,记录写入操作日志,一旦日志被以追加写的形式写入硬盘,就返回写入成功,由后台线程将写入日志作用于原有的磁盘文件生成新的磁盘数据.Leve ...

  9. LevelDB源码分析-Open

    Open LevelDB的初始化主要由Open函数实现: Status DB::Open(const Options &options, const std::string &dbna ...

  10. LevelDB源码分析-Version

    Version VersionSet类 VersionSet管理整个LevelDB的当前状态: class VersionSet { public: // ... // Apply *edit to ...

随机推荐

  1. 通过virsh console进入虚拟机

    1.virsh启动一个虚拟机.执行脚本test_qga.sh 2.virsh vncdisplay <vm_ID> 3.vnc登录到vm里面,执行#systemctl start seri ...

  2. python request.args 解析

    requst.args 获得的是 列表类型原始 aa=ff&&bb=gg 通过request.args 分解为 ImmutableMultiDict([('page', u'10')] ...

  3. js学习之BOM和DOM

    1. 浏览器的原理 1.1 浏览器的多线程 (1)  解析js引擎线程 (2)  UI渲染线程 (3)  事件发起线程 (4)  发起请求的线程 (5)  定时器的线程 1.2 同步异步 (1)  前 ...

  4. js基础复习~Array对象

    Array对象 lenght 获取到数组的长度 concat() 方法用于合并两个或多个数组.此方法不会更改两大有数组,而是返回一个新的数组 let arr1 = ["a",&qu ...

  5. Activiti组任务(十)

    1 Candidate-users 候选人 1.1需求 在流程定义中在任务结点的 assignee 固定设置任务负责人,在流程定义时将参与者固定设置在.bpmn 文件中,如果临时任务负责人变更则需要修 ...

  6. 【bzoj2733】[HNOI2012]永无乡

    题目描述: 永无乡包含 n 座岛,编号从 1 到 n,每座岛都有自己的独一无二的重要度,按照重要度可 以将这 n 座岛排名,名次用 1 到 n 来表示.某些岛之间由巨大的桥连接,通过桥可以从一个岛 到 ...

  7. 爬虫小例1:ajax形式的网页数据的抓取

    ---恢复内容开始--- 下面记录如何抓取ajax形式加载的网页数据: 目标:获取“https://movie.douban.com/typerank?type_name=%E5%89%A7%E6%8 ...

  8. ASP.NET MVC 下拉的使用(ViewData传递)

    C#部分 public void GetViewData() { List<string> data = new List<string>(); data.Add(" ...

  9. 170829-关于AOP面向切面编程

    1.AOP概念:Aspect Oriented Programming 面向切面编程 2.作用:本质上来说是一种简化代码的方式 继承机制 封装方法 动态代理  …… 3.情景举例 ①数学计算器接口[M ...

  10. 文档流&文字&CSS常用命令

    文档流 文档流就是文档内元素流动方向 流动方向 内联元素从左往右流,宽度不够,之字形,且元素会被截断 块元素从上往下流动,一排一排 注意事项 内联元素中有英文单词,流动时宽度不够,英文单词会整体迁移, ...