美文网首页
深入理解CAP(1) - 严格定义

深入理解CAP(1) - 严格定义

作者: 西部小笼包 | 来源:发表于2020-02-21 22:58 被阅读0次

    我大概搜索了一下互联网上的关于cap的中文资料,大多停留在非常浅的层面在讨论cap, 而有一些英文资料则非常深入的讲述了他们。http://blog.thislongrun.com/2015/03/the-cap-theorem-series.html
    。(DDIA的作者也吐槽cap被用懒了,其实他们都有很严格的定义和证明)

    大众科普的理解

    CAP理论指的是一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项。
    还会有些人说, 其实P是必须的,只能在C和A里2个选1个。
    其实上述的说法不难理解。
    首先一致性大概意思是,我去任何一个节点拿到的数据都是最新的,和去单机拿并无差别。所以在分布式环境下这就要求所有副本必须得同步。比如3个节点都含有账户a的余额,则你向其中一个节点更新账户余额,它必须要成功更新掉所有3个节点才能告诉你成功。不然数据就会不一致。
    但是一旦2个节点和另一个节点发生了网络分区。彼此之间信息无法传达,就会造成客户永远不能更新成功。因为必须要3个节点都更新了才能返回给客户端。
    这个时候如果不管那个没更新的节点,更新成功一部分就返回给客户端成功了。那么也是可以的,这里保证了在网络分片的场景下的可用性,但是牺牲了一致性。因为分片一旦消除,就会有同一个账户下的几个副本余额不一样。
    而分布式场景下,p是一定会发生的,这就是为什么有人会说其实是a,c二选一。
    关于这一层的理解只是简要概括。如果完全没了解过CAP的,可以参考这篇文章https://zhuanlan.zhihu.com/p/105907157

    下面开始进入正文,严谨的CAP探讨。

    严格的定义

    下面的定义是出自2002年Seth Gilbert 和 Nancy Lynch 证明cap理论为真的时候的定义。
    论文地址

    Consistent is: “Atomic, linearizable, consistency [...]. There must exist a total order on all operations such that each operation looks as if it were completed at a single instant. This is equivalent to requiring requests of the distributed shared memory to act as if they were executing on a single node, responding to operations one at a time.”

    Available is: “For a distributed system to be continuously available, every request received by a non-failing node in the system must result in a response.”

    可以看到可用性的定义是任何一个去没有失败的节点的请求系统一定要给一个应答。这个定义和大众的理解会不同,我们稍后会展开。

    Partition is: “The network will be allowed to lose arbitrarily many messages sent from one node to another. When a network is partitioned, all messages sent from nodes in one component of the partition to nodes in another component are lost.”

    基于上述定义,我们可以用很tricky 的方式去实现2个最简单的符合定义的cp 和 ap系统。
    因为在cp下,a可以被牺牲,所以下述实现满足。

    Implementation 1: CP made easy
    void put(key, value){
      throw “not available”
    }
    
    value get(key){
      throw “not available”
    }
    

    如果c可以被牺牲的,ap其实也非常简单,我们永远返回初始值即可。比如下面这个实现

    Implementation 2: AP is easy too
    void put(key, value){
      // do nothing
    }
    
    value get(key){
      throw “no value for this key” 
    }
    

    所以一般的ap系统,不会去完全牺牲一致性。他们往往会称自己是最终一致性的。也就是系统不发生更新后,最终所有的节点会就所有数据达成一致的系统。这个定义也是非常模糊的。我们可以简单看看下面几个实现,你们觉得是最终一致的吗?

    Implementation 1: Trying the simplest option
    void put(key, value){
      // do nothing
    }
    
    value get(key){
      // Hey, it’s easy to implement! We’re going
      //  to pretend that we have not yet received
      //  the write, it is enough!
      throw “no value for this key” 
    }
    

    不是最终一致的,因为我们要求没有put之后,经过有限时间可以拿到最新的值

    Implementation 2: Trying to be fast for reads
    void put(key, value){
       doRealPut(key, value) // does the ‘real’ job
    }
    
    value get(key){
      if (random(2) == 1) // 50% of the time
        throw “no value for this key”
      else
        return doRealGet(key)
        // As, 50% of the time, I pretend 
        //  that I have not yet received
        //  the first insertion, I
        //  divide the latency by two!
    }
    

    上面这种实现,同样找不到一个这样的时间节点,所以也不是最终一致。

    Implementation 3: Playing on words
    void put(key, value){
       doRealPut(key, value)
    }
    
    value get(key){
      if (currentDate.year < 2020)
        throw “no value for this key”
      else
        return doRealGet(key)
        // I will win all read benchmarks until 2020!
    }
    

    上面可以说是最终一致的,因为到了2020年,就能拿到最新值了。

    Implementation 4: Trying EC for hard real-time systems
    void put(key, value){
      doRealPut(key, value)
    }
    
    value get(key){
      try (timeLimit = 10 ms) { // run in 10ms or throw
        return doRealGet(key)
      } catch (TimeOutException) {
        // Max response time is 10ms!
        throw “no value for this key”
      }
    }
    

    上面这种实现非常实用,但是一个ec的系统是不允许这样处理的。因为eventually all accesses will return the last updated的承诺被破坏。

    2个除外

    1. 节点失败不应该被考虑进cap的理论里。
    2. 网络丢包不应该被考虑进cap的理论里。
      俗话说:“节点出现故障,网络数据包丢失,分区出现,因此您需要使用CAP进行权衡。” 实际上,CAP的范围并不广泛。 让我们看看为什么进程崩溃或节点故障不是CAP中的分区。
      一般人会把网络分区和节点失效放在一起考虑的原因是因为我们无法区分这两种情况。 当一个节点不响应请求,也不发起请求,基本就认为它挂了(有可能也是处在另一个网络分区)

    首先在严格的定义中,available 里说明了请求发送到每一个没有挂掉的节点必须得到响应。
    其次,论文中的证明过程也没有考虑节点失败的。

    Let’s figure a system with two nodes: N1, N2.
    step 0: a shared register receives an initial value: SR=0
    step 1: N1 is partitioned from N2. By the partition definition, there can be no communication between the two nodes.
    step 2: N1 writes a new value for SR: SR=1
    step 3: N2 reads SR. It cannot get the latest value written by N1. Hence the system is either non consistent (N2 reads the old value) or not available (the writing by N1 fails or the reading by N2 fails).

    让我们回到这句话:“不可能将发生故障的节点与另一个分区上的节点区分开。” 这是真的。

    如果希望可用,将继续运行两个分区上的所有节点。 一个分区不知道其他节点是否已死或已分区这一事实并不是一个问题:它们可以独立工作。

    如果要保持强一致,则需要选择一个分区作为可用分区。 第二个分区上的节点将杀死自己,或者至少不会响应请求。 在这种情况下,分区将等同于故障,但是这种等价性是需要在节点上维护一段特殊的针对这种情况的代码来实现的。

    节点故障不是分区,并且CAP定理没有说明故障时该如何。 因此,在推理节点故障或服务器崩溃时,不应使用它。

    那么网络丢包为什么也不考虑呢?
    网络确实会丢失数据包。 但是,TCP允许将数据包丢失隐藏到应用程序中,这就是CAP证明实际上所做的。 无论如何,使用CAP定理时,丢包是无关紧要的:CAP是关于分区的,而不是节点故障或丢包。 尽管基于错误的逻辑有时可以做出正确的决定,但是通常最好只在适用于该问题的情况下使用定理:死节点和数据包丢失是不好的,但是这2个问题是不会迫使你在可用性和一致性之间做出选择。

    相关文章

      网友评论

          本文标题:深入理解CAP(1) - 严格定义

          本文链接:https://www.haomeiwen.com/subject/dltxqhtx.html