Merge pull request #151 from ZvanYang/patch-5

Update ch5.md
This commit is contained in:
YIN, Gang 2021-12-13 11:20:29 +08:00 committed by GitHub
commit 7bb62dd964
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

16
ch5.md
View File

@ -682,27 +682,27 @@ LWW实现了最终收敛的目标但以**持久性**为代价:如果同一
#### 合并同时写入的值
这种算法可以确保没有数据被无声地丢弃,但不幸的是,客户端需要做一些额外的工作:如果多个操作并发发生,则客户端必须通过合并并发写入的值来擦屁股。 Riak称这些并发值**兄弟siblings**。
这种算法可以确保没有数据被无声地丢弃,但不幸的是,客户端需要做一些额外的工作:客户端随后必须通过合并并发写入的值来进行清理。 Riak称这些并发值为**兄弟siblings**。
合并兄弟值,本质上是与多领导者复制中的冲突解决相同的问题,我们先前讨论过(请参阅“[处理写入冲突](#处理写入冲突)”)。一个简单的方法是根据版本号或时间戳(最后写入胜利)选择一个值,但这意味着丢失数据。所以,你可能需要在应用程序代码中做更聪明的事情。
合并并发值,本质上是与多领导者复制中的冲突解决问题相同,我们先前讨论过(请参阅“[处理写入冲突](#处理写入冲突)”)。一个简单的方法是根据版本号或时间戳(最后写入胜利)选择一个值,但这意味着丢失数据。所以,你可能需要在应用程序代码中额外更聪明的事情。
以购物车为例,一种合理的合并兄弟方法就是集合求并集。在[图5-14](img/fig5-14.png)中,最后的两个兄弟是[牛奶,面粉,鸡蛋,熏肉]和[鸡蛋,牛奶,火腿]。注意牛奶和鸡蛋同时出现在两个兄弟里,即使他们每个只被写过一次。合并的值可以是[牛奶,面粉,鸡蛋,培根,火腿],没有重复。
以购物车为例,一种合理的合并值的方法就是做并集。在[图5-14](img/fig5-14.png)中,最后的合并结果是[牛奶,面粉,鸡蛋,熏肉]和[鸡蛋,牛奶,火腿]。注意牛奶和鸡蛋同时出现在两个并发值里,即使他们每个只被写过一次。合并的值可以是[牛奶,面粉,鸡蛋,培根,火腿]他们没有重复。
然而,如果你想让人们也可以从他们的购物车中**删除**东西,而不是仅仅添加东西,那么把兄弟求并集可能不会产生正确的结果:如果你合并了两个兄弟购物车,并且只在其中一个兄弟值里删掉了它,那么被删除的项目会重新出现在并集终值中【37】。为了防止这个问题一个项目在删除时不能简单地从数据库中删除相反系统必须留下一个具有合适版本号的标记,以指示合并兄弟时该项目已被删除。这种删除标记被称为**墓碑tombstone**。 (我们之前在“[散列索引”](ch3.md#散列索引)中的日志压缩的上下文中看到了墓碑。)
然而,如果你想让人们也可以从他们的购物车中**删除**东西,而不是仅仅添加东西,那么把并发值做并集可能不会产生正确的结果:如果你合并了两个客户端的购物车,并且只在其中一个客户端里面删掉了它,那么被删除的项目会重新出现在这两个客户端的交集结果中【37】。为了防止这个问题一个项目在删除时不能简单地从数据库中删除相反系统必须留下一个具有适当版本号的标记,在合并兄弟时表明该项目已被删除。这种删除标记被称为**墓碑tombstone**。 (我们之前在“[散列索引”](ch3.md#散列索引)中的日志压缩的上下文中看到了墓碑。)
因为在应用程序代码中合并兄弟是复杂且易出错,所以有一些数据结构被设计出来用于自动执行这种合并,如“[自动冲突解决](#自动冲突解决)”中讨论的。例如Riak的数据类型支持使用称为CRDT的数据结构家族【38,39,55】可以以合理的方式自动合并兄弟,包括保留删除。
因为在应用程序代码中合并是复杂且易出错,所以有一些数据结构被设计出来用于自动执行这种合并,如“[自动冲突解决](#自动冲突解决)”中讨论的。例如Riak的数据类型支持使用称为CRDT的数据结构家族【38,39,55】可以以合理的方式自动合并包括保留删除。
#### 版本向量
[图5-13](img/fig5-13.png)中的示例只使用一个副本。当有多个副本但没有领导者时,算法如何修改?
[图5-13](img/fig5-13.png)使用单个版本号来捕获操作之间的依赖关系,但是当多个副本并发接受写入时,这是不够的。相反,除了对每个键使用版本号之外,还需要在**每个副本**中使用版本号。每个副本在处理写入时增加自己的版本号,并且跟踪从其他副本中看到的版本号。这个信息指出了要覆盖哪些值,以及保留哪些值作为兄弟
[图5-13](img/fig5-13.png)使用单个版本号来捕获操作之间的依赖关系,但是当多个副本并发接受写入时,这是不够的。相反,还需要在**每个副本**以及**每个键**使用版本号。每个副本在处理写入时增加自己的版本号,并且跟踪从其他副本中看到的版本号。这个信息指出了要覆盖哪些并发值,以及保留哪些并发值。
所有副本的版本号集合称为**版本向量version vector**【56】。这个想法的一些变体正在被使用但最有趣的可能是在Riak 2.0 【58,59】中使用的**分散版本矢dotted version vector**【57】。我们不会深入细节但是它的工作方式与我们在购物车示例中看到的非常相似。
所有副本的版本号集合称为**版本向量version vector**【56】。这个想法的一些变体正在被使用但最有趣的可能是在Riak 2.0 【58,59】中使用的**虚线版本向dotted version vector**【57】。我们不会深入细节但是它的工作方式与我们在购物车示例中看到的非常相似。
与[图5-13](img/fig5-13.png)中的版本号一样当读取值时版本向量会从数据库副本发送到客户端并且随后写入值时需要将其发送回数据库。Riak将版本向量编码为一个字符串它称为**因果上下文causal context**)。版本向量允许数据库区分覆盖写入和并发写入。
另外,就像在单个副本的例子中,应用程序可能需要合并兄弟。版本向量结构确保从一个副本读取并随后写回到另一个副本是安全的。这样做可能会创建兄弟,但只要兄弟姐妹合并正确,就不会丢失数据。
另外,就像在单个副本中的情况一样,应用程序可能需要合并并发值。版本向量结构能够确保从一个副本读取并随后写回到另一个副本是安全的。这样做虽然可能会在其他副本上面创建数据,但只要能正确合并就不会丢失数据。
> #### 版本向量和向量时钟
>