From e87d83e6b6ee9363b5ea947370c37799cfbcff49 Mon Sep 17 00:00:00 2001 From: selfboot Date: Thu, 16 May 2024 21:36:46 +0800 Subject: [PATCH] Improve leveldb source --- source/_drafts/leveldb-source-memtable.md | 9 +++++ source/_drafts/leveldb-source-skiplist.md | 3 +- source/_drafts/leveldb-source-unstand-c.md | 2 ++ source/_drafts/leveldb-source-utils.md | 42 ++++++++++++++++++++++ 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 source/_drafts/leveldb-source-memtable.md diff --git a/source/_drafts/leveldb-source-memtable.md b/source/_drafts/leveldb-source-memtable.md new file mode 100644 index 0000000000..ad1cb65720 --- /dev/null +++ b/source/_drafts/leveldb-source-memtable.md @@ -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 被创建来接受新的写入操作。这样可以保证写入操作的连续性,不受到影响。 diff --git a/source/_drafts/leveldb-source-skiplist.md b/source/_drafts/leveldb-source-skiplist.md index 03fc9f17e0..dd6b18f130 100644 --- a/source/_drafts/leveldb-source-skiplist.md +++ b/source/_drafts/leveldb-source-skiplist.md @@ -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 Table; ``` + ## 跳表简单介绍 diff --git a/source/_drafts/leveldb-source-unstand-c.md b/source/_drafts/leveldb-source-unstand-c.md index 20ae2cded5..c2755727b0 100644 --- a/source/_drafts/leveldb-source-unstand-c.md +++ b/source/_drafts/leveldb-source-unstand-c.md @@ -92,6 +92,8 @@ class LEVELDB_EXPORT Iterator { ## 其他 +### constexpr + `constexpr` 指定了用于声明常量表达式的变量或函数。这种声明的目的是告知编译器**这个值或函数在编译时是已知**的,这允许在编译期间进行更多的优化和检查。 ```c++ diff --git a/source/_drafts/leveldb-source-utils.md b/source/_drafts/leveldb-source-utils.md index 47aca60eda..8ff75a5744 100644 --- a/source/_drafts/leveldb-source-utils.md +++ b/source/_drafts/leveldb-source-utils.md @@ -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 了。