Skip to content

Commit

Permalink
Improve leveldb source
Browse files Browse the repository at this point in the history
  • Loading branch information
selfboot committed May 16, 2024
1 parent 00f44e9 commit e87d83e
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 1 deletion.
9 changes: 9 additions & 0 deletions source/_drafts/leveldb-source-memtable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
title: LevelDB 源码阅读:MemTable 内存表的实现细节
tags: [C++, LevalDB]
category: 源码剖析
toc: true
description:
---

MemTable 的主要作用是存储最近写入的数据。在 LevelDB 中,所有的写操作首先都会被记录到一个 Write-Ahead Log(WAL,预写日志)中,以确保持久性,然后数据会被存储在 MemTable 中。当 MemTable 达到一定的大小阈值后,它会被转换为一个不可变的 Immutable MemTable,并且一个新的 MemTable 会被创建来接收新的写入。此时会触发一个后台过程将其写入磁盘形成 SSTable。这个过程中,一个新的 MemTable 被创建来接受新的写入操作。这样可以保证写入操作的连续性,不受到影响。
3 changes: 2 additions & 1 deletion source/_drafts/leveldb-source-skiplist.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ description:
---

在 LevelDB 中,MemTable 中的数据存储在 Table 中,而这里 Table 底层实现就是 SkipList(跳表)。跳表是William Pugh 在论文 [Skip Lists: A Probabilistic Alternative to
Balanced Trees](https://15721.courses.cs.cmu.edu/spring2018/papers/08-oltpindexes1/pugh-skiplists-cacm1990.pdf) 中提出的一种数据结构。有点类似**有序链表**,但是可以有多层,通过空间换时间,最终有很好的查找、插入性能。和一些平衡树比起来,代码实现也比较简单,因此应用比较广泛。
Balanced Trees](https://15721.courses.cs.cmu.edu/spring2018/papers/08-oltpindexes1/pugh-skiplists-cacm1990.pdf) 中提出的一种概率性数据结构。有点类似**有序链表**,但是可以有多层,通过空间换时间,允许快速的查询、插入和删除操作,平均时间复杂度为 `O(log n)`。和一些平衡树比起来,代码实现也比较简单,性能稳定,因此应用比较广泛。

```c++
typedef SkipList<const char*, KeyComparator> Table;
```

<!-- more -->

## 跳表简单介绍
Expand Down
2 changes: 2 additions & 0 deletions source/_drafts/leveldb-source-unstand-c.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ class LEVELDB_EXPORT Iterator {

## 其他

### constexpr

`constexpr` 指定了用于声明常量表达式的变量或函数。这种声明的目的是告知编译器**这个值或函数在编译时是已知**的,这允许在编译期间进行更多的优化和检查。

```c++
Expand Down
42 changes: 42 additions & 0 deletions source/_drafts/leveldb-source-utils.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,45 @@ Skewed 的实现比较有意思,首先从 [0, max_log] 范围内均匀选择
// range [0,2^max_log-1] with exponential bias towards smaller numbers.
uint32_t Skewed(int max_log) { return Uniform(1 << Uniform(max_log + 1)); }
```
## CRC32 循环冗余校验
CRC(**Cyclic Redundancy Check,循环冗余检查**)是一种通过特定算法来计算数据的校验码的方法,广泛用于**网络通讯和数据存储系统**中以检测数据在传输或存储过程中是否发生错误。CRC32是一种常见的CRC算法,使用了一个32位的校验和。
CRC 的计算基于**多项式除法**,处理的数据被视为一个巨大的多项式,通过**这个多项式除以另一个预定义的“生成多项式”**,然后取余数作为输出的CRC值。CRC算法具有天然的**流式计算特性**,可以先计算消息的一部分的CRC,然后将这个结果作为下一部分计算的初始值(init_crc)。下面的 `Extend` 函数接受一个初始的 CRC 值(可能是之前数据块的CRC结果),然后计算加上新的数据块后的CRC值。这使得 LevelDB 能够在不断追加数据时连续计算CRC,而不需要每次都从头开始。
```c++
// Return the crc32c of concat(A, data[0,n-1]) where init_crc is the
// crc32c of some string A. Extend() is often used to maintain the
// crc32c of a stream of data.
uint32_t Extend(uint32_t init_crc, const char* data, size_t n);
// Return the crc32c of data[0,n-1]
inline uint32_t Value(const char* data, size_t n) { return Extend(0, data, n); }
```

`crc32c.cc` 中的实现比较比较复杂,涉及到查找表(table-driven approach)、数据对齐、和可能的硬件加速,具体的原理可以参考 [A PAINLESS GUIDE TO CRC ERROR DETECTION ALGORITHMS](http://www.ross.net/crc/download/crc_v3.txt)。其中**生成多项式**的选择对CRC算法的有效性和错误检测能力至关重要。生成多项式并不是随意选取的,它们通常通过数学和计算机模拟实验被设计出来,以确保最大化特定数据长度和特定应用场景下的错误检测能力,常见的生成多项式`0x04C11DB7` 就是在IEEE 802.3标准中为 CRC-32 算法选定的。

这里补充说下,CRC 只是用来**检测随机错误**,比如网络传输或者磁盘存储中某些比特位发生了翻转。它不是纠错校验码,只能检测到错误,并**不能纠正错误**。此外我们也可以故意对内容进行篡改然后保证 CRC 结果一样,如果要防篡改,则要用到更为复杂的加密哈希函数或者数字签名技术。

另外在 `crc32c.h` 中还看到有一个 Mask,这里代码注释也写的很清楚了,如果数据本身包含CRC值,然后直接在包含CRC的数据上再次计算CRC,可能会降低CRC的错误检测能力。因此,LevelDB 对CRC值进行高低位交换后加上一个常数(kMaskDelta),来“掩码”原始的CRC值。这种变换后的CRC值可以存储在文件中,当要验证数据完整性时,使用 Unmask 函数将掩码后的CRC值转换回原始的CRC值,再与当前数据的CRC计算结果进行比较。

```c++
// Return a masked representation of crc.
//
// Motivation: it is problematic to compute the CRC of a string that
// contains embedded CRCs. Therefore we recommend that CRCs stored
// somewhere (e.g., in files) should be masked before being stored.
inline uint32_t Mask(uint32_t crc) {
// Rotate right by 15 bits and add a constant.
return ((crc >> 15) | (crc << 17)) + kMaskDelta;
}

// Return the crc whose masked representation is masked_crc.
inline uint32_t Unmask(uint32_t masked_crc) {
uint32_t rot = masked_crc - kMaskDelta;
return ((rot >> 17) | (rot << 15));
}
```
这里其实有个有意思的地方,原始 CRC32 值交换高 15 位后,加上常量后可能会大于 uint32_t 的最大值,**导致溢出**。**在 C++ 中,无符号整型的溢出行为是定义良好的,按照取模运算处理**。比如当前 crc 是 32767,这里移动后加上常量,结果是7021325016,按照 $ 2^{32} $ 取模后结果是 2726357720。而在 Unmask 中的减法操作,同样会溢出,C++中这里也是按照取模运算处理的。这里 $ 2726357720-kMaskDelta = -131072 $ 按照 $ 2^{32} $ 后结果是 4294836224,再交换高低位就拿到了原始 CRC 32767 了。

0 comments on commit e87d83e

Please sign in to comment.