CAP和BASE理论理解

113 阅读6分钟

大家好,我是魔性的茶叶,最近在研究CAP和BASE理论,写点心得和大家分享

CAP

C(Consistency)一致性:指分布式中多个节点,数据保持一致,多个客户端调用服务端不同的节点,得到的数据应该是一致的。

A(Availability)可用性:指节点能够正常提供服务,服务不会不提供服务或者响应时间超长

P(Partition tolerance):分区容错性,指网络分区或者节点失效的情况,服务仍然能够继续工作,不会因为网络分区或者节点失效导致无法提供服务(用人话解释,就是有多个节点,一个节点挂了其他节点一样能提供服务)。

分布式的前提就是多节点,所以分区容错性是一定需要的,也就是一定需要P(如果一个节点挂了,整个集群都不能提供服务了,这算什么分布式?)

那么,c和a为什么只能选其中一个呢?

(能不能ac都要呢,能,只要放弃p,服务只有一个节点,数据当然TM的一致又可用,就是唯一一个缺点这算啥分布式,这不就一单体吗)。

如果要保证强一致性,那么说明每个节点的数据肯定要一致,那么节点之间的数据不一致是不可以被容忍的,每次客户端发起调用,假如此时请求的服务器节点数据还未同步完,客户端得等待服务器节点完成同步,此时可用性下降了。

如果要保证强可用性,那么客户端调用服务器节点就需要立刻返回,但是此时数据有未同步过来的风险,也就是说一致性下降了。

你想想,假如你写个程序,主库写入一条数据,希望待会再读出来,结果下一行代码直接读不出来抛个空指针异常,你和老板说因为数据还没同步到从库,wok,这还得了,老板得劈了你。

那么有没有完美的策略呢,就没有一个方法又c又a又p吗。

很遗憾,真没有,鱼和熊掌不能兼得,连高斯林来了都要摇头。

但是我们参考牛逼的中间件,同时吃到一部分鱼和一部分熊掌。

mysql的主从同步策略,也会遇到网络分区的问题,但是mysql加入了半同步策略,保证一定数量的节点一定能同步到数据,而不是事务提交卡在等待其他节点响应,mysql相当于做了一个折中(trade off原则),同时保证了一部分的可用性和一致性。

再看下kafka,也会在配置里面让用户填入数据的同步的策略,或者是leader等待所有从节点写入完成后才提交。

这些中间件的可配置策略其实告诉我们一件事,没有完美的配置策略(不然就直接用了,不给你配置了),请君在可用性和一致性里面选一个吧。

BASE

其实上面提到的中间件的折中方案,都是遵循BASE理论,先解释下BASE这四个单词啥意思。

BA(Basically Available)基本可用:不追求强一致性,在完全可用和完全不可用中间找个中间点,保障用户的基本使用,比如说在双11的大流量场景下,允许个人中心暂时不可用,将所有资源提供给抢购服务上,这也算一种一种基本可用。

S(Soft state)软状态:CAP强调的状态属于一种“硬状态“,何谓硬状态呢,指的是数据处于一种最终状态,比如说在硬状态下,数据只会由1到2,系统内只会存在1或2的数据,而软状态允许数据存在1.5这个值,允许数据存在中间状态。

E(Eventually consisten)最终一致性:不强调数据立刻变成某种状态,但是最后要变成某种状态。比如说银行转账,对方可以并不立刻收到钱,但是最后他一定会收到钱。中间时刻,转账状态变成打钱的账户已经扣除转过去的钱,但是收到转账的账户未收到钱的软状态,最后结果还是去到发起转账的账户扣钱,收到转账的账户收到钱。

所以我们看到mysql的半同步策略和kafka的leader对follower部分同步策略其实都是BASE理论的一种实现。

那么在我们设计功能/系统架构的时候能不能引入这种理论呢,当然是可以的。

假如我们设计一个支付的记账功能,功能是收到支付完成请求-》记录支付回调信息-》调用记账服务记录支付流水

我们设计成这样

  1. 开启事务
  2. 支付回调信息落库
  3. 调用记账服务记录支付流水
  4. 提交事务

这样,假如调用记账服务失败,我们可以直接回滚整个事务,这个流程属于CAP的设计,属于强一致的设计,用户立刻就能看到支付流水。

这种设计在现有的流程下是没问题的,但是这个时候老板说要加个功能,支付成功的时候给这个用户加积分,加积分需要调用积分服务。

我们按照之前的流程设计成这样:

  1. 开启事务
  2. 支付回调信息落库
  3. 调用记账服务记录支付流水
  4. 调用积分服务增加积分
  5. 提交事务

这种流程是不行的,假如记账服务调用成功而积分服务失败的情况下,本地事务回滚,支付状态回滚,出现了数据不一致,支付流水增加了,但是积分没增加。

这个时候我们就需要引入BASE理论了,重新修改下流程,新增一张支付回调任务表:

  1. 开启事务
  2. 支付回调信息落库
  3. 支付回调任务记录落库
  4. 提交事务

定时器流程开始

  1. 扫描支付回调任务未写入支付流水的任务
  2. 开始事务
  3. 写入支付流水
  4. 修改支付回调任务为待调用记账服务状态
  5. 提交事务
  6. 扫描待调用记账服务的支付回调任务记录
  7. 开始事务
  8. 修改支付回调任务为待调用积分服务状态
  9. 调用积分服务状态
  10. 提交事务
  11. 扫描待调用积分服务的支付回调任务记录
  12. 开始事务
  13. 修改支付回调任务状态为完成
  14. 调用积分服务
  15. 提交事务

可以看到,引入了各个服务调用的中间状态,我们通过定时器反复重试来解决这个问题,这种设计在其他服务支持幂等的情况可以支持无限的扩展。最终效果也达到了最终一致性。

再说个例子,面试里经常说的秒杀活动,库存通过先扣减redis中的库存,再通过消息队列去扣减数据库库存来实现redis和数据库库存的一致也是一种BASE理论的实现。