<数据密集型应用系统设计>读书笔记(一)

Posted by danner on May 17, 2021

第一章

数据密集型应用定义:“数据” 是成败决定性因素,包括数据的规模、数据的复杂性、数据产生与变化的速率。

可靠性

即使发生了某些错误,系统仍可以继续正常工作。

硬件故障:多是相互独立

  • 硬盘崩溃
  • 内存故障
  • 停电
  • 断网

硬件冗余来减少系统故障率

软件错误:难以预料,节点之间软件关联,往往导致更多的系统故障

  • 输入特定值导致服务器崩溃
  • 系统依赖其他服务,但服务无响应或异常响应
  • 级联故障,某个组件的小故障触发另一个组件故障,引发更多的系统问题

软件问题有时没有快速解决方案,必须事先进行全面的测试、进程隔离,允许进程崩溃并自动重启,监控并分析生产环节的行为表现。

人为失误

  • 充分测试(单元测试、系统集成测试、自动化测试)
  • 提供快速的恢复机制以尽量减少影响面(回滚、滚动发布代码)
  • 建设详细而清晰的监控子系统:性能指标和错误率

可扩展性

  • 负载:确定系统的负载是什么
  • 性能:负载增加,系统如何变化
    • 百分位 P95、P99、P999
  • 扩展:
    • 有状态服务,垂直扩展(机器加性能)
    • 无状态服务,分布到多台机器(微服务分布式)

可维护性

  • 可运维性
    • 提供对系统运行时行为和内部的可观测性,方便监控
    • 支持自动化
    • 避免绑定特定的机器,允许机器停机维护
    • 提供良好的文档和易于理解的操作模式
    • 提供良好的配置
    • 尝试自我修复,减少管理员手动控制
  • 简单性:简化复杂度
    • 抽象:好的抽象可以隐藏大量的实现细节,对外提供干净、易懂的接口
  • 可演化性:易于改变
    • 提高敏捷性:轻松的修改数据系统(与系统的复杂度有关)

数据模型与查询语言

每层都通过提供一个简洁的数据模型来隐藏下层的复杂性。

比较关系模型、文档模型和基于图的数据模型,讨论多种查询语言并并比较它们的使用场景。

关系模型和文档模型

在关系数据库中,查询优化器自动决定以何种顺序执行查询,以及使用哪些索引。

  • 文档数据模型:模式灵活,由于局部性而带来较好的性能,对于某些应用来说,它更接近于应用程序所使用的数据结构
    • 局部性:类似宽表下只查一次而不是查几张表,相关数据归为一组
    • 一对多关系,更为合适
    • 访问文档大部分内容的情况(文档是整个读出json,只要某个字段却需要读整个json),文档尽量小且避免写入时增加文档大小
  • 关系模型:联结操作多对一和多对多关系更简洁的表达
    • 倾向于某种数据分解

读模式/写模式

数据查询语言

  • 声明式查询语言
    • 指定所需模式(from),结果需要什么条件(where),以及如何转换数据(udf);隐藏数据库引擎的实际细节
    • 功能上有更多限制,但给数据库提供更多自动优化的空间
  • 命令式的查询API:完全写代码实现
  • MapReduce 既不是声明式查询语言,也不是一个完全命令式的查询API,而是介于两者之间
    • 查询的逻辑用代码片段实现,这些代码被框架重复调用;函数式编程,输入 -> 输出 ,无副作用(不会改变输入)

图状数据模型

关系模型能处理简单的多对多关系,但随着数据之间的关联越来越复杂,可将数据模型转化为图模型

图更为强大的的用途在于,提供了单个数据存储区中保存完全不同类型对象的一致性方式。

没怎么看懂

数据存储与检索

如何存储输入的数据,并在收到查询请求时,怎样重新找到数据。

数据结构

增加一些额外的元数据,这些元数据作为路标,帮助定位想要的数据。

适当的索引可以加速读取查询,但每个索引都会减慢写速度

哈希索引:

key-value,value 是数据在磁盘的位置

  • 哈希表存放在内存(key 不能太多),哈希表放在磁盘性能差很多
  • 只适合键值查询,不适合区间查询
LSM-Tree
  • 基于合并和压缩排序文件原理的存储引擎

  • 合并方法:并发读取多个文件,比较每个文件的第一个键,把最小的键(根据排序顺序)先拷贝到输出文件,重复此过程,产生一个按键排序的新文件(有些情况还需要去重)。

  • 可以在文件中查找键不需要在内存保存所有的键:内存中存储稀疏键,文件内容是排序的可以根据稀疏键按序找到特定的键

数据写入是任意顺序,那如何让键排序呢?B-trees 是在磁盘维护排序结构,LSM 是保存在内存。工作流程如下

  • 当写入时,将其添加到内存中的平衡树数据结构中(红黑树支持排序),成为内存表
  • 当内存大于阈值,将其作为 SSTables 写到顺序写磁盘,写磁盘是会自动创建新的内存表提供数据写入
  • 读请求时,首先在内存中查找,然后在磁盘中查找(太慢了,需要优化)。
  • 后台周期性的合并与压缩 SSTables,生成新的 SSTables 并丢弃覆盖或删除的数据。
优化

读操作时,会扫描内存表和所有磁盘表,这个工作量是巨大的。优化这种访问,使用额外的布隆过滤器(确定某个键不存在)。不同的策略会影响甚至决定 SSTables 压缩和合并的时机:LevelDB 和 RocksDB 使用分层压缩,HBase 使用大小分级,Cassandra 则同时支持两种压缩。大小分级压缩:较新和较小的 SSTables 被连续合并到较旧和较大的 SSTables。分层压缩:键的范围分裂成更多的更小的 SSTables,旧数据移动到单独的“层级”,这样压缩可以逐步进行并节省磁盘。

B-Tree

几乎所有关系型数据库的标准实现,B-Tree 也保留排序的 key-value。

B-tree 将数据库分解成固定大小块或页,传统大小为4kB,是内部读写的最小单元,这种设计更接近底层硬件,磁盘也是以固定大小的块排列。

算法确保树保持平衡:具有n个键的B-tree 具有O(long n) 深度,大多数数据库为3-4层。查询很简单,但修改时,B-tree 会变得繁琐。

B-tree 底层的写操作是用新数据覆盖磁盘上的旧页。在磁盘驱动器上,需要将磁头移到正确的位置(随机写),然后旋转盘面,最后用新的数据覆盖相应的扇区(SSD 情况下必须一次擦除并重写非常大的存储芯片块)。WAL (write ahead log) 预写日志是仅支持追加修改的文件,崩溃后恢复使用。

优化
  • 写时复制而不是覆盖之前的页,更新时复制原先的页修改后的到新的页,指向新创建的页
  • 保存键的元信息(最大自小值),而不是完整的键,减少空间
  • 尝试相邻页可以按顺序保存在磁盘上,增快按顺序扫描大段键的情况
  • 添加额外的指针到树中,指向左右的兄弟页,这样可以不用跳回父页就能顺序扫描
B-tree 和 LSM-tree
  • 通常而言,LSM-tree 写入更快,读取较慢(读取多个 SSTable);B-tree 读取更快,针对具体场景可能会有不一样的表现
LSM-tree 优势

​ B-tree 需要写两次:WAL日志、页本身(即使修改几个字节也要整页操作);LSM-tree 的合并使日志结构索引和数据被写多次,称为写放大

LSM-tree 可以更好压缩,B-tree 会有碎片,LSM-tree 定期合并可以消除碎片。

LSM-tree 劣势

合并会占磁盘写入I/O,高吞吐写入和后台合并会抢磁盘I/O。

B-tree 中每个键都有唯一对应索引的位置,而 LSM-tree 中不同的 SSTable 具有相同键的副本。

如果数据库希望提高强大的的事务语义,B-tree 更好;事务隔离是通过键范围上的锁来实现

事务处理和分析处理

事务:一个逻辑单元的一组读写操作。

列式存储

每列中的所有值存在一起

物化视图:对于数据仓库来说,提前聚合

数据编码与演化

数据编码格式

序列化/反序列化

数据流模式

基于数据库的数据流

基于数据库实现数据共享,需要考虑新旧版本表结构对程序的影响

基于服务的数据流:REST 和 RPC

RPC 主要用于同一个组织内多项服务之间的请求,通常发生在同一个数据中心。

基于消息传递的数据流