分布式相关

【转摘】条分缕析分布式:到底什么是一致性?

凡是做服务器开发的技术同学,估计都对分布式系统以及相关的理论感兴趣。而对于分布式理论,大家讨论的最多的恐怕就是「分布式一致性」问题了。然而,不管是学术界还是业界的发展历史上,对于「一致性」这个概念的理解,始终充满了混乱。如果你问一个技术同学,到底什么是分布式一致性,估计会得到五花八门的答案。这其中比较常见的说法可能是这样的:一致性就是多个服务器节点中的数据保持一致(至少百度百科上差不多就是这么说的

凡是做服务器开发的技术同学,估计都对分布式系统以及相关的理论感兴趣。而对于分布式理论,大家讨论的最多的恐怕就是「分布式一致性」问题了。然而,不管是学术界还是业界的发展历史上,对于「一致性」这个概念的理解,始终充满了混乱。

如果你问一个技术同学,到底什么是分布式一致性,估计会得到五花八门的答案。这其中比较常见的说法可能是这样的:一致性就是多个服务器节点中的数据保持一致(至少百度百科上差不多就是这么说的)。而如果再讨论得深入一点,可能就会谈到所谓的分布式一致性协议,比如Paxos之类的;还有CAP定理,也跟「一致性」有关。

但是,「一致性」这个词是非常有迷惑性的。如果用英文来表达的话,跟「一致性」有关的至少有两个词:consistency和consensus。它们经常都被翻译成「一致性」,这进一步加剧了这个概念被滥用的程度。为了接下来讨论方便,我们先简单地澄清一下:

  • 网上通常提到的诸如Paxos之类的分布式一致性协议,其实是consensus这个词。它如果被翻译成「共识」,可能会更好一些。为了表达清晰,本文后面在讨论consensus问题的时候,尽量使用「共识」这个词。

  • ACID或CAP里C,用的都是consistency这个词,但真实含义迥然不同。

  • 此外,还经常会听到人们关于「强一致性」的说法,而且这种说法通常都会牵涉到CAP定理或者「分布式事务」的概念。「强一致性」与CAP定理确实关系密切,但与「分布式事务」的关系却不知从何而来。

下面,我们就对这些概念进行详细的解析。

ACID中的一致性

ACID是数据库事务的四个特性,分别是原子性 (Atomicity)、一致性 (Consistency)、隔离性 (Isolation)和持久性 (Durability)。

我们现在关注的是其中的C,即一致性Consistency。它是什么意思呢?通俗地说,它指的是任何一个数据库事务的执行,都应该让整个数据库保持在「一致」的状态。那怎样的状态才算「一致」呢?举个例子,假设在银行账户之间进行转账。显然,「转账」这个操作应该确保在转账前后账户总额保持不变,这是任何一个转账操作必须要遵守的规定。现在假设要从账户A向账户B转账100元,于是我们启动了一个数据库事务。在这个事务中,可以先从账号A中减去100元,再往账户B中增加100元。这样的一个事务操作,满足了“转账前后账户总额保持不变”的规定,因此我们说:这个事务操作保持了数据库的「一致性」;同时,在这个事务执行前后,数据库都处于一种「一致」的状态。

从上一段的描述中,我们容易看出:

  • ACID中的「一致性」,是对于整个数据库的「一致」状态的维持。抽象来看,对数据库每进行一次事务操作,它的状态就发生一次变化。这相当于把数据库看成了状态机,只要数据库的起始状态是「一致」的,并且每次事务操作都能保持「一致性」,那么数据库就能始终保持在「一致」的状态上 (Consistency Preservation)。

  • 所谓状态是不是「一致」,其实是由业务层规定的。比如前面这个转账的例子,“转账前后账户总额保持不变”,这个规定只对于「转账」这个特定的业务场景有效。如果换一个业务场景,「一致」的概念就不是这样规定了。所以说,ACID中的「一致性」,其实是体现了业务逻辑上的合理性,并不是由数据库本身的技术特性所决定的。

我们再来看一下,为了让事务总是能保持ACID的一致性,我们需要在实现上考虑哪些因素呢?

至少两个方面需要考虑:一个是出错情况 (failure/error);一个是并发 (concurrency) 行为。

首先,对于任何系统来说,错误都是在所难免的。而错误又可以细分为两类。

第一类,事务本身的实现逻辑可能存在错误。比如,从账户A向账户B转账100元,在这个事务中,如果我们只从账号A中减去了100元,但忘记了往账户B中增加100元,那么这个事务就是错误的。显然,避免第一类错误,是保持一致性的前提,这需要应用层进行恰当的编码来保证。

第二类,则是意想不到的各种软硬件错误。比如,还是从账户A向账户B转账100元,事务本身的实现逻辑没有问题,它先执行了从账号A中减去了100元,但在执行往账户B中增加100元之前,却发生了意想不到的错误,比如进程突然crash了,或是磁盘满了,或是网络突然不通了,或是其它任何可能的硬件错误。这时候,事务只执行了前一半,势必会破坏数据库整体状态的一致性。那怎么办呢?这其实就需要ACID中的A(原子性)来保障了。简言之,原子性保障了事务的执行要么全部成功,要么全部失败,而不允许出现“只执行了一半”这种“部分成功”的情况。

其次,并发行为也可能会影响事务的一致性。在数据库系统中,并发行为体现在可能存在多个事务同时操作同一份数据的情况。还是拿前面转账的例子来说,假设有两个事务:事务1从账户A向账户B转账100元,事务2从账户A向账户C转账50元。如果两个事务先后顺序执行,自然没有问题。但如果两个事务同时执行了,那么可能会出现下面的执行序列(假设账号A的初始余额为x元):

  1. <事务1>:读取账户A的余额,读到了x元;

  2. <事务2>:读取账户A的余额,也读到了x元;

  3. <事务1>:向账户A中写入(x-100)元;

  4. <事务2>:向账户A中写入(x-50)元;

  5. ......

上面的执行过程,账户A中最后被写入的值是(x-50)元,显然是不对的(事务的一致性会被破坏)。如果两个转账的事务能正确执行完,那么账户A的余额应该是(x-150)元才对。

这个并发的问题怎么处理呢?这就需要ACID中的I(隔离性)来保障了。什么是隔离性呢?它对于并发执行的多个事务进行合理的排序,保障了不同事务的执行互不干扰。换言之,隔离性这种特性,能够让并发执行的多个事务就好像是按照「先后顺序」执行的一样。

经过上面的分析,现在关于ACID中的一致性,我们可以得到一些结论了:

  • ACID中的一致性,是个很偏应用层的概念。这跟ACID中的原子性、隔离性和持久性有很大的不同。原子性、隔离性和持久性,都是数据库本身所提供的技术特性;而一致性,则是由特定的业务场景规定的。怪不得《Desig

原创不易,完成人机校验,阅读全文

相关推荐