Update ch09.md

This commit is contained in:
qtmuniao 2023-11-02 16:23:11 +00:00 committed by GitHub
parent db2f79d47a
commit c0fb3738a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

10
ch09.md
View File

@ -16,7 +16,7 @@
> 为什么共识协议如此重要呢?他和真实系统的连接点在于哪里?答曰,**操作日志**。而大部分**数据系统**都可以抽象为一系列**数据操作**的依次施加,即状态机模型。而共识协议可以让多机对某个**确定**的**操作序列**达成共识,进而对系统的任意状态达成共识。 > 为什么共识协议如此重要呢?他和真实系统的连接点在于哪里?答曰,**操作日志**。而大部分**数据系统**都可以抽象为一系列**数据操作**的依次施加,即状态机模型。而共识协议可以让多机对某个**确定**的**操作序列**达成共识,进而对系统的任意状态达成共识。
一旦我们实现了**共识协议**,应用层可以依赖其做很多事情。例如,你有一个使用单主模型的数据库,如果主副本所在节点宕机,我们便可以使用共识协议选出新的主。在第五章处理节点下线(**Handling Node Outages**)一节中我们提到过,只有唯一的主,并且所有副本都认可该主,是一个需要确保的非常重要的特性。如果有超过一个节点都认为自己是主,我们称之为**脑裂**split brain。脑裂很容易导致数据丢失而正确实现的共识协议能够避免该问题。 一旦我们实现了**共识协议**,应用层可以依赖其做很多事情。例如,你有一个使用单主模型的数据库,如果主副本所在节点宕机,我们便可以使用共识协议选出新的主。在第五章处理节点下线(**Handling Node Outages**)一节中我们提到过,只有唯一的主,并且所有副本都认可该主,是一个需要确保的非常重要的特性。如果有超过一个节点都认为自己是主,我们称之为**脑裂**split brain。脑裂很容易导致数据丢失而正确实现的共识协议能够避免该问题。
在本章稍后的地方,“分布式事务和共识协议”一小节里,我们将会详细讨论用以解决共识相关问题的算法。但在此之前,我们需要探索下分布式系统中我们可以提供的**保证**和**抽象**有哪些。 在本章稍后的地方,“分布式事务和共识协议”一小节里,我们将会详细讨论用以解决共识相关问题的算法。但在此之前,我们需要探索下分布式系统中我们可以提供的**保证**和**抽象**有哪些。
@ -32,7 +32,7 @@
但这是一个相当不靠谱的保证——没有提供任何关于**何时**收敛的信息。而在收敛之前,对于相同数据的读取,可能会返回任意值甚至不返回。举个例子,你向多副本数据库中写入了一条数据,并立即读取他。你能读到什么,最终一致性对此不会提供任何保证,因为读取请求可能会被路由到任何其他副本。 但这是一个相当不靠谱的保证——没有提供任何关于**何时**收敛的信息。而在收敛之前,对于相同数据的读取,可能会返回任意值甚至不返回。举个例子,你向多副本数据库中写入了一条数据,并立即读取他。你能读到什么,最终一致性对此不会提供任何保证,因为读取请求可能会被路由到任何其他副本。
最终一致性对于应用层开发者很不友好,因为它表现出的行为和单线程程序中的变量完全不一致。在**单线程模型**里,如果对某个变量赋值后立即读取,我们默认一定会读到刚才的赋值,而不是**读到旧值**或者**读取失败**。数据库在对外表现上很像一组可读写的**变量集**,但具有复杂多的语义。 最终一致性对于应用层开发者很不友好,因为它表现出的行为和单线程程序中的变量完全不一致。在**单线程模型**里,如果对某个变量赋值后立即读取,我们默认一定会读到刚才的赋值,而不是**读到旧值**或者**读取失败**。数据库在对外表现上很像一组可读写的**变量集**,但具有复杂多的语义。
在使用只提供弱保证的数据库时,我们需要**时刻**记得其限制,而不能偶尔自己增加额外假设,否则,会产生非常致命且难以察觉的 BUG。因为大部分时间里应用层表现得毫无波澜只有在系统中出现故障网络拥塞、节点宕机或在高负载场景下这些边缘情况才会被触发。 在使用只提供弱保证的数据库时,我们需要**时刻**记得其限制,而不能偶尔自己增加额外假设,否则,会产生非常致命且难以察觉的 BUG。因为大部分时间里应用层表现得毫无波澜只有在系统中出现故障网络拥塞、节点宕机或在高负载场景下这些边缘情况才会被触发。
@ -55,7 +55,7 @@
在提供最终一致性语义的数据库里,如果你问不同副本同一个问题(比如说查询某条数据),则很可能得到不同的回答(响应),这就很让人迷惑了。如果多副本数据库在行为上能够表现的像只有一个副本,应用层编程将会简单很多。这样在任意时刻,每个客户端所看到的数据视图都是一样的,而不用去担心引入多副本带来的**副本滞后**replication lag等问题。 在提供最终一致性语义的数据库里,如果你问不同副本同一个问题(比如说查询某条数据),则很可能得到不同的回答(响应),这就很让人迷惑了。如果多副本数据库在行为上能够表现的像只有一个副本,应用层编程将会简单很多。这样在任意时刻,每个客户端所看到的数据视图都是一样的,而不用去担心引入多副本带来的**副本滞后**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。为了理解这个说法让我们看一个非线性一致性系统的例子 在一个提供线性一致性的系统中,只要某个客户端成功的进行了写入某值,其他所有客户端都可以在数据库中读到该值。提供单副本的抽象,意味着客户端任何时刻读到的都是**最近、最新**up-to-date的值而不会是过期缓存、副本中的旧值。换句话说线性一致性是一种数据**新鲜度保证**recency guarantee。为了理解这个说法让我们看一个非线性一致性系统的例子
@ -315,7 +315,7 @@ CAP 最初被提出只是一个为了激发数据库取舍讨论的模糊的取
- **线性一致性**Linearizability让我们回忆下对于可线性化的理解可线性化对外表现的像**所有操作都发生于单副本上,并且会原子性的完成**。这就意味着,对于任意两个操作,我们总是可以确定其发生的先后关系,也即在可线性化系统中,所有的操作顺序满足全序关系。如之前图 9-4 中给的例子。 - **线性一致性**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关系而非全序关系有些操作存在因果因此可比而另外一些操作则是并发的即不可比。 - **因果一致性**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))中我们见过类似的现象,所有的事件不在一条时间线上,而是有相当复杂的图形依赖。图中的每个箭头,本质上定义了一种因果依赖,也即偏序关系。 并发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。中止事务时会丢弃所有部分执行的结果。
**只有参与系统都支持原子提交时,上述分布式事务才是可行的**。例如,假设处理消息的一个副作用是发送邮件,且邮件服务器不支持两阶段提交。则在消息处理失败进行重试的过程中,可能出现邮件被发送多次的现象。但如果,在事务中止时,消息处理的所有副作用都可以回滚,则处理步骤可以像没有任何事情发生过一样,安全地进行重试。 **只有参与系统都支持原子提交时,上述分布式事务才是可行的**。例如,假设处理消息的一个副作用是发送邮件,且邮件服务器不支持两阶段提交。则在消息处理失败进行重试的过程中,可能出现邮件被发送多次的现象。但如果,在事务中止时,消息处理的所有副作用都可以回滚,则处理步骤可以像没有任何事情发生过一样,安全地进行重试。