包含标签 分布式 的文章

理解 CAP 理论

背景 CAP理论 实际上听起来非常简单,但是有时候,遇到一些具体的问题的时候, 还是不能很清晰的分辨出来,到底是CP还是AP,以及一些其他的问题。因此,专门作为"生产者"来学习下,加深理解。 首先,在理论计算机科学中,CAP定理(CAP theorem),又被称作布鲁尔定理(Brewer's theorem),它指出对于一个分布式计算系统来说,不可能同时满足以下三点: 1.一致性(Consistency) (等同于所有节点访问同一份最新的数据副本) 2.可用性(Availability)(每次请求都能获取到非错的响应——但是不保证获取的数据为最新数据) 3.分区容错性(Partition tolerance)(以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。) 根据定理,分布式系统只能满足三项中的两项而不可能满足全部三项。 这个定理起源于加州大学柏克莱分校(University of California, Berkeley)的计算机科学家埃里克·布鲁尔在2000年的分布式计算原理研讨会(PODC)上提出的一个猜想。 在2002年,麻省理工学院(MIT)的赛斯·吉尔伯特和南希·林奇发表了布鲁尔猜想的证明,使之成为一个定理。 举例 假设你明天就要放长假了,你想买一本战争与和平的书籍,你最喜欢的在线商城里面只有一本了。 一致性: Consistency,在Gilbert and Lynch 的论文里,他们也用 “Atomic” 原子性来代替一致性这个单词。 在买书的这个例子里,你要么就是把书放到了购物车,要么就是放失败了,要么付款,要么没付款,不可能说放了一半,或者说买了一半。只有一本书,如果两个客户都准备买,缺乏一致性的话,如果两个人都完成了下单,可能会出问题。比如两个人都下了单,当然,在这个例子中,并不严重。 我们也可以用数据库来解决这个问题,数据库里有个字段减去个1,然后当及其他客户也要付款的时候,我们提示他没了。 数据库看很好用。因为具有ACID的能力。既有一致性,又有原子性,中间状态对第二个客户端是不可见的。是隔离的。因为第二个客户端再下单的时候,另一个用户在事务中的话,就锁住了数据库那条记录。 可用性: 可用性就是说,当你需要的时候,大部分情况下,服务都是可以为你服务的。以买书为例,在用户A开启事务的时候,有那么几毫秒是锁表的,这个阶段,服务可以认为对其他用户是不可用的。并不是说要时时刻刻可用,一般会有个可用率的指标。如果记不住,可以通过这里来计算: https://uptime.is/99.99999 分区容错: 如果你就一个数据库,一个服务端,那一般也都是原子的,如果挂了,服务不可用,但是数据还是一致的。 一旦你把数据和代码逻辑,开始部署在不同的节点上,这时候就存在分区。如Node A 不能和Node B通信来,这种分区问题经常出现。 用图来证明: 在一个网络环境下,有两个节点,N1和N2,共享相同的数据V,在买书这个例子中,这个数据里面存储的就是有多少本书,假设初始值是V0,在N1上运行一个买卖算法,A,假设这个算法没有bug,非常正确,可心来,N2也是类似的,叫做B,A写了一个新值到V中,然后B从V中读取。 正常流程是这样,A写完之后,N1和N2通过一个消息(非具体的消息),将这个值同步给N2。然后B也就能读到了。 1.A写了一个值V1 2.从N1发了个消息M到N2。 3.B也能从V中读到V1了 但是,现实没有这么美好 网络发生了分区,从N1到N2的消息没有投递成功。这样,到第三步的时候,N2读到了V0这个错误的值。 如果M是一个异步消息,那么N1都没办法知道N2是不是收到了。即使有办法保证M这个消息一定发出去了。那么N1也没办法知道,这个消息是不是被投递了,也不知道N2处理的时候,有没有问题。那么,如果我们把M改成同步消息呢。也不行,因为这意味着将A写值到N1,和从N1到N2更新事件是一个原子操作。 CAP告诉我们,如果我们想要A和B高度可用(低延迟),我们就要N1和N2保持分区容错,比如出现消息丢失,消息未投递,硬件故障,或者处理失败。这种情况下,就会出现有时候一些节点任务V是V0,另一些节点认为是V1. 如果有一个事务,叫做a1,a1可能是一个写操作,a2是一个读操作,在本地系统中,通过数据库或者自己加锁,加隔离是很简单的。可以强制a1写完之后,a2才发生,但是在分布式环境中,一旦加了这些东西,就影响额分区容错和可用性。 处理CAP CA 不要 P,不要分区容错 不保证分区容错,那么你可以部署在一个台机器上,但是容量受限。并且还是会存在网络问题。分布式环境下,网络分区是必然的。除非你就不想做分布式。 在分布式的环境下,网络无法做到100%可靠,有可能出现故障,因此分区是一个必须的选项,如果选择了CA而放弃了P,若发生分区现象,为了保证C,系统需要禁止写入,此时就与A发生冲突,如果是为了保证A,则会出现正常的分区可以写入数据,有故障的分区不能写入数据,则与C就冲突了。因此分布式系统理论上不可能选择CA架构,而必须选择CP或AP架构。 从Google的经验中可以得到的结论是,无法通过降低CA来提升P。要想提升系统的分区容错性,需要通过提升基础设施的稳定性来保障。 所以,对于一个分布式系统来说。P是一个基本要求,CAP三者中,只能在CA两者之间做权衡,并且要想尽办法提升P。 CP 不要 A 不要可用性 当你想要分区容错的时候,并且可以容忍长时间的停机或者无影响。就可以舍弃可用性。 一个保证了CP而一个舍弃了A的分布式系统,一旦发生网络故障或者消息丢失等情况,就要牺牲用户的体验,等待所有数据全部一致了之后再让用户访问系统。 设计成CP的系统其实也不少,其中最典型的就是很多分布式数据库,他们都是设计成CP的。在发生极端情况时,优先保证数据的强一致性,代价就是舍弃系统的可用性。如Redis、HBase等, 常用的Zookeeper也是在CAP三者之中选择优先保证CP的。ZooKeeper是个CP 的,即任何时刻对ZooKeeper的访问请求能得到一致的数据结果,同时系统对网络分区具备容错性。但是它不能保证每次服务请求的可用性,也就是在极端环境下,ZooKeeper可能会丢弃一些请求,消费者程序需要重新请求才能获得结果。 ZooKeeper 是分布式协调服务,它的职责是保证数据在其管辖下的所有服务之间保持同步、一致。所以就不难理解为什么 ZooKeeper 被设计成CP而不是AP特性的了。从实际情况来分析,在使用 Zookeeper 获取服务列表时,如果 ZooKeeper 正在选举或者 ZooKeeper 集群中半数以上的机器不可用,那么将无法获取数据。所以说,ZooKeeper 不能保证服务可用性。 Eureka 则是一个AP系统,一部分节点挂掉不会影响到正常节点的工作,不会出现类似 ZK 的选举 Leader 的过程,客户端发现向某个节点注册或连接失败,会自动切换到其他的节点。 只要有一台 Eureka 存在,就可以保证整个服务处在可用状态,只不过有可能这个服务上的信息并不是最新的信息。 SofaRegistry 也是一个AP系统。 AP 不要 C 不要一致性 要高可用并允许分区,则需放弃一致性。一旦网络问题发生,节点之间可能会失去联系。为了保证高可用,需要在用户访问时可以马上得到返回,则每个节点只能用本地数据提供服务,而这样会导致全局数据的不一致性。 这种舍弃强一致性而保证系统的分区容错性和可用性的场景和案例非常多。前面我们介绍可用性的时候说到过,很多系统在可用性方面会做很多事情来保证系统的全年可用性可以达到N个9,所以,对于很多业务系统来说,比如淘宝的购物,12306的买票。都是在可用性和一致性之间舍弃了一致性而选择可用性。 举个例子,你在12306买票的时候肯定遇到过这种场景,你购买的时候提示你是有票的(但是可能实际已经没票了),你也正常下单了。但是过了一会系统提示你下单失败,余票不足。这其实就是先在可用性方面保证系统可以正常的服务,然后在数据的一致性方面做了些牺牲,会影响一些用户体验,但是也不至于造成用户流程的严重阻塞。 但是,我们说很多网站牺牲了一致性,选择了可用性,这其实也不准确的。就比如上面的买票的例子,其实舍弃的只是强一致性。退而求其次保证了最终一致性。也就是说,虽然下单的瞬间,关于车票的库存可能存在数据不一致的情况,但是过了一段时间,还是要保证最终一致性的。也就是说,最终不会出现,2个人买到了同样的票。 对于多数大型互联网应用的场景,主机众多、部署分散,而且现在的集群规模越来越大,所以节点故障、网络故障是常态,而且要保证服务可用性达到N个9,即保证P和A,舍弃C(退而求其次保证最终一致性)。虽然某些地方会影响客户体验,但没达到造成用户流程的严重程度。 怎么选择呢 既要又要。那怎么办? 虽然三个不能保证,但我们能不能在一致性上作出一些妥协,不追求时时刻刻的强一致性,转而追求最终一致性,所以引入 BASE 理论。 在分布式事务中,BASE 最重要是为 CAP 提出了最终一致性的解决方案,BASE 强调牺牲高一致性,从而获取可用性,数据允许在一段时间内不一致,只要保证最终一致性就可以了,实现最终一致性。 弱一致性:系统不能保证后续访问返回更新的值。需要在一些条件满足之后,更新的值才能返回。从更新操作开始,到系统保证任何观察者总是看到更新的值的这期间被称为不一致窗口。 最终一致性:这是弱一致性的特殊形式;存储系统保证如果没有对某个对象的新更新操作,最终所有的访问将返回这个对象的最后更新的值。 BASE 模型 BASE 模型是传统 ACID 模型的反面,不同于 ACID,BASE 强调牺牲高一致性,从而获得可用性,数据允许在一段时间内的不一致,只要保证最终一致就可以了。 BASE 模型反 ACID 模型,完全不同 ACID 模型,牺牲高一致性,获得可用性或可靠性:Basically Available 基本可用。 支持分区失败(e.g. sharding碎片划分数据库)Soft state 软状态,状态可以有一段时间不同步,异步。 Eventually consistent 最终一致,最终数据是一致的就可以了,而不是时时一致。 参考 http://www.julianbrowne.com/article/brewers-cap-theorem https://www.cnblogs.com/13yan/p/9243669.html……

阅读全文

深入理解Raft协议

本文部分以JRaft为例,来详细介绍Raft。 Raft 来源 首先,我们介绍 Raft 问题的来源,Raft 实际上是一个一致性算法的一种实现,和Paxos等价,但是在实现上,简化了一些,并且更加易用。 这里面又引入了两个名字。一个是一致性,一个是Paxos,我们先说一致性, 一致性是一个可容错的分布式系统重的最基本的一个问题,一致性包含了“多个服务器对同一个值达成共识,一旦对某个值达成共识,这个决定就是不可变了”,通常,一致性算法,当多数服务器可用的时候,才有效,比如5个server,那么2个挂了,是没问题的,但是再挂一个,超过一半,就不能提供服务了。这句话也说明,他不会返回错误的值,因为都不提供服务了。 一致性通常和 Replicated State Machines(后面简称RSM)相关,最早提出是在图灵奖得主Leslie Lamport的著名论文"Time, clocks, and the ordering of events in a distributed system(1978)“论文中,比较系统性的阐述是在Fred Schneider的论文” Implementing fault-tolerant services using the state machine approach(1990)“中。 它的基本思想是一个分布式的RSM系统由很多个replica组成,每个replica是一个状态机,它的状态保存在一组状态变量中。状态机的状态通过并且只能通过外部命令(commands)来改变。比如你可以把MySQL服务器想像成一个状态机。它每接收到一条带修改功能的SQL语句(比如update/insert)就会改变它的状态。一组配置好replication的MySQL servers就是典型的RSM。 RSM能够工作基于这样的假设: 如果一些状态机具有相同的初始状态,并且他们接收到的命令也相同,处理这些命令的顺序也相同,那么它们处理完这些命令后的状态也应该相同。 因为replica都具有相同的状态,所以坏掉任何一个也没有关系。有了RSM之后理论上可以做到永远不会因为机器的物理故障而丢失数据。 也就是说,根据论文的指导,比较普遍的构建容错系统的方法是,每个服务器都维持一个状态机和一个Log,状态机就是我们想要实现容错的一个组件实现,比如想实现一个分布式环境下可容错的 Hash Table, 客户端会和这个状态机交互,每个状态机从log中获取input命令,在这个Hash Table 的例子中,这个log 可能是类似 把X 设置成3这样的命令,一致性算法必须确保,如果任何一个状态机在第N个命令中认可了n被设置为了3,那么其他机器上的状态机器就绝对不应该设置为其他值,这就能保证其他所有的机器总是处理相同的命令序列,最终大家都是同样的状态。 至于Paxos,这里可以先简单理解就是个一致性算法的实现方式,和 Raft 类似。 总结就一致性是为了解决分布式环境下的容错问题,而Raft 和 Paxos 是其中的一种实现。 核心怎么实现呢 要实现Raft,根据作者的表述,通过对状态空间的简化,以及问题的分解,实现方只需要实现的就是各个子问题 状态空间: 状态太多就会增加理解的困难程度。Raft 算法尽可能地确定各个环节的状态。典型地,Raft 算法采用 strong leader 的模型,每个日志的读写均由 Leader 从中主动协调,这样一来,整体系统的数据流将非常简单:从 Leader 流行 Follower。而且每个节点的状态也只有 3 种:Leader,Candidate 和 Follower。 子问题: Leader election:描述如何从集群的几个节点中选举出 Leader; Log Replication:描述如何将日志同步到各个节点从而达成一致; Safety:定义了一组约束条件来保证 Raft 算法的强一致性; Membership changes:描述如何变更集群关系(增加或者减少节点); Leader election Raft的节点被称为peer,节点的状态是Raft算法的关键属性,在任何时候,Raft节点可能处于以下三种状态: Leader:Leader负责处理客户端的请求,同时还需要协调日志的复制。在任意时刻,最多允许存在1个Leader,其他节点都是Follower。注意,集群在选举期间可能短暂处于存在0个Leader的场景。 Follower:Follower是被动的,它们不主动提出请求,只是响应Leader和Candidate的请求。注意,节点之间的通信是通过RPC进行的。 Candidate:Candidate是节点从Follower转变为Leader的过渡状态。因为Follower是一个完全被动的状态,所以当需要重新选举时,Follower需要将自己提升为Candidate,然后发起选举。 但是这种机制也带来一个麻烦,如果一个节点 因为自己的原因没有看到 Leader 发出的通知,他就会自以为是的试图竞选成为新的Leader,虽然不断发起选举且一直未能当选(因为Leader和其他船都正常通信),但是它却通过自己的投票请求实际抬升了全局的 Term 为了阻止这种“捣乱”,可以设计一个预投票 (pre-vote) 环节。候选者在发起投票之前,先发起预投票,如果没有得到半数以上节点的反馈,则候选者就会放弃参选,也就不会提升全局的 Term。 Candidate 被 ET(Election Timeout) 触发 Candidate 开始尝试发起 pre-vote 预投票 Follower 判断是否认可该 pre-vote request Candidate 根据 pre-vote response 来决定是否发起 RequestVoteRequest Follower 判断是否认可该 RequestVoteRequest Candidate 根据 Response 来判断自己是否当选 线性一致性 线性一致读是在分布式系统中实现 Java volatile 语义,当客户端向集群发起写操作的请求并且获得成功响应之后,该写操作的结果要对所有后来的读请求可见。其实就是CAP里面的C, Raft log read 实际上如果基于Raft本身的设计,因为每次 Read 都需要走 Raft 流程,Raft Log 存储、复制带来刷盘开销、存储开销、网络开销,走 Raft Log不仅仅有日志落盘的开销,还有日志复制的网络开销,另外还有一堆的 Raft “读日志” 造成的磁盘占用开销,导致 Read 操作性能是非常低效的,所以在读操作很多的场景下对性能影响很大,在读比重很大的系统中是无法被接受的,通常都不会使用。……

阅读全文