mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-11-17 14:43:43 +08:00
fix img
This commit is contained in:
@@ -223,7 +223,7 @@ function hide_canvas() {
|
||||
<p>CAP 意味着即使所有节点都在运行中,我们也可能会遇到一致性问题,这是因为它们之间存在连接性问题。CAP 理论常常用三角形表示,就好像我们可以任意匹配三个参数一样。然而,尽管我们可以调整可用性和一致性,但分区容忍性是我们无法实际放弃的。</p>
|
||||
<p>如果我们选择了 CA 而放弃了 P,那么当发生分区现象时,为了保证 C,系统需要禁止写入。也就是,当有写入请求时,系统不可用。这与 A 冲突了,因为 A 要求系统是可用的。因此,分布式系统理论上不可能选择 CA 架构,只能选择 CP 或者 AP 架构。</p>
|
||||
<p>如下图所示,其实 CA 类系统是不存在的,这里你需要特别注意。</p>
|
||||
<p><img src="assets/Cip5yGAJWLmAJW7kAABPImLZRig108.png" alt="Drawing 0.png" /></p>
|
||||
<p><img src="assets/Cip5yGAJWLmAJW7kAABPImLZRig108.png" alt="png" /></p>
|
||||
<p>图 1 CAP 理论</p>
|
||||
<p>CAP 中的可用性也不同于上述的高可用性,CAP 定义对请求的延迟没有任何限制。此外,与 CAP 相反,数据库的高可用性并不需要每个在线节点都可以提供服务。</p>
|
||||
<p>CAP 里面的 C 代表线性一致,除了它以外,还有其他的一致模式,我们现在来具体介绍一下。</p>
|
||||
@@ -232,7 +232,7 @@ function hide_canvas() {
|
||||
<p>从用户的角度看,分布式数据库就像具有共享存储的单机数据库一样,节点间的通信和消息传递被隐藏到了数据库内部,这会使用户产生“分布式数据库是一种共享内存”的错觉。一个支持读取和写入操作的单个存储单元通常称为寄存器,我们可以把代表分布式数据库的共享存储看作是一组这样的寄存器。</p>
|
||||
<p>每个读写寄存器的操作被抽象为“调用”和“完成”两个动作。如果“调用”发生后,但在“完成”之前该操作崩溃了,我们将操作定义为失败。如果一个操作的调用和完成事件都在另一个操作被调用之前发生,我们说这个操作在另一个操作之前,并且这两个操作是顺序的;否则,我们说它们是并发的。</p>
|
||||
<p>如下图所示,a)是顺序操作,b)和 c)是并发操作。</p>
|
||||
<p><img src="assets/Ciqc1GAJWMaAahgyAAA9-0_mXvY966.png" alt="Drawing 1.png" /></p>
|
||||
<p><img src="assets/Ciqc1GAJWMaAahgyAAA9-0_mXvY966.png" alt="png" /></p>
|
||||
<p>图 2 顺序操作&并发操作</p>
|
||||
<p>多个读取或写入操作可以同时访问一个寄存器。对寄存器的读写操作不是瞬间完成的,需要一些时间,即调用和完成两个动作之间的时间。由不同进程执行的并发读/写操作不是串行的,根据寄存器在操作重叠时的行为,它们的顺序可能不同,并且可能产生不同的结果。</p>
|
||||
<p>当我们讨论数据库一致性时,可以从两个维度来区别。</p>
|
||||
@@ -265,7 +265,7 @@ function hide_canvas() {
|
||||
<li>第三次读只能返回 2,因为第二次写是在第一次写之后进行的。</li>
|
||||
</ol>
|
||||
<p>下图正是现象一致性的直观展示。</p>
|
||||
<p><img src="assets/Ciqc1GAJWNaABNY5AACytLnfuEE642.png" alt="Drawing 2.png" /></p>
|
||||
<p><img src="assets/Ciqc1GAJWNaABNY5AACytLnfuEE642.png" alt="png" /></p>
|
||||
<p>图 3 线性一致性</p>
|
||||
<p>线性一致性的代价是很高昂的,甚至 CPU 都不会使用线性一致性。有并发编程经验的朋友一定知道 CAS 操作,该操作可以实现操作的线性化,是高性能并发编程的关键,它就是通过编程手段来模拟线性一致。</p>
|
||||
<p>一个比较常见的误区是,使用一致性算法可以实现线性一致,如 Paxos 和 Raft 等。但实际是不行的,以 Raft 为例,算法只是保证了复制 Log 的线性一致,而没有描述 Log 是如何写入最终的状态机的,这就暗含状态机本身不是线性一致的。</p>
|
||||
@@ -273,10 +273,10 @@ function hide_canvas() {
|
||||
<h4>顺序一致性</h4>
|
||||
<p>由于线性一致的代价高昂,因此人们想到,既然全局时钟导致严格一致性很难实现,那么顺序一致性就是放弃了全局时钟的约束,改为分布式逻辑时钟实现。顺序一致性是指所有的进程以相同的顺序看到所有的修改。读操作未必能及时得到此前其他进程对同一数据的写更新,但是每个进程读到的该数据的不同值的顺序是一致的。</p>
|
||||
<p>下图展示了 P1、P2 写入两个值后,P3 和 P4 是如何读取的。以真实的时间衡量,1 应该是在 2 之前被写入,但是在顺序一致性下,1 是可以被排在 2 之后的。同时,尽管 P3 已经读取值 1,P4 仍然可以读取 2。但是需要注意的是这两种组合:1->2 和 2 ->1,P3 和 P4 从它们中选择一个,并保持一致。下图正是展示了它们读取顺序的一种可能:2->1。</p>
|
||||
<p><img src="assets/CgqCHmAJWOCABAs2AABs-o-Dn-I630.png" alt="Drawing 3.png" /></p>
|
||||
<p><img src="assets/CgqCHmAJWOCABAs2AABs-o-Dn-I630.png" alt="png" /></p>
|
||||
<p>图 4 顺序一致性</p>
|
||||
<p>我们使用下图来进一步区分线性一致和顺序一致。</p>
|
||||
<p><img src="assets/Ciqc1GAJWOaAT1zmAAB5GZRY2aI676.png" alt="Drawing 4.png" /></p>
|
||||
<p><img src="assets/Ciqc1GAJWOaAT1zmAAB5GZRY2aI676.png" alt="png" /></p>
|
||||
<p>图 5 区分线性一致和顺序一致</p>
|
||||
<p>其中,图 a 满足了顺序一致性,但是不满足线性一致性。原因在于,从全局时钟的观点来看,P2 进程对变量 x 的读操作在 P1 进程对变量 x 的写操作之后,然而读出来的却是旧的数据。但是这个图却是满足顺序一致性,因为两个进程 P1 和 P2 的一致性并没有冲突。</p>
|
||||
<p>图 b 满足线性一致性,因为每个读操作都读到了该变量的最新写的结果,同时两个进程看到的操作顺序与全局时钟的顺序一样。</p>
|
||||
@@ -292,10 +292,10 @@ function hide_canvas() {
|
||||
<li>闭包传递:和时钟向量里面定义的一样,如果 a->b、b->c,那么肯定也有 a->c。</li>
|
||||
</ol>
|
||||
<p>那么,为什么需要因果关系,以及没有因果关系的写法如何传播?下图中,进程 P1 和 P2 进行的写操作没有因果关系,也就是最终一致性。这些操作的结果可能会在不同时间,以乱序方式传播到读取端。进程 P3 在看到 2 之前将看到值 1,而 P4 将先看到 2,然后看到 1。</p>
|
||||
<p><img src="assets/Ciqc1GAJWPCATmnsAACWjAazgFM942.png" alt="Drawing 5.png" /></p>
|
||||
<p><img src="assets/Ciqc1GAJWPCATmnsAACWjAazgFM942.png" alt="png" /></p>
|
||||
<p>图 6 因果一致性</p>
|
||||
<p>而下图显示进程 P1 和 P2 进行因果相关的写操作并按其逻辑顺序传播到 P3 和 P4。因果写入除了写入数据外,还需要附加一个逻辑时钟,用这个时钟保证两个写入是有因果关系的。这可以防止我们遇到上面那张图所示的情况。你可以在两个图中比较一下 P3 和 P4 的历史记录。</p>
|
||||
<p><img src="assets/CgqCHmAJWPiAexxkAACijWQR6zY931.png" alt="Drawing 6.png" /></p>
|
||||
<p><img src="assets/CgqCHmAJWPiAexxkAACijWQR6zY931.png" alt="png" /></p>
|
||||
<p>图 7 逻辑时钟</p>
|
||||
<p>而实现这个逻辑时钟的一种主要方式就是向量时钟。向量时钟算法利用了向量这种数据结构,将全局各个进程的逻辑时间戳广播给所有进程,每个进程发送事件时都会将当前进程已知的所有进程时间写入到一个向量中,而后进行传播。</p>
|
||||
<p>因果一致性典型案例就是 COPS 系统,它是基于 causal+一致性模型的 KV 数据库。它定义了 dependencies,操作了实现因果一致性。这对业务实现分布式数据因果关系很有帮助。另外在亚马逊 Dynamo 基于向量时钟,也实现了因果一致性。</p>
|
||||
@@ -308,7 +308,7 @@ function hide_canvas() {
|
||||
<p>那么它们之间的联系如何呢?其实就是事务的隔离性与一致模型有关联。</p>
|
||||
<p>如果把上面线性一致的例子看作多个并行事务,你会发现它们是没有隔离性的。因为在开始和完成之间任意一点都会读取到这份数据,原因是一致性模型关心的是单一操作,而事务是由一组操作组成的。</p>
|
||||
<p>现在我们看另外一个例子,这是展示事务缺乏一致性后所导致的问题。</p>
|
||||
<p><img src="assets/CgqCHmAJWQGAARkoAAB7ZMQP49s438.png" alt="Drawing 7.png" /></p>
|
||||
<p><img src="assets/CgqCHmAJWQGAARkoAAB7ZMQP49s438.png" alt="png" /></p>
|
||||
<p>图 8 事务与一致性</p>
|
||||
<p>其中三个事务满足隔离性。可以看到 T2 读取到了 T1 入的值。但是这个系统缺乏一致性保障,造成 T3 可以读取到早于 T2 读取值之前的值,这就会造成应用的潜在 Bug。</p>
|
||||
<p>那现在给出结论:事务隔离是描述并行事务之间的行为,而一致性是描述非并行事务之间的行为。其实广义的事务隔离应该是经典隔离理论与一致性模型的一种混合。</p>
|
||||
|
||||
Reference in New Issue
Block a user