fix italics in ch09

This commit is contained in:
songpengwei 2024-03-05 09:54:08 +08:00
parent f903431adb
commit b49cac7cee

20
ch09.md
View File

@ -12,7 +12,7 @@
构建一个容错系统最好的方法是:**找到一些基本抽象,可以对上提供某些承诺,应用层可以依赖这些承诺来构建系统,而不必关心底层细节**。在第七章中,通过使用事务,应用层可以假设不会发生宕机(**原子性**,意思是不会因为宕机出现让事务停留在半成功的状态),没有其他应用并发访问数据库数据(**隔离性**),且存储系统非常可靠(**持久性**)。事务模型会隐藏节点宕机、**竞态条件**race conditions、硬盘故障等底层细节即使这些问题出现了应用层也不必关心。
本章将继续讨论一些可以减轻应用层负担的分布式系统中的**基本抽象**。比如,分布式系统中最重要的一个抽象——**共识**consensus_让所有节点在**某件事情**上达成一致_。在本章稍后的讨论可以看出,让系统中的所有节点在有网络故障和节点宕机的情况下达成共识,是一件非常棘手的事情。
本章将继续讨论一些可以减轻应用层负担的分布式系统中的**基本抽象**。比如,分布式系统中最重要的一个抽象——**共识**consensus*让所有节点在**某件事情**上达成一致*。在本章稍后的讨论可以看出,让系统中的所有节点在有网络故障和节点宕机的情况下达成共识,是一件非常棘手的事情。
> 为什么共识协议如此重要呢?他和真实系统的连接点在于哪里?答曰,**操作日志**。而大部分**数据系统**都可以抽象为一系列**数据操作**的依次施加,即状态机模型。而共识协议可以让多机对某个**确定**的**操作序列**达成共识,进而对系统的任意状态达成共识。
@ -28,7 +28,7 @@
在第五章中日志滞后问题Problems with Replication Lag小节我们分析了一些多副本数据所遇到的时序问题。在相同时刻如果对比多副本数据库中一份数据的两个副本我们可能会看到不一致的数据。这是因为写请求到达不同的数据节点总会存在一个**时间差**。无论我们使用什么数据**副本模型**(单主、多主和无主),这种数据的不一致性都有可能会发生。
大部分**多副本数据库**replicated databases提供**最终一致性**eventually consistency的保证这意味着只要你对数据库停写并等待足够长的时间则所有对相同数据的读取请求最终会返回相同的结果。从另一个角度来说所有的不一致都是暂时的最终都会被解决当然这得是在网络故障能最终修复的假设之下。描述相同意思的一个更好的名字可能**收敛性**_convergence_),即最终,所有副本都会收敛到相同的值。
大部分**多副本数据库**replicated databases提供**最终一致性**eventually consistency的保证这意味着只要你对数据库停写并等待足够长的时间则所有对相同数据的读取请求最终会返回相同的结果。从另一个角度来说所有的不一致都是暂时的最终都会被解决当然这得是在网络故障能最终修复的假设之下。描述相同意思的一个更好的名字可能**收敛性***convergence*),即最终,所有副本都会收敛到相同的值。
但这是一个相当不靠谱的保证——没有提供任何关于**何时**收敛的信息。而在收敛之前,对于相同数据的读取,可能会返回任意值甚至不返回。举个例子,你向多副本数据库中写入了一条数据,并立即读取他。你能读到什么,最终一致性对此不会提供任何保证,因为读取请求可能会被路由到任何其他副本。
@ -252,7 +252,7 @@ CAP 最初被提出只是一个为了激发数据库取舍讨论的模糊的取
> CAP 有时候被表述为在做系统设计时一致性consistency、可用性Availability、分区容错性Partition tolerance只能三取其二。然而这种说法极具误导性因为网络分区是一种故障类型而不是一种可以取舍的选项不管你喜欢还是不喜欢它都在那。当然也有人理解为用单机系统可以规避但我们当下讨论的前提是分布式系统。
> 在网络正常连通时,系统可以同时提供一致性(线性一致性)和完全的可用性。当网络故障发生时,你必须在线性一致性和完全可用性之间二选一。因此,对于 CAP 更好的一个表述可能是:**当网络出现分区时,一致性和可用性只能二选其一**_either Consistent or Available when Partitioned_)。一个可靠的网络,可以减少其上的系统该选择的次数,但无论如何,分布式系统中,该选择是无法避免的。
> 在网络正常连通时,系统可以同时提供一致性(线性一致性)和完全的可用性。当网络故障发生时,你必须在线性一致性和完全可用性之间二选一。因此,对于 CAP 更好的一个表述可能是:**当网络出现分区时,一致性和可用性只能二选其一***either Consistent or Available when Partitioned*)。一个可靠的网络,可以减少其上的系统该选择的次数,但无论如何,分布式系统中,该选择是无法避免的。
> 在有关 CAP 的讨论,有几种关于可用性的大相径庭的定义,且将 CAP 升格为定理并给出证明中的提到的**形式化的可用性**并非通常意义中所说的可用性。很多所谓“高可用”的系统通常并不符合 CAP 定理中关于可用性的独特idiosyncratic定义。总而言之CAP 有很多容易误解和模糊不清的概念,并不能帮助我们更好的理解系统,因此最好不用 CAP 来描述一个系统。
@ -304,7 +304,7 @@ CAP 最初被提出只是一个为了激发数据库取舍讨论的模糊的取
这和现实生活一样,一件事的发生会引起另一件的事的出现:一个节点读取数据之后,**依据**读取内容依赖于读取写入了一些数据另一个节点读取这些写入进而写入了另外一些数据循环往复。操作operation发生的**依赖链条**定义了系统中事件的因果——也即,**谁居先,谁处后**。
如果一个系统遵循因果约束,则我们称其为**因果一致的**_causally consistent_)。比如,快照隔离就可以提供因果一致性:当从数据库读取数据的时候,如果你能读到某个时间点的数据,就一定能读到其之前的数据(当然,要在该数据还没有被删除的情况下)。
如果一个系统遵循因果约束,则我们称其为**因果一致的***causally consistent*)。比如,快照隔离就可以提供因果一致性:当从数据库读取数据的时候,如果你能读到某个时间点的数据,就一定能读到其之前的数据(当然,要在该数据还没有被删除的情况下)。
### 因果序非全序
@ -427,7 +427,7 @@ Lamport 时间戳不依赖于物理时钟,但可以提供全序保证,对于
小结一下,在分布式系统中,为了实现类似于针对用户名的**唯一性约束**,仅为所有时间进行全局定序是不够的,你还需要知道该定序何时完成。对于某个创建账户的操作,如果我们能够确定在最终的全序里,不会有其他操作插到该操作之前,我们便可以安全的让该操作成功。
确定全局定序何时收敛,将会在接下来的小节 —— **全序广播**_total order broadcast_)中讨论。
确定全局定序何时收敛,将会在接下来的小节 —— **全序广播***total order broadcast*)中讨论。
## 全序广播
@ -483,11 +483,11 @@ Lamport 时间戳不依赖于物理时钟,但可以提供全序保证,对于
由于所有日志条目都会以同样的顺序送达每个节点,若有并发写入,则所有节点都能依靠日志顺序就谁“先来后到”达成一致。当有同名冲突时,可以选择第一条作为赢家,并舍弃其后的冲突请求。可以使用类似的方式,基于日志来实现涉及到多对象的事务的可串行化。
尽管该方式能够提供线性化的写入,却不能保证线性化的读取。如果你从一个异步同步日志的节点读取日志,就有可能读到陈旧的数据(更精确一点说,上述过程能够提供**顺序一致性**s*equential consistency*,有时也被称为**时间线一致性**_timeline consistency_,比线性一致性稍弱)。在此基础上,如果想让读取也变得可线性化,有几种做法:
尽管该方式能够提供线性化的写入,却不能保证线性化的读取。如果你从一个异步同步日志的节点读取日志,就有可能读到陈旧的数据(更精确一点说,上述过程能够提供**顺序一致性**s*equential consistency*,有时也被称为**时间线一致性***timeline consistency*,比线性一致性稍弱)。在此基础上,如果想让读取也变得可线性化,有几种做法:
- 让读取也走日志,即通过追加消息的方式将读取顺序化,然后当读取请求所在节点**收到**这条读取日志时才去真正的去读。则消息在日志中的位置定义了整个时间序列中读取真正发生的时间点。etcd 中的法定读取就是用的类似的做法)
- 如果日志服务允许查询最新日志的位置,则可以在请求到来时,获取当时最新位置,然后不断查询日志看是否已经跟到最新位置。如果跟到了,就进行读取。(这是 Zookeeper 中 sync() 操作的背后的原理)
- 可以将读取路由到写入发生的那个节点,或者与写入严格同步的节点,以保证能够读到最新的内容。(这种技术用于**链式复制**_chain replication_ 中)
- 可以将读取路由到写入发生的那个节点,或者与写入严格同步的节点,以保证能够读到最新的内容。(这种技术用于**链式复制***chain replication* 中)
### 使用线性一致存储实现全序广播
@ -558,7 +558,7 @@ Lamport 时间戳不依赖于物理时钟,但可以提供全序保证,对于
**事务提交后是不可撤销的**——在事务提交后,你不能再改变主意说,我要重新中止这个事务。这是因为,一旦事务提交了,就会对其他事务可见,从而可能让其他事务依赖于该事务的结果做出一些新的决策;这个原则构成了**读已提交**read commited隔离级别的基础参见[读已提交](https://ddia.qtmuniao.com/#/ch07?id=%e8%af%bb%e5%b7%b2%e6%8f%90%e4%ba%a4))。如果事务允许在提交后中止,其他已经读取了该事务结果的事务也会失效,从而引起事务的级联中止。
当然,事务所造成的**结果**在事实上是可以被撤销的,比如,通过**补偿事务**_compensating transaction_)。但,从数据库的视角来看,这就是另外一个事务了;而跨事务的正确性,需要应用层自己来保证。
当然,事务所造成的**结果**在事实上是可以被撤销的,比如,通过**补偿事务***compensating transaction*)。但,从数据库的视角来看,这就是另外一个事务了;而跨事务的正确性,需要应用层自己来保证。
### 两阶段提交简介
@ -568,7 +568,7 @@ Lamport 时间戳不依赖于物理时钟,但可以提供全序保证,对于
![Untitled](img/ch09-fig09.png)
> **不要混淆 2PC 和 2PL**。Two-phase _commit_ (2PC) 和 two-phase locking (2PL参见[两阶段锁](https://ddia.qtmuniao.com/#/ch07?id=%e4%b8%a4%e9%98%b6%e6%ae%b5%e9%94%81)) 是两个完全不同的概念。2PC 是为了在分布式系统中进行原子提交,而 2PL 是为了进行事务并发控制的一种加锁方式。为了避免歧义,可以忽略他们在名字简写上的相似性,而把它们当成完全不同的概念。
> **不要混淆 2PC 和 2PL**。Two-phase commit (2PC) 和 two-phase locking (2PL参见[两阶段锁](https://ddia.qtmuniao.com/#/ch07?id=%e4%b8%a4%e9%98%b6%e6%ae%b5%e9%94%81)) 是两个完全不同的概念。2PC 是为了在分布式系统中进行原子提交,而 2PL 是为了进行事务并发控制的一种加锁方式。为了避免歧义,可以忽略他们在名字简写上的相似性,而把它们当成完全不同的概念。
2PC 引入了一个单机事务中没有的角色:**协调者**coordinator有时也被称为事务管理器transaction manager。协调者通常以库的形式出现并会嵌入到请求事务的应用进程中但当然它也可以以单独进程或者服务的形式出现。比如说Narayana, JOTM, BTM, or MSDTC.
@ -692,7 +692,7 @@ XA 不是一个网络协议——它定义了一组和事务协调者交互的 C
唯一的出路是让管理员手动的来提交或者中止事务。管理员首先需要检查所有包含未定事务的参与者,看是否有任何参与者提交或者中止了,从而对其他卡主的参与者手动执行相同操作(**通过外力来让所有参与者达成一致**)。解决该问题需要大量手工操作,并且在线上环境中断服务的巨大压力和时间限制下(不然,为什么协调者会处在此种错误状态下?)。
很多 XA 事务的实现会留有紧急后门,称为**启发式决策**_heuristic decisions_):允许一个参与者不用等待协调者的决策,而**单方面**决定中止还是提交一个未定事务。需要说明的是,这里的启发式仅仅是**可能打破原子性**probably breaking atomicity的一种委婉说法。因为这么做可能会违反两阶段提交所提供的保证。因此这种启发式决策仅是为了救急而不能进行日常使用。
很多 XA 事务的实现会留有紧急后门,称为**启发式决策***heuristic decisions*):允许一个参与者不用等待协调者的决策,而**单方面**决定中止还是提交一个未定事务。需要说明的是,这里的启发式仅仅是**可能打破原子性**probably breaking atomicity的一种委婉说法。因为这么做可能会违反两阶段提交所提供的保证。因此这种启发式决策仅是为了救急而不能进行日常使用。
### 分布式事务的限制