数据密集型应用系统设计(3)

文章目录

  • 数据存储与检索
    • 数据库核心是数据结构
    • 哈希索引
    • SSTable和LSM-Tree
    • B-trees
    • B-tree和LSM-tree
    • 其他索引结构(*)
    • 事务处理和分析系统
    • 小结

数据存储与检索

本章我们从数据库的角度来探讨探讨如何存储输入的数据,并且在收到查询时找到数据。

针对事务型负载和针对分析型负载的存储引擎优化存在很大的差异。

数据库核心是数据结构

日志通常指的是应用程序的运行输出日志,来记录发生了什么事情。当然日志还有一个更通用的含义,表示一个仅能追加的记录序列集合。它可能是人类不可读的,可能是二进制格式而只能被其他程序读取。

日志方式的查找开销是 O ( n ) O(n) O(n)

为了高效查找数据库中的特定键的值,需要新的数据结构:索引。索引的基本想法是保留一些额外的元数据,将这些元数据作为路标,帮助定位想要的数据。

索引是基于原始数据派生而来的额外数据结构。很多数据库允许单独添加和删除索引而不改变数据库的内容,以影响查询性能。对于写入,任何类型的索引都会降低写的速度(相比于日志的简单追加文件方式)。

所以适当的索引可以加速读取查询,但每个索引都会降低写速度:这是存储系统中重要的权衡设计。

哈希索引

假设数据存储全部采用追加式文件组成,最简单的索引策略是保存内存中的hash map,把每个键一一映射到数据文件中特定的字节偏移量,这样就可以找到每个值的位置。当查找某个值时,使用hash map来找到文件中的偏移量,即存储位置,然后读取内容。

如果key比较少,但每个key都有大量的写操作,将所有key保存在内存中是可取的。可以采用上述方式。

具体可见日志结构的存储引擎-bitcask。

哈希表索引的缺陷:

  1. 哈希表必须全部放入内存。因为磁盘上的hash map表现不好,需要大量的随机访问I/O,当哈希变满时,继续增长代价昂贵,并且哈希冲突时需要处理复杂的处理逻辑。
  2. 区间查询效率不高,只能采用逐个查找的方式查询每一个键。

SSTable和LSM-Tree

上述的日志结构的存储段中的键值对是按照写入顺序排列的。如果我们希望按照键对键值对排序,这种格式称为SSTable,它有以下优点:

  1. 合并段更加高效简单。
  2. 在文件中查找特定的键时,不需要在内存中保存所有键的索引。(通过稀疏索引减少索引的数量)
  3. 读请求需要扫描全球范围内的多个键值对,可以考虑将这些记录保存到一个块中并且在写磁盘之前将其压缩。

构建和维护SSTables
存储引擎的基本工作流程如下:

  1. 当写入时,将其添加到内存中的平衡树数据结构中(例如红黑树),有时被称作内存表
  2. 当内存表过大时,将其作为SSTable文件写入磁盘。由于树已经维护了按键排序的键值对,写磁盘可以比较高效。新的SSTable文件成为数据库的最新部分。当SSTable写磁盘的同时,写入可以继续添加到一个新的内存表实例
  3. 为了处理读请求,首先尝试在内存表中查找键,然后是最新的磁盘段文件,接下来是次新的磁盘段文件,以此类推,直到找到目标
  4. 后台进程周期性执行段合并和压缩过程,以合并多个段文件,并丢弃那些被覆盖或者删除的值

以上的算法本质在很多引擎使用,如LevelDB,RocksDB,类似的存储引擎还有Cassandra和HBase。

LSM-Tree的基本思想是保存在后台合并的一系列SSTable。基于合并和压缩排序文件原理的存储引擎通常都被称为LSM存储引擎。

B-trees

B-tree同样保留按键排序的键值对,以实现高效的键值查询和区间查询,但是它本质上具有非常不同的设计理念。

不同于之前看到的日志结构索引将数据库分解为可变大小的段,通常大小为几兆字节或者更大,并且始终按照顺序写入段。B-trees将数据库分解成固定大小的块或者页,传统上大小是4KB,页是内部读/写的最小单元。

由于每个页面都可以使用地址进行标识,就可以让一个页面引用另外一个页面。可以用这些页面引用构造一个树状页面,索引一个键时,从根页面开始逐渐向下寻找,直到到达一个包含单个键的叶子页,它包含每个内联键的值或包含可以找到值的页的引用。

分支因子是一个页中包含的子页的引用数量,实际中通常为几百个。

更新策略和崩溃恢复策略:

  1. 更新现有键的值即搜索包含该键的页子叶,更改该页的值
  2. 添加新键,需要找到范围包含该键的页,并且添加。如果此页无可用空间,则分裂成两个半满的页,且更新父页。
  3. 为了崩溃恢复,使用预写日志WAL,所有修改先更新WAL再修改树本身的页。WAL用于将树恢复到最近一致的状态。

B-tree和LSM-tree

根据经验,LSM-tree通常对于写入更快,B-tree读取更快。

LSM-tree的优点:

由于B-tree存储引擎按固定页写入,那么通常每一页都会有些空间无法使用。而LSM-tree不是面向页的,并且定期重写SSTable以消除碎片化,所以具有较低的碎片率。

LSM-tree通常能够承受比B-tree更高的写入吞吐量,部分原因是具有较低的写放大(一次数据写入导致多次磁盘写入称为写放大),部分原因因为它是以顺序方式写入紧凑的SSTable文件。

LSM-tree的缺点:

日志结构存储的缺点是有时候压缩过程会干扰正在进行的读写操作。另外写入吞吐量和压缩要共享磁盘的有限的写入带宽,也就是说数据库的数据量越大,压缩就需要越多的磁盘带宽。

B-tree的每个键恰好唯一对应于索引中的某个位置,而日志结构的存储引擎可能在不同的段中具有相同键的多个副本。如果在这种场景下需要提供强大的事务语义,无疑B-tree更具竞争力。

其他索引结构(*)

在索引中存储值
多列索引
全文搜索和模糊搜索
在内存中保存所有内容

事务处理和分析系统

待补

小结

本章的重点在于介绍数据库内部如何处理存储和检索,例如存储新数据和查询数据。

存储引擎大致分为两大类:针对事务处理(OLTP)优化的架构,以及针对分析型(OLAP)的优化架构。

  1. OLTP系统通常面向用户,这意味着它们可能收到大量的请求。为了处理负载,应用程序通常在每个查询中只涉及少量的记录。应用程序基于某种键来请求记录,而存储引擎使用索引来查找所请求键的数据。磁盘寻道时间通常是瓶颈。
  2. 由于不是直接面对最终用户,数据仓库和类似的分析型系统相对并不太广为人知,它们主要由业务分析师使用。处理的查询请求数目远低于OLTP,但每个查询通常要求很严苛,需要在短时间内扫描数百万条记录。磁盘带宽通常是瓶颈。

在OLTP中分为两个主要流派:

  1. 日志结构流派,只允许追加式更新文件和删除过时的文件,但不会修改已写入的文件。
  2. 原地更新流派,将磁盘视为可以覆盖的一组固定大小的页。B-tree是最典型代表,已用于所有主要的关系数据库和大量的非关系数据库。

你可能感兴趣的:(计算机系统,系统架构,数据库,数据结构)