Skip to content

Latest commit

 

History

History
130 lines (72 loc) · 9.57 KB

CAP.md

File metadata and controls

130 lines (72 loc) · 9.57 KB

CAP

CAP原则又称CAP定理,指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性)这三个基本需求,最多只能同时满足其中的2个

简介

Consistency(一致性)

指数据在多个副本之间能够保持一致的特性(严格的一致性)

Availability(可用性)

指系统提供的服务必须一直处于可用的状态,每次请求都能获取到非错的响应(不保证获取的数据为最新数据)

Partition tolerance(分区容错性)

分布式系统在遇到任何网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务(?),除非整个网络环境都发生了故障

什么是分区?

在分布式系统中,不同的节点分布在不同的子网络中,由于一些特殊的原因,这些子节点之间出现了网络不通的状态,但他们的内部子网络是正常的。从而导致了整个系统的环境被切分成了若干个孤立的区域,这就是分区。

在分布式的环境下,网络无法做到100%可靠,有可能出现故障,因此分区是一个必须的选项

例子

根据CAP原则定义,系统的一致性、可用性和分区容错性细分如下:

  • 一致性:N1和N2的数据库V之间的数据是否完全一样。
  • 可用性:N1和N2的对外部的请求能否做出正常的响应。
  • 分区容错性:N1和N2之间的网络是否互通。

假设在N1和N2之间网络断开的时候, 这里有两种选择:

  • 第一:牺牲数据一致性,保证可用性。响应旧的数据V0给用户。
  • 第二:牺牲可用性,保证数据一致性。阻塞等待,直到网络连接恢复,数据更新操作M完成之后,再给用户响应最新的数据V1。

这个过程,证明了要满足分区容错性的分布式系统,只能在一致性和可用性两者中,选择其中一个

CAP原则权衡

1.CA without P

如果不要求P(不允许分区),则C(强一致性)和A(可用性)是可以保证的。但其实分区不是你想不想的问题,而是始终会存在,因此CA的系统更多的是允许分区后各子系统依然保持CA。

2.CP without A

如果不要求A(可用),相当于每个请求都需要在Server之间强一致,而P(分区)会导致同步时间无限延长,如此CP也是可以保证的。很多传统的数据库分布式事务都属于这种模式。

3.AP wihtout C

要高可用并允许分区,则需放弃一致性。一旦分区发生,节点之间可能会失去联系,为了高可用,每个节点只能用本地数据提供服务,而这样会导致全局数据的不一致性。现在众多的NoSQL都属于此类。

小结

这种应用一般要保证服务可用性达到N个9,即保证P和A,只有舍弃C(退而求其次保证最终一致性)。虽然某些地方会影响客户体验,但没达到造成用户流程的严重程度。

对于涉及到钱财这样不能有一丝让步的场景,C必须保证。网络发生故障宁可停止服务,这是保证CA,舍弃P。

孰优孰劣,没有定论,只能根据场景定夺,适合的才是最好的。


服务注册

zookeeper选择CP

zookeeper保证CP,即任何时刻对zookeeper的访问请求能得到一致性的数据结果,同时系统对网络分割具备容错性,但是它不能保证每次服务的可用性。从实际情况来分析,在使用zookeeper获取服务列表时,如果zk正在选举或者zk集群中半数以上的机器不可用,那么将无法获取数据。所以说,zk不能保证服务可用性

eureka选择AP

eureka保证AP,eureka在设计时优先保证可用性,每一个节点都是平等的,一部分节点挂掉不会影响到正常节点的工作,不会出现类似zk的选举leader的过程,客户端发现向某个节点注册或连接失败,会自动切换到其他的节点,只要有一台eureka存在,就可以保证整个服务处在可用状态,只不过有可能这个服务上的信息并不是最新的信息。

服务注册应该选择AP还是CP

对于服务注册来说,针对同一个服务,即使注册中心的不同节点保存的服务注册信息不相同,也并不会造成灾难性的后果,对于服务消费者来说,能消费才是最重要的,就算拿到的数据不是最新的数据,消费者本身也可以进行尝试失败重试。总比为了追求数据的一致性而获取不到实例信息整个服务不可用要好。

所以,对于服务注册来说,可用性比数据一致性更加的重要,选择AP。

分布式锁,是选择AP还是选择CP ?

基于数据库实现分布式锁

利用表的 UNIQUE KEY idx_lock (method_lock) 作为唯一主键,当进行上锁时进行insert动作,数据库成功录入则以为上锁成功,当数据库报出 Duplicate entry 则表示无法获取该锁。

不过这种方式对于单主却无法自动切换主从的mysql来说,基本就无法现实P分区容错性,(Mysql自动主从切换在目前并没有十分完美的解决方案)。可以说这种方式强依赖于数据库的可用性,数据库写操作是一个单点,一旦数据库挂掉,就导致锁的不可用。这种方式基本不在CAP的一个讨论范围

基于redis实现分布式锁

redis单线程串行处理天然就是解决串行化问题,用来解决分布式锁是再适合不过。

实现方式: setnx key value Expire_time

获取到锁 返回 1 , 获取失败 返回 0

为了解决数据库锁的无主从切换的问题,可以选择redis集群,或者是 sentinel 哨兵模式,实现主从故障转移,当master节点出现故障,哨兵会从slave中选取节点,重新变成新的master节点。

哨兵模式故障转移是由sentinel集群进行监控判断,当maser出现异常即复制中止,重新推选新slave成为master,sentinel在重新进行选举并不在意主从数据是否复制完毕具备一致性

所以redis的复制模式是属于AP的模式。保证可用性,在主从复制中“主”有数据,但是可能“从”还没有数据,这个时候,一旦主挂掉或者网络抖动等各种原因,可能会切换到“从”节点,这个时候可能会导致两个业务县城同时获取得两把锁

上述的问题其实并不是redis的缺陷,只是redis采用了AP模型,它本身无法确保我们对一致性的要求。redis官方推荐redlock算法来保证,问题是redlock至少需要三个redis主从实例来实现,维护成本比较高,相当于redlock使用三个redis集群实现了自己的另一套一致性算法,比较繁琐,在业界也使用得比较少。

能否使用redis作为分布式锁?

能不能使用redis作为分布式锁,这个本身就不是redis的问题,还是取决于业务场景,我们先要自己确认我们的场景是适合 AP 还是 CP , 如果在社交发帖等场景下,我们并没有非常强的事务一致性问题,redis提供给我们高性能的AP模型是非常适合的,但如果是交易类型,对数据一致性非常敏感的场景,我们可能要寻在一种更加适合的 CP 模型

思考:自身支付中台项目的使用场景

基于zookeeper实现分布式锁

redis其实无法确保数据的一致性,先来看zookeeper是否合适作为我们需要的分布式锁,首先zk的模式是CP模型,也就是说,当zk锁提供给我们进行访问的时候,在zk集群中能确保这把锁在zk的每一个节点都存在。

这个实际上是zk的leader通过二阶段提交写请求来保证的,这个也是zk的集群规模大了的一个瓶颈点

  • 业务线程-1 业务线程-2 分别向zk的/lock目录下,申请创建有序的临时节点
  • 业务线程-1 抢到/lock0001 的文件,也就是在整个目录下最小序的节点,也就是线程-1获取到了锁
  • 业务线程-2 只能抢到/lock0002的文件,并不是最小序的节点,线程2未能获取锁
  • 业务线程-1 与 lock0001 建立了连接,并维持了心跳,维持的心跳也就是这把锁的租期
  • 当业务线程-1 完成了业务,将释放掉与zk的连接,也就是释放了这把锁

分布式事务,是怎么从ACID解脱,投身CAP/BASE

如果说到事务,ACID是传统数据库常用的设计理念,追求强一致性模型,关系数据库的ACID模型拥有高一致性+可用性,所以很难进行分区,所以在微服务中ACID已经是无法支持,我们还是回到CAP去寻求解决方案,不过根据上面的讨论,CAP定理中,要么只能CP,要么只能AP,如果我们追求数据的一致性而忽略可用性这个在微服务中肯定是行不通的,如果我们追求可用性而忽略一致性,那么在一些重要的数据(例如支付,金额)肯定出现漏洞百出,这个也是无法接受。所以我们既要一致性,也要可用性。我全都要了!

都要是无法实现的,但我们能不能在一致性上作出一些妥协,不追求强一致性,转而追求最终一致性,所以引入BASE理论,在分布式事务中,BASE最重要是为CAP提出了最终一致性的解决方案,BASE强调牺牲高一致性,从而获取可用性,数据允许在一段时间内不一致,只要保证最终一致性就可以了。

参考链接: