ch5: 修改明显的错误

This commit is contained in:
yjhmelody 2018-08-27 21:50:36 +08:00
parent c7bdd81420
commit 889e49e021

16
ch5.md
View File

@ -523,7 +523,7 @@
在Dynamo风格的数据库中参数nw和r通常是可配置的。一个常见的选择是使n为奇数通常为3或5并设置 $w = r =n + 1/ 2$(向上取整)。但是可以根据需要更改数字。例如,设置$w = n$和$r = 1$的写入很少且读取次数较多的工作负载可能会受益。这使得读取速度更快,但具有只有一个失败节点导致所有数据库写入失败的缺点。
> 集群中可能有多于n的节点。集群的机器数可能多于副本但是任何给定的值只能存储在n个节点上。 这允许对数据集进行分区,从而支持可以放在一个节点上的数据集更大的数据集。 将在第6章回到分区。
> 集群中可能有多于n的节点。集群的机器数可能多于副本但是任何给定的值只能存储在n个节点上。 这允许对数据集进行分区,从而支持可以放在一个节点上的数据集更大的数据集。 将在第6章回到分区。
>
仲裁条件$w + r> n$允许系统容忍不可用的节点,如下所示:
@ -590,7 +590,7 @@
一旦网络中断得到解决,代表另一个节点临时接受的一个节点的任何写入都被发送到适当的“本地”节点。这就是所谓的**带提示的接力hinted handoff**。 (一旦你再次找到你的房子的钥匙,你的邻居礼貌地要求你离开沙发回家。)
松散法定人数提高写入可用性特别有用只要有任何w节点可用数据库就可以接受写入。然而这意味着即使当$w + r> n$时也不能确定读取某个键的最新值因为最新的值可能已经临时写入了n之外的某些节点【47】。
松散法定人数对写入可用性的提高特别有用只要有任何w节点可用数据库就可以接受写入。然而这意味着即使当$w + r> n$时也不能确定读取某个键的最新值因为最新的值可能已经临时写入了n之外的某些节点【47】。
因此在传统意义上一个松散的法定人数实际上不是一个法定人数。这只是一个保证即数据存储在w节点的地方。不能保证r节点的读取直到提示已经完成。
@ -632,11 +632,11 @@
即使写入没有自然的排序,我们也可以强制任意排序。例如,可以为每个写入附加一个时间戳,挑选最**“最近”**的最大时间戳,并丢弃具有较早时间戳的任何写入。这种冲突解决算法被称为**最后写入胜利LWW, last write wins**是Cassandra 【53】唯一支持的冲突解决方法也是Riak 【35】中的一个可选特征。
LWW实现了最终收敛的目标但以**持久性**为代价如果同一个Key有多个并发写入即使它们都被报告为客户端成功因为它们被写入 w 个副本),其中一个写道会生存下来,其他的将被无声丢弃。此外LWW甚至可能会删除不是并发的写入我们将在的“[有序事件的时间戳](ch8.md#有序事件的时间戳)”中讨论。
LWW实现了最终收敛的目标但以**持久性**为代价如果同一个Key有多个并发写入即使它们都被报告为客户端成功因为它们被写入 w 个副本),但只有一个写入将存活,而其他写入将被静默丢弃。此外LWW甚至可能会删除不是并发的写入我们将在的“[有序事件的时间戳](ch8.md#有序事件的时间戳)”中讨论。
有一些情况如缓存其中丢失的写入可能是可以接受的。如果丢失数据不可接受LWW是解决冲突的一个很烂的选择。
与LWW一起使用数据库的唯一安全方法是确保一个键只写入一次然后视为不可变从而避免对同一个密钥进行并发更新。例如推荐使用Cassandra的方法是使用UUID作为键从而为每个写操作提供一个唯一的键【53】。
与LWW一起使用数据库的唯一安全方法是确保一个键只写入一次然后视为不可变从而避免对同一个密钥进行并发更新。例如Cassandra推荐使用的方法是使用UUID作为键从而为每个写操作提供一个唯一的键【53】。
#### “此前发生”的关系和并发
@ -670,7 +670,7 @@
1. 客户端 1 将牛奶加入购物车。这是该键的第一次写入服务器成功存储了它并为其分配版本号1最后将值与版本号一起回送给客户端。
2. 客户端 2 将鸡蛋加入购物车,不知道客户端 1 同时添加了牛奶(客户端 2 认为它的鸡蛋是购物车中的唯一物品)。服务器为此写入分配版本号 2并将鸡蛋和牛奶存储为两个单独的值。然后它将这两个值**都**反回给客户端 2 ,并附上版本号 2 。
3. 客户端 1 不知道客户端 2 的写入,想要将面粉加入购物车,因此认为当前的购物车内容应该是 [牛奶,面粉]。它将此值与服务器先前向客户端 1 提供的版本号 1 一起发送到服务器。服务器可以从版本号中知道[牛奶,面粉]的写入取代了[牛奶]的先前值,但与[鸡蛋]的值是**并发**的。因此,服务器将版本 3 分配给[牛奶,面粉]覆盖版本1值[牛奶],但保留版本 2 的值[蛋],并将所有的值返回给客户端 1 。
4. 同时,客户端 2 想要加入火腿,不知道客端户 1 刚刚加了面粉。客户端 2 在最后一个响应中从服务器收到了两个值[牛奶]和[蛋],所以客户端 2 现在合并这些值,并添加火腿形成一个新的值,[鸡蛋,牛奶,火腿]。它将这个值发送到服务器,带着之前的版本号 2 。服务器检测到新值会覆盖版本 2 [eggs],但新值也会与版本 3 [牛奶,面粉]**并发**所以剩下的两个值是v3 [milkflour]和v4[鸡蛋,牛奶,火腿]。
4. 同时,客户端 2 想要加入火腿,不知道客端户 1 刚刚加了面粉。客户端 2 在最后一个响应中从服务器收到了两个值[牛奶]和[蛋],所以客户端 2 现在合并这些值,并添加火腿形成一个新的值,[鸡蛋,牛奶,火腿]。它将这个值发送到服务器,带着之前的版本号 2 。服务器检测到新值会覆盖版本 2 [鸡蛋],但新值也会与版本 3 [牛奶,面粉]**并发**所以剩下的两个是v3 [牛奶,面粉]和v4[鸡蛋,牛奶,火腿]
5. 最后,客户端 1 想要加培根。它以前在v3中从服务器接收[牛奶,面粉]和[鸡蛋],所以它合并这些,添加培根,并将最终值[牛奶,面粉,鸡蛋,培根]连同版本号v3发往服务器。这会覆盖v3[牛奶,面粉](请注意[鸡蛋]已经在最后一步被覆盖但与v4[鸡蛋,牛奶,火腿]并发,所以服务器保留这两个并发值。
![](img/fig5-13.png)
@ -706,7 +706,7 @@
#### 版本向量
[图5-13](img/fig5-13.png)中的示例只使用一个副本。如果有没有主库,有多个副本,算法如何改变
[图5-13](img/fig5-13.png)中的示例只使用一个副本。当有多个副本但没有领导者时,算法如何修改
[图5-13](img/fig5-13.png)使用单个版本号来捕获操作之间的依赖关系,但是当多个副本并发接受写入时,这是不够的。相反,除了对每个键使用版本号之外,还需要在**每个副本**中版本号。每个副本在处理写入时增加自己的版本号,并且跟踪从其他副本中看到的版本号。这个信息指出了要覆盖哪些值,以及保留哪些值作为兄弟。
@ -758,7 +758,7 @@
***无主复制***
客户端发送每个写入到几个节点,并从多个节点并行读取,以检测和纠正具有陈旧数据的节点。
每种方法都有优点和缺点。单主复制是非常流行的,因为它很容易理解,不需要担心冲突解决。在出现故障节点,网络中断和延迟峰值的情况下,多领导者和无领导者复制可以更加稳健,但代价很难推理,只能提供非常弱的一致性保证
每种方法都有优点和缺点。单主复制是非常流行的,因为它很容易理解,不需要担心冲突解决。在出现故障节点,网络中断和延迟峰值的情况下,多领导者和无领导者复制可以更加稳健,但以更难以推理并仅提供非常弱的一致性保证为代价
复制可以是同步的,也可以是异步的,在发生故障时对系统行为有深远的影响。尽管在系统运行平稳时异步复制速度很快,但是在复制滞后增加和服务器故障时要弄清楚会发生什么,这一点很重要。如果一个领导者失败了,并且你推动一个异步更新的追随者成为新的领导者,那么最近承诺的数据可能会丢失。
@ -770,7 +770,7 @@
***单调读***
当用户在某个时间点看到数据后,他们不应该在较早的时间点看到数据。
用户在一个时间点看到数据后,他们不应该在某个早期时间点看到数据。
***一致前缀读***