天天看点

ACID一致性与CAP一致性浅谈一、概述二、ACID一致性的分级三、CAP一致性的分级

一、概述

一致性在越来越多的场景中被提到,但是在数据库与分布式这两个领域中,一致性却是两个完全不同的理解,许多对这两个领域了解不是特别清楚的,便非常容易混淆。

数据库领域:

  • 即ACID中的C,主要是指事务开始前和结束后,数据库的完整性约束没有被破坏。比如A向B转账,不可能A扣了钱,B却没收到。

分布式领域:

  • 即CAP中的C,主要指多副本之间,数据的同步一致,比如存在A、B、C三个副本,A中写入数据 hello,写完马上读B和C,就一定要读出 hello,读出来我们就称之为符合一致性。

二、ACID一致性的分级

在数据库事务中,可能存在丢失修改、不能重复读、脏读等问题,它们都是由于并发事务在修改同一份数据的时候导致的问题,此类问题可以通过对同一个资源加锁的方式来解决,而最后一种情况是由于不同事务并发时,新增数据导致的问题,对于新增的记录是无法加锁的,此种情况只能通过事务的串行化来解决。而串行化与并发是矛盾的,所以要在性能和事务的一致性强度上取得一个平衡,就涉及到不同的隔离等级。

2.1 不一致情况

在多个事务并行运行时,可能会出现以下问题,影响数据库的一致性:

  • 修改丢失:丢失修改是事务A和B先后更改数据数据x(假设初始是x0),但是在A未正式更改前,B已经读取了原先的数据x0,最后A更改后为x1,B更改的并不是A更新后的x1,而是更改的x0,更改后假设为x2,这时x2将x1覆盖了,相当于事务A针对x的更改丢失了。
  • 脏读: 事务T1读取了T2更改的x,但是T2在实际存储数据时可能出错回滚了,这时T1读取的实际是无效的数据,这种情况下就是脏读
  • 不可重复读:是说在T1读取x时,由于中间T2更改了x,所以T1前后两次读取的x值不相同,这就是所谓的不可重复读
  • 幻读:在T1读取符合某个条件的所有记录时,T2增加了一条符合该条件的记录,这就导致T1执行过程中前后读取的记录可能不一致,即T2之后读取时会多出一条记录。

2.2 隔离级别

事务的隔离级别从低到高有:读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)

  1. Read Uncommitted:事务读数据时不会加锁,写数据时会有行级共享锁。假设事务1先于事务2,当事务1更新数据的时候,事务2可以读取事务1未提交的数据,但是不能更新事务1正在更新的数据。而如果事务1只是读数据,那么事务2既可以读数据,也可以更新数据。这种情况下无法规避脏读,不可重复读的问题。
  2. Read Committed:即在一个事务修改数据过程中,如果事务还没提交,其他事务不能读该数据,或者说只能读取committed的数据。事务读数据的瞬间会加行级共享锁,一旦读完该行,立即释放该行级共享锁;而写数据的瞬间会加行级排它锁,直到事务结束。这种情况下就避免了脏读,但是却不能避免不可重复读的问题
  3. Repeatable Read:当然就再升一级,为的就是避免不可重复读的问题,所以名字叫repeatable read。怎么实现的呢,我们知道read committed是,事务读操作只在读的一瞬间加锁,读完这行就释放锁了,而repeatable read级别是读的一瞬间加锁,但是一直到事务结束才释放锁。但是repeatable read不能解决幻读的问题,因为幻读是增加记录,并不是更改原先的记录。
  4. Serialization:到达这一级别的隔离,可以彻底解决一致性的所有问题。一般来说是通过加表锁来解决串行化的问题。

三、CAP一致性的分级

在分布式系统中,一致性指在某写操作后,任何读操作,都应该获取到该写操作写入的那个最新的值。相当于要求分布式系统中的各节点时时刻刻保持数据的一致性。依据数据在节点中同步,读取可能出现的错误情况,可以将一致性分为以下几个等级:

  • 强一致性:指系统中的某个数据被成功更新后,后续在任何节点、对该数据进行任何读取操作,都将得到更新后的值
  • 弱一致性:弱一致性是相对于强一致性而言,它不保证总能得到最新的值;
  • 最终一致性:是弱一致性的特殊形式,即保证在没有新的更新的条件下,经过一段“不一致时间窗口”,最终所有的访问都是最后更新的值。最常见的是DNS服务,更新域名指向的机器后,多级缓存要等到expiration time的时候才会更新,但是随着时间的推移,最终数据会趋于一致。

最终一致性根据更新数据后各进程访问到数据的时间和方式的不同,又可以区分为:

  • 因果一致性。如果进程A通知进程B它已更新了一个数据项,那么进程B的后续访问将返回更新后的值,且一次写入将保证取代前一次写入。与进程A无因果关系的进程C的访问遵守一般的最终一致性规则。
  • “读己之所写(read-your-writes)”一致性。当进程A自己更新一个数据项之后,它总是访问到更新过的值,绝不会看到旧值。这是因果一致性模型的一个特例。
  • 会话(Session)一致性。这是上一个模型的实用版本,它把访问存储系统的进程放到会话的上下文中。只要会话还存在,系统就保证“读己之所写”一致性。如果由于某些失败情形令会话终止,就要建立新的会话,而且系统的保证不会延续到新的会话。
  • 单调(Monotonic)读一致性。如果进程已经看到过数据对象的某个值,那么任何后续访问都不会返回在那个值之前的值。
  • 单调写一致性。系统保证来自同一个进程的写操作顺序执行。要是系统不能保证这种程度的一致性,就非常难以编程了。

上述最终一致性的不同方式可以进行组合,例如单调读一致性和读己之所写一致性就可以组合实现。

一致性与副本数:

从服务端角度,如何尽快将更新后的数据分布到整个系统,降低达到最终一致性的时间窗口,是提高系统的可用度和用户体验非常重要的方面。对于分布式数据系统:

  • N — 数据复制的份数
  • W — 更新数据是需要保证写完成的节点数
  • R — 读取数据的时候需要读取的节点数

如果W+R>N,写的节点和读的节点重叠,则是强一致性。例如对于典型的一主一备同步复制的关系型数据库,N=2,W=2,R=1,则不管读的是主库还是备库的数据,都是一致的。

如果W+R<=N,则是弱一致性。例如对于一主一备异步复制的关系型数据库,N=2,W=1,R=1,则如果读的是备库,就可能无法读取主库已经更新过的数据,所以是弱一致性。

对于分布式系统,为了保证高可用性,一般设置N>=3。不同的N,W,R组合,是在可用性和一致性之间取一个平衡,以适应不同的应用场景。

  • 如果N=W,R=1,任何一个写节点失效,都会导致写失败,因此可用性会降低,但是由于数据分布的N个节点是同步写入的,因此可以保证强一致性。
  • 如果N=R,W=1,只需要一个节点写入成功即可,写性能和可用性都比较高。但是读取其他节点的进程可能不能获取更新后的数据,因此是弱一致性。这种情况下,如果W<(N+1)/2,并且写入的节点不重叠的话,则会存在写冲突  
ACID一致性与CAP一致性浅谈一、概述二、ACID一致性的分级三、CAP一致性的分级