理论结合,分布式CAP实战
架构师技术联盟的业务多活架构和分布式CAP实战,分布式CAP的学习笔记
CAP理论
CAP原则是指任意一个分布式系统,同时最多只能满足其中的两项,而无法同时满足三项。
Consistency(一致性):每时每刻每个节点上的同一份数据是否都是一致的。这就要求任何更新都是原子的,要么全部成功,要么全部失败。想象一下使用分布式事务来保证所有系统的原子性是多么低效的一个操作。
Availability(可用性):任意时刻系统是否都可以提供读和写服务。
举个例子,当我们用事务将所有节点锁住来进行某种写操作时,如果某个节点发生不可用的情况,会让整个系统不可用。对于分片式的 NoSQL 中间件集群(Redis,Memcached)来说,一旦一个分片歇菜了,整个系统的数据也就不完整了,读取宕机分片的数据就会没响应,也就是不可用了。
需要说明一点,那些选择 CP 的分布式系统,并不是代表可用性就完全没有了,只是可用性没有保障了。为了增加可用性保障,这类中间件往往都提供了”分片集群+复制集”的方案。
Partition tolerance(分区容忍性):P 并不是像 CA 一样是一个独立的性质,它依托于 CA 来进行讨论。
参考文献中的解释:”除非整个网络瘫痪,否则任何时刻系统都能正常工作”,言下之意是小范围的网络瘫痪,节点宕机,都不会影响整个系统的 CA。
我感觉这个解释听着还是有点懵逼,所以个人更愿意解释为当节点之间网络不通时(出现网络分区),可用性和一致性仍然能得到保障。
从个人角度理解,分区容忍性又分为“可用性分区容忍性”和“一致性分区容忍性”。
出现分区时会不会影响可用性的关键在于需不需要所有节点互相沟通协作来完成一次事务,不需要的话是铁定不影响可用性的。
庆幸的是应该不太会有分布式系统会被设计成完成一次事务需要所有节点联动,一定要举个例子的话,全同步复制技术下的 MySQL 是一个典型案例。
出现分区时会不会影响一致性的关键则在于出现脑裂时有没有保证一致性的方案,这对主从同步型数据库(MySQL、SQL Server)是致命的。
一旦网络出现分区,产生脑裂,系统会出现一份数据两个值的状态,谁都不觉得自己是错的。
其实分布式系统很难满足 CAP 的前提条件是这个系统一定是有读有写的,如果只考虑读,那么 CAP 很容易都满足。
比如一个计算器服务,接受表达式请求,返回计算结果,搞成水平扩展的分布式,显然这样的系统没有一致性问题,网络分区也不怕,可用性也是很稳的,所以可以满足 CAP。
水平扩展应用+单数据库实例的 CAP 分析
分布式系统解决的最大的痛点,是单体单机系统的可用性问题。
一家互联网公司的发展之路上,第一次与分布式相遇应该都是在单体应用的水平扩展上。(ps:要想高可用,必须分布式。这句话有个前提:单体应用水平扩展场景下。单体的话,高可用不一定分布式)
同一个应用启动了多个实例,连接着相同的数据库(为了简化问题,先不考虑数据库是否单点)。
这样的系统天然具有的是 AP(可用性和分区容忍性):
一方面解决了单点导致的低可用性问题。
另一方面无论这些水平扩展的机器间网络是否出现分区,这些服务器都可以各自提供服务,因为他们之间不需要进行沟通。
然而,这样的系统是没有一致性可言的,想象一下每个实例都可以往数据库 insert和update(注意这里还没讨论到事务),那还不乱了套。
于是我们转向了让 DB 去做这个事,这时候”数据库事务”就被用上了。用大部分公司会选择的 MySQL 来举例,用了事务之后会发现数据库又变成了单点和瓶颈。
单点就像单机一样(本例子中不考虑从库模式),理论上就不叫分布式了,如果一定要分析其 CAP 的话,根据上面的步骤分析过程应该是这样的:
1) 分区容忍性P:先看有没有考虑分区容忍性,或者分区后是否会有影响。单台MySQL无法构成分区,要么整个系统挂了,要么就活着。
2) 可用性分区容忍性A:分区情况下,假设恰好是该节点挂了,系统也就不可用了,所以可用性分区容忍性不满足。
3) 一致性分区容忍性C:分区情况下,只要可用,单点单机的最大好处就是一致性可以得到保障。
因此这样的一个系统,个人认为只是满足了 CP。A 有但不出色,从这点可以看出,CAP 并不是非黑即白的。
讨论分布式是希望系统的可用性是多个系统多活的,一个挂了另外的也能顶上,显然单机单点的系统不具备这样的高可用特性。
水平扩展的服务应用+数据库这样的系统的 CAP 魔咒主要发生在数据库层。
因为大部分这样的服务应用都只是承担了计算的任务(像计算器那样),本身不需要互相协作,所有写请求带来的数据的一致性问题下沉到了数据库层去解决。
如果没有数据库层,而是应用自己来保障数据一致性,那么这样的应用之间就涉及到状态的同步和交互了,ZooKeeper 就是这么一个典型的例子。
水平扩展应用+主从数据库集群的 CAP 分析
下图所示的模式成为了当下大部分中小公司所使用的架构:
从上图我可以看到三个数据库实例中只有一个是主库,其他是从库。
一定程度上,这种架构极大的缓解了”读可用性”问题,而这样的架构一般会做读写分离来达到更高的”读可用性”,幸运的是大部分互联网场景中读都占了 80% 以上,所以这样的架构能得到较长时间的广泛应用。
写可用性可以通过 Keepalived 这种 HA(高可用)框架来保证主库是活着的,但仔细一想就可以明白,这种方式并没有带来性能上的可用性提升。还好,至少系统不会因为某个实例挂了就都不可用了。
可用性勉强达标了,这时候的 CAP 分析如下:
1) 分区容忍性P:依旧先看分区容忍性,主从结构的数据库存在节点之间的通信,他们之间需要通过心跳来保证只有一个 Master。
然而一旦发生分区,每个分区会自己选取一个新的 Master,这样就出现了脑裂,常见的主从数据库(MySQL,Oracle 等)并没有自带解决脑裂的方案。所以分区容忍性是没考虑的。
2) 一致性C:不考虑分区,由于任意时刻只有一个主库,所以一致性是满足的。
3) 可用性A:不考虑分区,HA 机制的存在可以保证可用性,所以可用性显然也是满足的。
所以这样的一个系统,我们认为它是 AC 的。我们再深入研究下,如果发生脑裂产生数据不一致后有一种方式可以仲裁一致性问题,是不是就可以满足 P 了呢。
还真有尝试通过预先设置规则来解决这种多主库带来的一致性问题的系统,比如 CouchDB,它通过版本管理来支持多库写入,在其仲裁阶段会通过 DBA 配置的仲裁规则(也就是合并规则,比如谁的时间戳最晚谁的生效)进行自动仲裁(自动合并),从而保障最终一致性(BASE),自动规则无法合并的情况则只能依赖人工决策了。