大家好,我是魔性的茶叶,最近在研究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理论的一种实现。
那么在我们设计功能/系统架构的时候能不能引入这种理论呢,当然是可以的。
假如我们设计一个支付的记账功能,功能是收到支付完成请求-》记录支付回调信息-》调用记账服务记录支付流水
我们设计成这样
- 开启事务
- 支付回调信息落库
- 调用记账服务记录支付流水
- 提交事务
这样,假如调用记账服务失败,我们可以直接回滚整个事务,这个流程属于CAP的设计,属于强一致的设计,用户立刻就能看到支付流水。
这种设计在现有的流程下是没问题的,但是这个时候老板说要加个功能,支付成功的时候给这个用户加积分,加积分需要调用积分服务。
我们按照之前的流程设计成这样:
- 开启事务
- 支付回调信息落库
- 调用记账服务记录支付流水
- 调用积分服务增加积分
- 提交事务
这种流程是不行的,假如记账服务调用成功而积分服务失败的情况下,本地事务回滚,支付状态回滚,出现了数据不一致,支付流水增加了,但是积分没增加。
这个时候我们就需要引入BASE理论了,重新修改下流程,新增一张支付回调任务表:
- 开启事务
- 支付回调信息落库
- 支付回调任务记录落库
- 提交事务
定时器流程开始
- 扫描支付回调任务未写入支付流水的任务
- 开始事务
- 写入支付流水
- 修改支付回调任务为待调用记账服务状态
- 提交事务
- 扫描待调用记账服务的支付回调任务记录
- 开始事务
- 修改支付回调任务为待调用积分服务状态
- 调用积分服务状态
- 提交事务
- 扫描待调用积分服务的支付回调任务记录
- 开始事务
- 修改支付回调任务状态为完成
- 调用积分服务
- 提交事务
可以看到,引入了各个服务调用的中间状态,我们通过定时器反复重试来解决这个问题,这种设计在其他服务支持幂等的情况可以支持无限的扩展。最终效果也达到了最终一致性。
再说个例子,面试里经常说的秒杀活动,库存通过先扣减redis中的库存,再通过消息队列去扣减数据库库存来实现redis和数据库库存的一致也是一种BASE理论的实现。