数据密集型应用系统设计(DDIA)读书笔记(6~9章)
前言
已经读完本书的第二部分了,发现第一篇读书笔记中的第5章应该放到这一篇来的,算了,有空改一下
目前读到这儿的最大感受是,共鸣非常多,就是平时有很多耳熟而不一定能详的名词术语,在本书中都有多次提及并进行了详细的讨论(例如:数据分区,事务,ACID,分布式系统,CAP理论, 两阶段提交, ZooKeeper等)
如果还在犹豫要不要读这本书,希望看了这篇笔记后下定决心吧,啃它!
书中每部分的小结值得反复阅读~
一、数据分区
- 面对一些海量数据或非常高的查询压力,复制技术还不够,我们还需要将数据拆分成为分区,也称为分片 P189
- 分区的定义:每一条数据只属于某个特定分区,每个分区都可以视为一个完整的小型数据库,虽然数据库可能存在一些跨分区的成分 P189
- 采用数据分区的主要目的是提高可扩展性,不同的分区可以放在一个无共享集群的不同节点上 P189
- 分区与复制通常结合使用 P190
- 分区的主要目的是将数据和查询负载均匀分布在所有节点上
- 分区不均匀会导致“倾斜” P191
- 基于关键字的分区 P191
-
基于关键字哈希值分区 P193
- 一致性哈希
- 二级索引技术是Solr和Elasticsearch等全文索引服务器存在之根本 P195
- 基于文档分区的二级索引
- 基于词条的二级索引分区
- 分区在平衡 P198
- 随着时间的推移,分区数据总会出现一些变化,这些变化要求数据和请求可以从一个节点转移到另一个节点
- 这样一个迁移负载的过程称为再平衡(or 动态平衡)
- 无论哪种分区方案,分区再平衡通常至少满足
- 平衡之后,负载、数据存储、读写请求等应该再集群范围更均匀的分布
- 在平衡执行过程中,数据库应该可以继续正常提供读写服务
- 避免不必要的负载迁移,以加快动态平衡,并减少网络和磁盘I/O影响
- 动态再平衡策略 P198
- 取模的缺点
- 使用固定数据量的分区 p199
- 动态分区
- 当分区的数据增长超过一个可配的参数阈值(HBase上默认是10GB),他就拆分为两个分区,每个承担一半的数据量,相反,如果有大量的数据被删除,并且分区缩小到某个阈值以下,则将其与相邻分区进行合并。该过程类似于B树的分裂操作
- 预分裂
- 按节点比例分区
- 请求路由
- 现在已经将数据集分布到多个节点上,但是仍有一个悬而未决的问题,当客户端需要发送请求时,如何知道应该连接哪个节点?如果发生了分区再平衡,分区与节点的对应关系随之还会变化。
- 几种不同的路由处理策略 P202
- 允许客户端链接任意的节点,如果恰好拥有所请求的分区,则直接处理该请求,否则,将请求转发到下一个合适的节点
- 将所有请求发送到一个路由层,由其进行转发
- 客户端感知分区和节点的关系。此时,客户端可以直接连接到目标节点,而不需要任何中介。
- 小结 P205
二、 事务
- 事务将应用程序的多个读、写操作捆绑在一起成为一个逻辑操作单元。即事务中的所有读写是一个执行的整体,整个事务要么成功(提交)、要么失败(终止或回滚)。如果失败,应用程序可以安全的重试
-
ACID的含义 P213
- 原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)与持久性(Durability)
- 不符合ACID标准的系统被冠以BASE,即基本可用性(Basically Available)、软状态(soft state)和最终一致性(Eventual consistency),但这基本没有什么实际作用
- 原子性定义的特征是:再出错时终止事务,并将部分完成的写入全部丢弃。
-
一致性 P214
- 一致性非常重要,但它在不同场合有着不同的具体含义,例如:
- 讨论副本一致性以及异步复制模型时,引出了最终一致性问题(参阅第五章复制滞后问题)
- 一致性哈希则是某些系统用于动态分区再平衡的方法(参见第6章一致性哈希)
- CAP理论中,一致性一次用来表示线性化(参见第9章“可线性化”)
- 而在ACID中,一致性主要指数据库处于应用程序所期待的“预期状态”
- ACID中的一致性主要是指对数据有特定的预期状态,任何数据更改必须满足这些状态约束(或者恒等条件)
- 一致性非常重要,但它在不同场合有着不同的具体含义,例如:
- 原子性,隔离性和持久性是数据库自身的属性,而ACID中的一致性更多的是应用层的属性
-
隔离性 P215
- 并发执行的多个事务相互隔离,他们不能互相交叉
-
持久性 P215
- 持久性保证一旦事务提交成功,及时存在硬件故障或数据库崩溃,事务所写入的任何数据也不会消失
- 对于某个特定链接,SQL语句BEGIN TRANSACTION和COMMIT之间的所有操作和属于同一个事务
- 单对象写入 P219
- 通常意义上的事务针对的是多个对象,将多个操作聚合为一个逻辑执行单元 P219
-
弱隔离级别 P221
- 读提交是最基本的事务隔离级别,它只提供以下两个保证
- 读数据库时,只能看到已成功提交的数据(防止脏读)
- 写数据库时,只会赴会已经成功提交的数据(防止脏写)
- 数据库通常采用行级锁来防止脏写 p224
- 当事务想修改某个对象(例如行或文档)时,它必须首先获得该对象的锁;然后一直持有锁直到事务提交(或终止)后,才能获得锁并继续 P224
- 防止脏写(采用读锁并不可行,运行较长时间的写事务会导致许多只读的事务等待太长时间)
- 对于每个待更新的对象,数据库都会维护其旧值和当前持锁事务将要设置的新值两个版本。在事务提交之前,所有其他读都读取旧值;仅当写事务提交之后,才会切换到读取新值
- 读提交是最基本的事务隔离级别,它只提供以下两个保证
-
快照级别隔离与可重复读 P225
- 如果查询的是数据库在某时刻点所冻结的一致性快照,则查询结果的含义非常明确
-
多版本并发控制(MVCC) P227
- 考虑到多个正在进行的事务可能会在不同的时间点查看数据库状态,所以数据库保留了对象多个不同的提交版本,这种技术因此也被称为多版本并发控制
- 典型的做法是,在读-提交级别下,对每一个不同的查询单独创建快照;而快照级别隔离则是使用一个快照来运行整个事务。
- 一致快照的可见性原则 P228
- 处理两个写事务并发的解决方案 P230
- 原子写操作,如果原子操作可行,那么它就是推荐的最佳方式
- 显式加锁
- 自动检测更新丢失 P232
- 原子比较和设置
- 冲突解决与复制
- 写倾斜与幻读 P233
- 写倾斜与幻读 P233
- 可以讲写倾斜视为一种更为广义的更新丢失问题。即如果两个事务读取相同的一组对象,然后更新其中一部分。
- 不同事务可能更新不同的对象,则可能发生写倾斜;
- 而不同的事务如果更新的是同一个对象,则可能发生脏写或更新丢失。
- 解决方案
- 串行化隔离级别
- 对事务依赖的行进行显式的加锁 P235
- 更多写倾斜的例子(会议室预定系统,多人游戏,声明一个用户名,防止双重开支) P236
- 产生写倾斜的原因 P236
- 幻读 P237
- 在一个事务中的写入改变了另一个事务查询结果的现象,称为幻读
- 实体化冲突解决幻读(书中不建议这么做)
- 串行化 P237
- 解决并发的方式是避免并发
- 串行执行小结,当满足以下约束条件时,串行执行事务可以实现串行化隔离
- 事务必须剪短而高效,否则一个缓慢的事务会影响到所有其他事务的执行性能;
- 仅限于活动数据集完全可以加载到内存的场景。有些很少访问的数据可能会被移到磁盘,但万一单线程事务需要访问它,就会严重拖累性能
- 写入吞吐量必须足够低,才能在单个CPU核上处理;否则就需要采用分区,最好没有跨分区事务
- 跨分区事务虽然也可以支持,但是占比必须很小
- 两阶段加锁 P242
- 快照级别隔离的口号“读写互不干扰” P243
- 谓词锁 P244
- 作用类似于之前描述的共享/独占锁,而区别在于,他并不属于某个特定的对象,而是作用与满足某些搜索条件的所有查询对象。
- 索引区间锁 P245
- 可串行化快照隔离 P246
- 小结 P250
三、分布式系统的挑战
- 在分布式系统中,可能会出现系统的一部分工作正常,但其他某些部分出现难以预测的故障,称之为“部分失效”
- 无共享并不是构建集群系统的唯一方式,但它却是构建互联网服务的主流方式 P263
- 不可靠的网络 P262
- 检测故障 p265
- 许多系统都需要自动检测节点失效这样的功能,例如:
- 负载均衡器要避免向已失效的节点继续分发请求(即将其做下线处理)
- 对于主从复制的分布式数据库,如果主节点失败,需要将某个从节点提升为主节点。不过,由于网络的不确定性很难准确判断节点是否确实失效。
- …还有4点,直接看书吧:)
- 许多系统都需要自动检测节点失效这样的功能,例如:
- 超时是故障检测的有效办法,但不幸的是,超时的时常没有标准可言 P266
- 网络拥塞与排队
- 驾车时往往由于交通堵塞,导致行车时间变化很大,同样,计算机网络上数据包延时的变化根源往往在于排队 P267
- 为什么数据中心网络和互联网采用分组交换而不是电路交换网络呢?答案是,他们针对突发流量进行了很多优化 P270
- 总之,当前广泛部署的技术无法为我们提供延迟或可靠性方面的硬件级保证,我们必须假设会出现网络拥塞,排队,和无上限的延迟。基于此,超时设置并不存在绝对正确的值,而是需要通过实验的方式来确定。 P270
- 延迟与资源利用率 P271
- 如果资源总是静态分配(例如,专用硬件和预留带宽分配),则某些环境下可以保证延迟的确定性。但是,这是以降低资源使用率为代价的,换句话说,其成本过于昂贵,而多租户、动态资源分配方式则可以提供更高的资源使用率,因而成本更低,当然也引入了可变延迟的缺点
- 网络中的可变延迟并不是一种自然规律,只是成本与收益互相博弈的结果
- 分布式系统多节点间复制数据时,可能由于时钟的时间差导致数据库写入丢失 P276
- 快照隔离,广泛用于小数据量,快速读写的事务以及大数据量,长时间运行的只读事务(例如设备分析),可以在数据库的某个一致状态上不需加锁、不违背读写隔离性的前提下高效支持只读事务。 P 278
- 开发实时系统代价昂贵的原因 P282
- 知识,真相与谎言 P282
- 如果节点存在“撒谎”的情况(即故意发送错误的活破坏性的响应),那么分布式系统处理的难度就上了一个台阶。例如,节点明明没有收到某条消息,但却对外声称收到了。这种行为称为拜占庭故障,在这样不信任的环境中需要达成共识的问题也被称为拜占庭将军问题 P286
- 在绝大多数服务器端数据系统中,部署拜占庭容错解决方案基本不太可行。
- 系统协议异常复杂
- 依赖于硬件层面的支持
- 对于真实系统的建模,最普遍的组合是崩溃-恢复模型结合部分同步模型。 P290
- 小结 P292
四、一致性与共识
- 分布式系统可能发生的故障:数据包丢失、顺序紊乱、重复发送或者延迟、时钟偏差、节点暂停(例如由于垃圾回收)甚至随时崩溃
- 分布式系统最重要的抽象之一就是共识:所有的节点就某一项提议达成一致 P303
- 为了使系统可线性化,需要添加一个重要的约束:一旦某个读操作返回了新值,之后所有的读(包括相同或不同客户端)都必须返回新值 P308
- 可线性化与可串行化的区别 P311
- 主从复制的系统需要确保有且只有一个主节点,否则会产生脑裂;选举行的主节点的常见的方法是使用锁:即每个启动的节点都试图获得锁,其中只有一个可以成功,即称为主节点。 P311
- 不管锁具体如何实现,他必须满足线性化:所有节点都必须同意哪个节点持有锁,否则就会出现问题。
- 线性化本质上意味着“表现的好像只有一个数据副本,且其上的所有操作都是原子的 ”。 P313
- CAP理论 P317
- 不要求线性化的应用更能容忍网络故障,这种思路通常被称为CAP定理
- CAP有时也代表一致性,可用性,分区容错性,系统通常只能支持其中两种特性。 P317
- 不过,上述理解存在误导性,网络区分是一种故障,不管喜欢还是不喜欢,它都可能发生,所以无法选择或逃避区分的问题。
- 在网络正常的时候,系统可以同时保证一致性(线性化)和可用性,而一旦发生了网络故障,必须要么选择线性(一致性),要么可用性。因此,更准确的称呼应该是“网络区分情况下,选择一致还是可用”。
- 围绕了CAP有太多的误解与困扰,最后反而无法帮助我们更好的理解系统(书中建议所以最好避免使用CAP)
- 之所以放弃线性化的原因就是性能,而不是为了容错。 P318
- 在可线性化数据存储中不存在并发操作,一定有一个时间线将所有操作都全序执行。 P321
- 可线性化一定因为着因果关系 P322
- Lamport时间戳 P325
- Lamport时间戳与物理墙上时钟并不存在直接对应关系,但它可以保证全序:给定两个Lamport时间戳,计数器较大的那个时间戳大;如计数器值正好相同,则节点越大,时间戳越大。
- 全序关系广播 P327
- 全序关系广播正式数据复制所需要的:如果每条消息代表数据库写请求,并且每个副本都按相同的顺序处理这些请求,那么所有副本可以保持一致(或许有些滞后);该原则也被称为状态机复制。 P328
-
分布式事务与共识 P330
- 共识问题是分布式计算中最重要也是最基本的问题之一。表面上看,目标只是让几个节点就某件事达成一致。这似乎很简单,或者至少不应该太难。不幸的是,许多失败的系统正式由于低估了这个问题所导致的。
- 需要集群节点达成一致的部分重要场景 P331
- 主节点选举
- 原子事务提交
- 两阶段提交(2PC)算法是解决原子提交最常见的方法。在各种数据库、消息系统和应用服务器中都有实现。
- 单节点上,事务的提交非常依赖于数据持久写入磁盘的顺序关系:先写入数据,然后在提交记录。 P322
- 事务提交(或)终止的关键点在于磁盘完成日志记录的时刻:在完成日志记录写之前如果发生了崩溃,则事务需要终止;
- 如果在日志写入完成之后,即使放生崩溃,事务也被安全提交。
- 通常2PC事务从应用程序在多数据库节点上执行数据读/写开始。我们将这些数据库节点称为事务中的参与者。当应用程序准备提交事务时,协调者开始阶段1:发送一个准备请求到所有节点,询问他们是否可以提交。协调周然后跟踪参与者的回应 P334
- 如果所有参与者回答“是”,表示他们已经准备好提交,那么协调者接下来在阶段2会发出提交请求,提交开始实际执行
- 如果有任何参数者会回复“否”,则协调者在阶段2中想所有节点发送放弃请求
- 2PC协议有两个关键点 P336
- 当参与者投票“是”时,它做出肯定提交的承诺(尽管还取决于其他参与者的投票,协调者才能做出做后的决定)
- 协调者做出了提交(或者放弃)的决定,这个决定也是不可撤销。正是这两个承诺确保了2PC的原子性
- 单节点的原子提交其实是将两个事件合二为一,写入事务日志即提交。
-
协调者发生故障 P336
- 如果在决定到达之前,出现协调者崩溃或者网络故障,则参与者只能无奈等待。此时参与者处在一种不确定的状态。
- 2PC能够顺利完成的唯一方法是等待协调者恢复。
- 两种不同的分布式数据库概念 P338
- 数据库内部的分布式事务
- 异构分布式事务
- XA事务 P339
- XA事务解决了多个参与者之间如何达成一致这样一个非常现实而重要的问题,但也引入了不少操作方面的限制 P341
-
共识问题总结 P341
- 通过使用共识算法来决定这些不相容的操作之中谁是获胜者
-
共识算法必须满足以下性质 P342
- 协商一致性,所有节点都接受相同的协议
- 诚实性,所有节点不能反悔,即对一项提议不能有两次决定
- 合法性,如果决定了值v,则v一定是由某个节点所提议的
- 可终止性,节点如果不崩溃最终一定可以达成决议
- 全序关系广播相当于持续的多轮共识(每一轮共识决定对应于一条消息) P343
- Epoch:世代编号,对于主从复制
- 如果法相当前的主节点失效,节点就开始一轮投票选举新的主节点。
- 选举会赋予一个单调递增的epoch号
- 如果出现了两个不同的节点对应于不同epoch号码(例如上一个epoch号码的主节点其实并没有正真挂掉),则具有更高epoch号码的主节点将获胜
- 共识的局限性 P345
- ZooKeeper和etcd主要针对保存少量、可完全载入内存的数据(虽然他们最终仍要写入磁盘以支持持久性)而设计,所以不要用他们保存大量的数据。他们通常采用容错的全序广播算法在所有节点上复制这些数据从而实现高可靠 P347
- 小结 P349
参考文档
《数据密集型应用系统设计》