1 CAP 理论
Consistency(一致性), 数据一致更新,所有数据变动都是同步的
Availability(可用性), 好的响应性能
Partition tolerance(分区容错性) 可靠性
定理:任何分布式系统只可同时满足二点,没法三者兼顾。
忠告:架构师不要将精力浪费在如何设计能满足三者的完美分布式系统,而是应该进行取舍。
1.1 Consistency (一致性)
一致性又称为原子性或者事务性。表示一个事务的操作是不可分割的,要不然这个事务完成,要不然这个事务不完成,不会出现这个事务完成了一半这样的情况。这种事务的原子性使得数据具有一致性。
我们通常情况下在数据库中存在的脏数据就属于数据没有具有一致性的表现。而在分布式系统中,经常出现的一个数据不具有一致性的情况是读写数据时缺乏一致性。比如两个节点数据冗余,第一个节点有一个写操作,数据更新以后没有有效的使得第二个节点更新数据,在读取第二个节点的时候就会出现不一致的问题出现。
传统的ACID数据库是很少存在一致性问题的,因为数据的单点原因,数据的存取又具有良好的事务性,不会出现读写的不一致。
关系数据库的ACID模型拥有高一致性 + 可用性 很难进行分区:
Ø Atomicity原子性:一个事务中所有操作必须全部完成,要么全部不完成。
Ø Consistency一致性. 在事务开始或结束时,数据库应该在一致状态。
Ø Isolation隔离层. 事务将假定只有它自己在操作数据库,彼此不知晓。
Ø Durability. 一旦事务完成,就不能返回。
Ø 跨数据库事务:2PC (two-phase commit), 2PC is the anti-scalability pattern (Pat Helland) 是反可伸缩模式的,JavaEE中的JTA事务可以支持2PC。因为2PC是反模式,尽量不要使用2PC,使用BASE来回避。
1.2 Availability 可用性
好的可用性主要是指系统能够很好的为用户服务,不出现用户操作失败或者访问超时等用户体验不好的情况。可用性通常情况下可用性和分布式数据冗余,负载均衡等有着很大的关联。
1.3 Partition Tolerance分区容错性
分区容错性和扩展性紧密相关。在分布式应用中,可能因为一些分布式的原因导致系统无法正常运转。好的分区容错性要求能够使应用虽然是一个分布式系统,而看上去却好像是在一个可以运转正常的整体。比如现在的分布式系统中有某一个或者几个机器宕掉了,其他剩下的机器还能够正常运转满足系统需求,这样就具有好的分区容错性。
1.4 CAP理论的意义(NOSQL理论基础)
随着互联网应用的飞速发展,数据量与日俱增,传统的ACID数据库已经不能满足如此大的海量数据存储了。这个时候需要设计出好的分布式数据存储方式。而这些分布式数据存储方式受到CAP理论的约束,不可能达到高一致性,高可用性,高分区容错性的完美设计。所以我们在设计的时候要懂得取舍,重点关注对应用需求来说比较重要的,而放弃不重要的,在CAP这三者之间进行取舍,设计出贴合应用的存储方案。
目前众多的分布式数据系统通过降低一致性来换取可用性。下面是一个简单的例子:
两个节点数据冗余,第一个节点先有一个写操作,第二个节点后有一个读操作。下面的图中a是整个过程,要具有一致性的话需要等待a1进行write,然后同步到a2,然后a2再进行write,只有整个事务完成以后,a2才能够进行read。但是这样的话使得整个系统的可用性下降,a2一直阻塞在那里等待a1同步到a2。这个时候如果对一致性要求不高的话,a2可以不等待a1数据对于a2的写同步,直接读取,这样虽然此时的读写不具有一致性,但是在后面可以通过异步的方式使得a1和a2的数据最终一致,达到最终一致性。
CAP 理论说在一个系统中对某个数据不存在一个算法同时满足 Consistency, Availability, Partition-tolerance 。 注意,这里边最重要和最容易被人忽视的是限定词“对某个数据不存在一个算法”。这就是说在一个系统中,可以对某些数据做到 CP, 对另一些数据做到 AP,就算是对同一个数据,调用者可以指定不同的算法,某些算法可以做到 CP,某些算法可以做到 AP。
要做到 CP, 系统可以把这个数据只放在一个节点上,其他节点收到请求后向这个节点读或写数据,并返回结果。很显然,串行化是保证的。但是如果报文可以任意丢失的话,接受请求的节点就可能永远不返回结果。
要做到 CA, 一个现实的例子就是单点的数据库。你可能会疑惑“数据库也不是 100% 可用的呀?” 要回答这个疑惑,注意上面说的故障模型和 availability 的定义就可以了。
要做到 AP, 系统只要每次对写都返回成功,对读都返回固定的某个值就可以了。
CAP 理论更重要的一个结果是, 在 Partial Synchronous System (半同步系统) 中,一个弱化的 CAP 是能达到的:
* 对所有的数据访问,总返回一个结果
* 如果期间没有报文丢失,那么返回一个满足 consistency 要求的结果。
这里的半同步系统指每个节点存在一个时钟,这些时钟不需要同步,但是按照相同的速率流逝。更通俗的来说,就是一个能够实现超时机制的系统。
举个例子,系统可以把这个数据只放在一个节点上,其他节点收到请求后向这个节点读或写数据,并设置一个定时器,如果超时前得到结果,那么返回这个结果,否则返回失败。
更进一步的,也是最重要的,实现一个满足最终一致性 (Eventually Consistency) 和 AP 的系统是可行的。现实中的一个例子是 Cassandra系统。
1.5 .BASE理论
BASE理论是CAP理论结合实际的产物。BASE(Basically Available, Soft-state,Eventuallyconsistent)英文中有碱的意思,这个正好和ACID的酸的意义相对,很有意思。BASE恰好和ACID是相对的,BASE要求牺牲高一致性,获得可用性或可靠性。
BASE模型反ACID模型,完全不同ACID模型,牺牲高一致性,获得可用性或可靠性:
Ø Basically Available基本可用。支持分区失败(e.g. sharding碎片划分数据库)
Ø Soft state软状态 状态可以有一段时间不同步,异步。
Ø Eventually consistent最终一致,最终数据是一致的就可以了,而不是时时高一致。
BASE思想的主要实现有
1.按功能划分数据库
2.sharding碎片
BASE思想主要强调基本的可用性,如果你需要High 可用性,也就是纯粹的高性能,那么就要以一致性或容错性为牺牲,BASE思想的方案在性能上还是有潜力可挖的。
现在NoSQL运动丰富了拓展了BASE思想,可按照具体情况定制特别方案,比如忽视一致性,获得高可用性等等,NOSQL应该有下面两个流派:
1. Key-Value存储,如Amaze Dynamo等,可根据CAP三原则灵活选择不同倾向的数据库产品。
2. 领域模型 + 分布式缓存 + 存储 (Qi4j和NoSQL运动),可根据CAP三原则结合自己项目定制灵活的分布式方案,难度高。
这两者共同点:都是关系数据库SQL以外的可选方案,逻辑随着数据分布,任何模型都可以自己持久化,将数据处理和数据存储分离,将读和写分离,存储可以是异步或同步,取决于对一致性的要求程度。
不同点:NOSQL之类的Key-Value存储产品是和关系数据库头碰头的产品BOX,可以适合非Java如PHP RUBY等领域,是一种可以拿来就用的产品,而领域模型 + 分布式缓存 + 存储是一种复杂的架构解决方案,不是产品,但这种方式更灵活,更应该是架构师必须掌握的。
满足一致性,可用性的系统,通常在可扩展性上不太强大:
·Traditional RDBMSs like Postgres,MySQL, etc (relational)
·Vertica (column-oriented)
·Aster Data (relational)
·Greenplum (relational)
满足一致性,分区容忍必的系统,通常性能不是特别高:
·BigTable (column-oriented/tabular)
·Hypertable (column-oriented/tabular)
·HBase (column-oriented/tabular)
·MongoDB (document-oriented)
·Terrastore (document-oriented)
·Redis (key-value)
·Scalaris (key-value)
·MemcacheDB (key-value)
·Berkeley DB (key-value)
满足可用性,分区容忍性的系统,通常可能对一致性要求低一些:
·Dynamo (key-value)
·Voldemort (key-value)
·Tokyo Cabinet (key-value)
·KAI (key-value)
·Cassandra (column-oriented/tabular)
·CouchDB (document-oriented)
·SimpleDB (document-oriented)
·Riak (document-oriented)
1.6 CAP的反对声音
Guy Pardon写了一篇文章“A CAP Solution (Proving Brewer Wrong)”来反对CAP理论。他提出了一个同时满足CAP的解决方案来反对Brewer的三者只能取其二的说法。
他设计的系统如下:
(1)程序如果能够读取数据库的话读取数据库,如果不能的话可以使用缓存代替。
(2)所有的读取操作使用版本号或者其他可以使用乐观锁的机制。
(3)客户端的所有更新操作全部放在队列中顺序处理。更新操作中要包括该更新的读取操作时的版本信息。
(4)当分区数量足够少的时候,可以处理队列中的更新操作。比较简单的方式是建立一个跨越所有分布式副本的事务,对每个副本进行更新操作(其他方式比如quorum等等也可以)。如果该更新的读取操作时的版本信息不是当前数据库中数据的版本信息,则将失败返回给客户端,否则返回成功。
(5)数据库操作结果(确认或者取消)通过异步的方式发送到客户端,可以通过邮件,消息队列或者其他异步方式。
该系统符合CAP如下:
C(高一致性):读取的数据都是基于快照的,而且错误的更新操作不会执行。
A(高可用性):读取和更新都会返回数据。
P(高分区容错性):允许网络或者节点出错。
该设计是符合BASE理论的。