diff --git a/ch09.md b/ch09.md index 7153699..2187399 100644 --- a/ch09.md +++ b/ch09.md @@ -16,7 +16,7 @@ > 为什么共识协议如此重要呢?他和真实系统的连接点在于哪里?答曰,**操作日志**。而大部分**数据系统**都可以抽象为一系列**数据操作**的依次施加,即状态机模型。而共识协议可以让多机对某个**确定**的**操作序列**达成共识,进而对系统的任意状态达成共识。 -一旦我们实现了**共识协议**,应用层可以依赖其做很多事情。例如,你有一个使用单主模型的数据库,如果主副本所在节点宕机,我们便可以使用共识协议选出新的主。在第五章处理节点下线(**Handling Node Outages**)一节中我们提到过,只有唯一的主,并且所有副本都认可该主,是一个需要确保的非常重要的特性。如果有超过一个节点都认为自己是主,我们称之为**脑裂**(split brain)。脑裂很容易导致数据丢失,而正确实现是的共识协议能够避免该问题。 +一旦我们实现了**共识协议**,应用层可以依赖其做很多事情。例如,你有一个使用单主模型的数据库,如果主副本所在节点宕机,我们便可以使用共识协议选出新的主。在第五章处理节点下线(**Handling Node Outages**)一节中我们提到过,只有唯一的主,并且所有副本都认可该主,是一个需要确保的非常重要的特性。如果有超过一个节点都认为自己是主,我们称之为**脑裂**(split brain)。脑裂很容易导致数据丢失,而正确实现的共识协议能够避免该问题。 在本章稍后的地方,“分布式事务和共识协议”一小节里,我们将会详细讨论用以解决共识相关问题的算法。但在此之前,我们需要探索下分布式系统中我们可以提供的**保证**和**抽象**有哪些。 @@ -32,7 +32,7 @@ 但这是一个相当不靠谱的保证——没有提供任何关于**何时**收敛的信息。而在收敛之前,对于相同数据的读取,可能会返回任意值甚至不返回。举个例子,你向多副本数据库中写入了一条数据,并立即读取他。你能读到什么,最终一致性对此不会提供任何保证,因为读取请求可能会被路由到任何其他副本。 -最终一致性对于应用层开发者很不友好,因为它表现出的行为和单线程程序中的变量完全不一致。在**单线程模型**里,如果对某个变量赋值后立即读取,我们默认一定会读到刚才的赋值,而不是**读到旧值**或者**读取失败**。数据库在对外表现上很像一组可读写的**变量集**,但具有复杂的多的语义。 +最终一致性对于应用层开发者很不友好,因为它表现出的行为和单线程程序中的变量完全不一致。在**单线程模型**里,如果对某个变量赋值后立即读取,我们默认一定会读到刚才的赋值,而不是**读到旧值**或者**读取失败**。数据库在对外表现上很像一组可读写的**变量集**,但具有复杂得多的语义。 在使用只提供弱保证的数据库时,我们需要**时刻**记得其限制,而不能偶尔自己增加额外假设,否则,会产生非常致命且难以察觉的 BUG。因为大部分时间里,应用层表现得毫无波澜,只有在系统中出现故障(网络拥塞、节点宕机)或在高负载场景下,这些边缘情况才会被触发。 @@ -55,7 +55,7 @@ 在提供最终一致性语义的数据库里,如果你问不同副本同一个问题(比如说查询某条数据),则很可能得到不同的回答(响应),这就很让人迷惑了。如果多副本数据库在行为上能够表现的像只有一个副本,应用层编程将会简单很多。这样在任意时刻,每个客户端所看到的数据视图都是一样的,而不用去担心引入多副本带来的**副本滞后**(replication lag)等问题。 -这就是**线性一致性**(linearizability)的基本思想,他还有很多其他称呼:原子一致性(atomic consistency)、强一致性(strong consistency)、即时一致性(immediate consistency),或者外部一致性(external consistency)。线性一致性的精确定义很精妙,本节余下部分会进行详细探讨。但其基本思想是,一个系统对外表现的像所有数据**只有一个副本**,作用于数据上的操作都可以**原子的完成**。有了这个保证,不管系统中实际上有多少副本,应用层都不用关心。这种抽象,或者说保证,类似于编程中的接口。 +这就是**线性一致性**(linearizability)的基本思想,他还有很多其他称呼:原子一致性(atomic consistency)、强一致性(strong consistency)、即时一致性(immediate consistency),或者外部一致性(external consistency)。线性一致性的精确定义很精妙,本节余下部分会进行详细探讨。但其基本思想是,一个系统对外表现的像所有数据**只有一个副本**,作用于数据上的操作都可以**原子地完成**。有了这个保证,不管系统中实际上有多少副本,应用层都不用关心。这种抽象,或者说保证,类似于编程中的接口。 在一个提供线性一致性的系统中,只要某个客户端成功的进行了写入某值,其他所有客户端都可以在数据库中读到该值。提供单副本的抽象,意味着客户端任何时刻读到的都是**最近、最新**(up-to-date)的值,而不会是过期缓存、副本中的旧值。换句话说,线性一致性是一种数据**新鲜度保证**(recency guarantee)。为了理解这个说法,让我们看一个非线性一致性系统的例子: @@ -315,7 +315,7 @@ CAP 最初被提出只是一个为了激发数据库取舍讨论的模糊的取 - **线性一致性**(Linearizability):让我们回忆下对于可线性化的理解,可线性化对外表现的像**所有操作都发生于单副本上,并且会原子性的完成**。这就意味着,对于任意两个操作,我们总是可以确定其发生的先后关系,也即在可线性化系统中,所有的操作顺序满足全序关系。如之前图 9-4 中给的例子。 - **因果一致性**(Causality)。如果我们无从判定两个操作的先后关系,则称之为**并发的**(concurrent,参见[发生于之前和并发关系](https://ddia.qtmuniao.com/#/ch05?id=%e5%8f%91%e7%94%9f%e4%ba%8e%e4%b9%8b%e5%89%8d%ef%bc%88happens-before%ef%bc%89%e5%92%8c%e5%b9%b6%e5%8f%91%e5%85%b3%e7%b3%bb))。从另一个角度说,如果两个事件因果相关,则其一定有序。也即,因果性定义了一种**偏序**(partial order)关系,而非全序关系:有些操作存在因果,因此可比;而另外一些操作则是并发的,即不可比。 -根据上述解释,在线性一致性的数据存储服务中,是不存在并发操作的:**因为必然存在一个时间线能将所有操作进行排序**。同一时刻可能会有多个请求到来,但是线性化的存储服务可以保证:**所有请求都会在单个副本上、一个单向向前的时间线上的某个时间点被原子的处理,而没有任何并发**。 +根据上述解释,在线性一致性的数据存储服务中,是不存在并发操作的:**因为必然存在一个时间线能将所有操作进行排序**。同一时刻可能会有多个请求到来,但是线性化的存储服务可以保证:**所有请求都会在单个副本上、一个单向向前的时间线上的某个时间点被原子地处理,而没有任何并发**。 并发(concurrency)意味着时间线的分叉与合并。但在重新合并之时,来自两个时间分支的操作就有可能出现不可比的情况。在第五章的图 5-14(参见[确定 Happens-Before 关系](https://ddia.qtmuniao.com/#/ch05?id=%e7%a1%ae%e5%ae%9a-happens-before-%e5%85%b3%e7%b3%bb))中我们见过类似的现象,所有的事件不在一条时间线上,而是有相当复杂的图形依赖。图中的每个箭头,本质上定义了一种因果依赖,也即偏序关系。 @@ -650,7 +650,7 @@ Lamport 时间戳不依赖于物理时钟,但可以提供全序保证,对于 异构的分布式事务系统可以将多种异构的系统,以强大的方式进行整合。例如,**当且仅当**数据库中处理消息的事务成功提交时,消息队列才会将该消息标记为**已处理**。可以将消息确认和数据库写入打包在单个事务里进行原子提交,来实现上述行为。在分布式事务的加持下,即使消息队列和数据库是跑在不同机器上的不同技术栈的进程,上述目标也能实现。 -如果**消息投递**或**数据库事务**任意一方出错,两者都会被中止。据此,消息队列可以在之后安全地重新投递该消息。通过将**消息投递**和**消息处理**打包进行原子的提交,不管成功之前重试多少次,我们都可以保证该消息只被**有效地**(effectively)处理**恰好一次**(exactly once)。中止事务时,会丢弃所有部分执行的结果。 +如果**消息投递**或**数据库事务**任意一方出错,两者都会被中止。据此,消息队列可以在之后安全地重新投递该消息。通过将**消息投递**和**消息处理**打包进行原子地提交,不管成功之前重试多少次,我们都可以保证该消息只被**有效地**(effectively)处理**恰好一次**(exactly once)。中止事务时,会丢弃所有部分执行的结果。 **只有参与系统都支持原子提交时,上述分布式事务才是可行的**。例如,假设处理消息的一个副作用是发送邮件,且邮件服务器不支持两阶段提交。则在消息处理失败进行重试的过程中,可能出现邮件被发送多次的现象。但如果,在事务中止时,消息处理的所有副作用都可以回滚,则处理步骤可以像没有任何事情发生过一样,安全地进行重试。