数据密集型应用系统设计(DDIA)读书笔记(1~5章)
前言
漫长的内功修炼过程,阅读过程中产生的读书笔记,可以看成是目录的升级版或者正文的简略版(笔记中详略均依据本人主观意见)
每章的小结值得反复阅读
一、可靠、可扩展、可维护的应用系统
- 构成数据密集型系统的基本模块 P11
- 硬盘的平均无故障时间(MTTF)约为10~50年 P15
- 滚动升级 P16
- 假定“人”是不可靠的,那么该如何保证系统的可靠性呢? P17
- 即使一个系统现在工作可靠,并不意味着他将来一定能够可靠运转。
- 发生退化的一个常见的原因是负载增加
- 如果系统以某种方式增长,我们应对增长的的措施有哪些。
- 我们该如何添加计算资源来处理额外的负载
- 描述负载(Twitter的负载) P18
- 描述性能 P20
- 响应时间异常的可能原因 P21
- 如果想知道典型的响应时间,平均值并不是合适的指标,因为他掩盖了一些信息,无法告诉有多少用户实际经历了多少延迟
- 因此最好使用百分位数 P21
- 中位数称为50百分位数(缩写p50,另外还有p95,p99,p999)
- 针对负载增加的情况,可采用自动弹性系统和手动 P24
- 可维护性 P25
- 一个优秀的运营团队至少负责以下内容 P25
- 简单性是我们构建系统的关键目标之一
- 简化系统并不意味着减少系统功能,而主要意味着消除意外方面的复杂性 P27
- 消除意外复杂性的最好手段之一是抽象,一个好的设计抽象可以赢隐藏大量的实现细节
- 可演化性:易于改变
- 小结 P28
二、数据模型与查询语言
- 主要有关系模型,文档模型和一些基于图的模型
- 采用noSQL的几个驱动因素 P35
- 比尔盖茨的简历 P37
- 如果在关系模式中读取一份简历,那么需要执行多路查询,要么需要执行混乱的多路链接
- 而对于JSON的表示方法,所有相关信息都在一个地方,一次查询就够了
- 用户简历到用户的职位,教育历史和联系信息的一对多关系,意味着数据存在树状结构,JSON表示将数结构显式化
- 当使用ID时,对人类有意义的信息都存储在一个地方,引用它的所有内容都使用ID(ID只在数据库中有意义);当直接存储文本时,则使用它的每条记录中都保存了一份这样的可读信息。 P38
- 如果复制了多份重复的数据,那么该模式通常就违背了规范化 P39
- 文档数据库的比较 P43
- 当集合中的数据不具有相同的结构时,(关系)模式带来的困扰大于他所能提供的帮助,无模式文档可能是更自然的数据模型。但是,当所有记录都有相同的结构时,模式则是记录和确保这种结构的有效机制。 P45
- 存储局部性的性能优势 P45
- 局部性优势仅适用需要同时访问文档大部分内容的场景
- 融合关系模型与文档模型是未来数据库发展的一条很好的途径 P46
- 声明式语言通常合适于并行执行,而命令式代码由于制定了特定的执行顺序,很难在多核和多台机器上并行化 P47
-
MapReduce查询 P50
- MapReduce是一种编程模型,用于在许多机器上批量处理海量数据,兴起于Google
- MongoDB支持有限的MapReduce方式在大量的文档上执行只读查询
- 图状数据模型 P52
- 对于声明式查询语言,通常在编写查询语句时,不需要指定执行细节:查询优化器会自动选择效率最高的执行策略,因此开发者可以专注于应用的其他部分。
- Datalog 基础 P62
- 小结 P65
- 基因组数据库软件 P66
- 全文搜索 P66
三、数据存储与检索
-
主要讨论日志结构的存储引擎和面向页的存储引擎 P71
-
一个世界上最简单的数据库 P72
-
许多数据库内部使用日志(log),日志是一个仅支持追加式更新的数据文件 P72
-
索引是基于原始数据派生而来的数据结构。很多数据库允许单独添加和删除索引,而不影响数据库的内容,他只会影响查询性能
- 维护额外的结构势必会引入开销,特别是在新数据写入时 P73
-
哈希索引 P73
-
哈希表索引的局限性 P76
-
SSTable(排序字符串列表):key-value对的顺序按键排序,要求每个键在每个合并的段文件中只能出现一次
-
Lucene是Elasticsearch和solr等全文搜索系统所使用的索引引擎,它采用了类似SSTable的方法来保存其字典。
- 全文索引比key-value复杂得多,但它基于类似的想法:给定搜索查询中的某个单词,找到提及该单词的所有文档(网页,产品描述等)。
- 它主要采用key-value结构实现,其中键是单词(词条),值是包含该单词的文档ID的列表(倒排表)。在Lucene中,从词条到posting list的映射关系保存在类SSTable的排序文件中,这些文件可以根据需要在后台合并。
-
SSTable相关的性能优化 P80
-
最广泛使用的一种数据结构:B-tree P80
- 像SSTable一样,B-tree保留按键排序的key-value对,这样可以实现高效的key-value查询和区间查询。但相似仅此而已:B-tree本质上具有非常不同的设计理念 P81
-
日志结构索引将数据分解为可变大小的段,B-tree将数据分解成固定大小的页(一般为大小4kb左右)
- 页是内部读/写的最小单元
- 这种设计更接近底层硬件,因为磁盘也是以固定大小的块排列
-
具有n个键的B-tree总是具有O(log n)的深度。 P82
- 大多数数据库可以适合3~4层的B-tree,因此不需要遍历非常深的页面层次即可找到所需的页(分支因子为500的4KB页的四级树可以存储高达256TB)
-
预写日志(write-ahead log WAL),也称为重做日志(仅支持追加修改),用于在数据库崩溃时,将B-tree恢复到最近一致的状态
-
B-tree的优化 P83
-
B-tree和LSM-tree P84
-
其他索引结构 P86
-
聚集索引(在索引中直接保存行数据)
-
非聚集索引(仅储存引用中的数据的引用)
-
覆盖索引(折中方案)
-
多列索引 P86
- 级联索引
- 多维索引
- 空间索引
-
全文搜索和模糊索引 P88
-
在内存中保存所有内容 P88
-
与直觉相反,内存数据库的性能优势并不是因为他们不需要从磁盘读取。如果有足够的内存,即使是基于磁盘的存储引擎,也可能永远不需要从磁盘读取,因为操作系统将最近使用的磁盘块缓存在内存当中。
- 相反,内存数据库可以更快,是因为他们避免了使用写磁盘的格式对内存数据接口编码的开销
-
反缓存方法(内存数据架构扩展到远大于可用内存的数据集,方法类似于操作系统对虚拟内存和交换文件的操作,但能更加有效的管理内存) P89
-
事务,主要指组成一个逻辑单元的一组读写操作 P89
-
对比事务处理(OLTP)与分析系统(OLAP)的主要特性 P90
-
数据仓库 P91
- 是单独的数据库,分析人员可以在不影响OLTP操作的情况下尽情的使用
- 数据仓库包含公司所有各种OLTP系统的只读副本
-
将数据导入仓库的过程称为提取-转换-加载(Extract-Transform-Load, ETL)P91
-
使用单独的数据仓库而不是直接查询OLTP系统进行分析,很大的优势在于数据仓库可以针对分析访问模式进行优化,事实证明,之前讨论的索引算法适合OLTP,但不擅长应对分析查询。
-
数仓模型:星型模式和雪花模型
-
在典型的数据仓库中,表通常非常宽:事实表通常超过100列,有时候有几百列,纬度表也可能非常宽,可能包括与分析相关的所有元数据。P94
-
根据列中的具体模式,可以采用不同的压缩技术。在数据仓库中特别有效的一种技术是位图编码。 P96
-
面向列存储(列压缩) P99
-
物化视图:一个类似表的对象,其内容是一些查询的结果,常用于OLAP当中…
-
小结 P101
四、数据编码与演化
- 滚动升级(分阶段发布),每次将新版本部署到少数几个节点,检查新版本是否正常运行,然后逐步在所有节点上升级新代码,这样新版本部署无需服务暂停,从而支持更频繁的版本发布和更好的演化。 P109
- 向前兼容,向后兼容 P110
- 程序通常使用(至少)两种不同的数据表示形式 P110
- 在内存中,数据保存在对象,结构体,列表,数组,哈希表和树等结构中。这些数据结构针对CPU的高效访问和操作进行了优化(通常使用指针)
- 将数据写入文件或通过网络发送时,必须将其编码为某种字包含的字节序列(例如Json文档)。由于指针对其他进程没有意义,所以这个字节序列表示看起来与内存中使用的数据结构大不一样。
- 从内存中的表示到字节序列的转化称为编码(或序列化),相反的过程称为解码(或解析,反序列化)
- 不同语言编码库带来的一些深层次问题 P111
- 不同的标准化编码(Json,XML,CSV) P111
- 对于仅在组织内部使用的数据,可以选择更紧凑或更快的解析格式。对于一个小数据集来说,收益可以忽略不计,但一旦达到TB级别,数据格式的选择就会产生很大的影响。 P113
- AVRO的读模式与写模式 P118
- Protocol Buffers、Thrift和Avro都使用了模式来描述二进制编码格式。它们的模式语言比XML模式或JSON模式简单的多 P123
- Json、XML、CSV与基于模式的二进制编码的对比(二进制的一些不错的属性) P124
- 它们可以比各种“二进制Json”变体更紧凑,可以省略编码数据中的字段名称。
- 模式是一种有价值的文档形式,因为模式时解码所必须的,所以可以确定它是最新的(而手动维护的文档可能很容易偏离现实)
- 模式数据库允许在部署任何内容之前检查模式更改的向前和向后兼容性
- 对于静态类型编程的用户来说,从模式生成代码的能力是有用的,它能够在编译时进行类型检查
- 数据流模式,最常见的进程间数据流动的方式: P124
- 通过数据库
- 通过服务调用
- 通过异步消息传递
- 数据比代码更加长久 P126
- 微服务 P127
- 服务本身可以是另一项服务的客户端(例如,典型的web应用服务器作为数据库的客户端)。
- 这种方法通常用于将大型应用程序按照功能区分分解为较小的服务,这样当一个服务需要另一个服务的某些功能或数据时,就会向另一个服务发出请求。这种构建应用程序的方式传统上被称为面向服务的体系结构(SOA),最近则更名为微服务体系结构
- 两种流行的web服务方法:REST和SOAP P128
- swagger P129
- 远程过程调用(RPC)的一些缺陷 P129
- 本地函数调用时可预测的…
- 网络请求有另一种结果:超时
- 重试失败请求,可能会发生请求实际上已经完成,只是响应丢失的情况。
- 调用本地函数,通常需要大致相同的时间来执行,而网络请求则无法预测。
- 调用本地函数时,可以高效的将引用(指针),传递给本地内存对象中。进行网络请求时,参数都需要进行编码。对于较大的对象来说,很容易出现问题。
- 客户端和服务可以用不同的编程语言来实现,所以RPC框架必须将数据类型从一种语言转换成另一种语言。
- 尝试使用远程服务看起来像编程语言中的本地对象一样毫无意义 P130
- RESTful Api的一些显著优点: P131
- 它有利于实验和调试
- 支持所有的主流编程语言和平台
- 并且有一个庞大的工具生态系统
-
异步消息队列
- 异步消息队列的优点: P132
- 如果接收方不可用或过载,他可以充当缓冲区,从而提高系统的可靠性
- 他可以自动将消息重新发送到崩溃的进程,从而防止消息丢失
- 它避免了发送方需要知道接收方的IP地址和端口号(这在虚拟机经常容易起起停停的云部署中特别有用)
- 它支持将一条消息发送给多个接收方
- 它在逻辑上将发送方与接收方分离(发送方只是发布消息,并不关心谁使用它们)
- 然而消息传递通信通常是单向的
- 但是我们可以在实现一个回复队列,该队列由原始消息发送者来消费(即可以实现类似RPC的请求/响应数据流)
- 异步消息队列的优点: P132
- 消息代理
- RabbitMQ
- Apache Kafka
- 三种流行的分布式Actor框架处理消息编码的方式如下 P133
- 小结 P134
五、分布式数据系统
- 在多台机器上分布数据的理由
- 扩展性
- 容错与高可用性
- 延迟考虑
- 系统扩展的几种架构 P140
- 共享内存架构(硬件成本高)
- 共享磁盘架构(频繁的资源竞争)
- 无共享架构(节点独立)✔
-
将数据分布在多节点时有两种常见的方式 P141
- 复制
- 在多个节点上保存相同数据的副本
- 数据复制方案的难点在于处理那些需要持续更改的数据 P145
- 三种流行的复制数据变化的方法
- 主从复制
- 多主节点复制
- 无主节点复制
- 分区
- 将一个大块头的数据拆分成多个较小的子集即分区,不同的分区分配给不同的节点(也称为分片)
- 上述两者经常结合使用
- 复制
- 每个保存数据库完整数据集的节点称之为副本 P146
- 主从复制的基本原理 P146
- 同步复制与异步复制 P147
- 把所有节点都配置为同步复制有些不切实际。 P148
- 在实践中,如果数据库启用了同步复制,通常意味着其中某一个从节点是同步的,而其他节点是异步模式。 P148
- 万一同步的从节点变得不可用或者性能下降,则将另一个异步的从节点提升为同步模式。
- 这样可以保证至少有两个节点拥有最新的数据副本。
- 这种配置称为半同步
- 主从复制还经常会被配置为全异步模式,不管从节点上数据多么滞后,主节点总是可以继续相应写请求,系统的吞吐性能更好。
- 如何配置新的从节点 P149
- 处理节点的失效 P149
- 从节点失效:追赶式恢复
- 主节点失效:节点切换
- 切换节点的风险 P151
- 上述问题,包括节点失效、网络不可靠、副本一致性、持久性、可用性和延迟之间的各种细微权衡,实际上正是分布式系统核心的基本问题。 P151
- 基于预写日志(WAL)传输 P152
- 基于行的逻辑日志复制 P153
- 基于触发器的复制 P154
- 借助许多关系数据库都支持的功能:触发器和存储过程
- 最终一致性 P155
- 复制滞后问题
- 读自己的写
- 读写一致性 P156
- 可行方案
- 单调读 P157
- 单调读保证,如果某个用户一次进行多次读取,则他绝对不会看到回滚现象,即在读取较新值之后又发生读旧值的情况 P157
- 前缀一致读(分区数据库中出现的一种特殊问题)
- 该保证是说,对于一系列按照某个顺序发生的写请求,那么读取这些内容时也会按照当时写入的顺序。
- 读写一致性 P156
- 读自己的写
- 复制滞后的解决方案 P159
- 多主节点复制
- 适用场景 P162
- 多数据中心
- 离线客户端操作
- 协作编辑
- 适用场景 P162
- 需要解决冲突问题
- 处理冲突最理想的策略是避免冲突 P164
- 系统必须收敛于一致状态
- 自定义冲突解决逻辑
- 解决冲突最合适的方式可能还是依靠应用层,所以大多数节点复制模型都有工具来让用户编写应用代码来解决冲突 P165
- 在写入时执行
- 在读取时执行
- 自动冲突解决 P166
- 操作转换,在线协作编辑应用背后的冲突解决算法 P166
- 多主节点复制
- 复制的拓补接口 P166
- 环形拓补
- 星型拓补
- 全部-至-全部拓补
- quorum读写 P170
- 无主节点复制由于旨在更好的容忍写入冲突,网络中断和延迟尖峰等,因此也可适用于多数据中心操作
- 处理写冲突
- 最终写入者获胜 P176
- 并发性,时间和相对性 P178
- 如果两个操作并不需要意识到对方,我们即可以声称他们是并发操作
- 确定前后关系,一种确定并发操作性的算法 P178
- 合并同时写入的值 P180
- 版本矢量
- 小结 P181
参考文档
《数据密集型系统设计》