分布式


CAP理论

1998年,加州大学的计算机科学家 Eric Brewer 提出,分布式系统有三个指标。

  1. 一致性(Consistency):在分布式系统中的所有数据备份,在同一时刻是否同样的值,即写操作之后的读操作,必须返回该值。(分为弱一致性、强一致性和最终一致性)

  2. 可用性(Availability):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)

  3. 分区容忍性(Partition tolerance):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。


取舍策略

  • CA without P:如果不要求P(不允许分区),则C(强一致性)和A(可用性)是可以保证的。但放弃P的同时也就意味着放弃了系统的扩展性,也就是分布式节点受限,没办法部署子节点,这是违背分布式系统设计的初衷的。 传统的关系型数据库RDBMS:Oracle、MySQL就是CA。

    纯单体应用,只有一个请求,肯定保持了一致性,可用性也能保持。

  • CP without A:如果不要求A(可用),相当于每个请求都需要在服务器之间保持强一致,而P(分区)会导致同步时间无限延长(也就是等待数据同步完才能正常访问服务),一旦发生网络故障或者消息丢失等情况,就要牺牲用户的体验,等待所有数据全部一致了之后再让用户访问系统。 设计成CP的系统其实不少,最典型的就是分布式数据库,如Redis、HBase等。对于这些分布式数据库来说,数据的一致性是最基本的要求,因为如果连这个标准都达不到,那么直接采用关系型数据库就好,没必要再浪费资源来部署分布式数据库。 最好的例子就是zookeeper,如果客户端心跳消失的时候,zookeeper会很快剔除该服务,之后就无法提供需求

    两个应用只有一个可用,另一个无法保证结果一致(挂了或者无法进行同步)所以不可用。

  • AP wihtout C:要高可用并允许分区,则需放弃一致性。一旦分区发生,节点之间可能会失去联系,为了高可用,每个节点只能用本地数据提供服务,而这样会导致全局数据的不一致性。典型的应用就如某米的抢购手机场景,可能前几秒你浏览商品的时候页面提示是有库存的,当你选择完商品准备下单的时候,系统提示你下单失败,商品已售完。这其实就是先在 A(可用性)方面保证系统可以正常的服务,然后在数据的一致性方面做了些牺牲,虽然多少会影响一些用户体验,但也不至于造成用户购物流程的严重阻塞。 Eureka就是一个AP架构的例子,当Eureka客户端心跳消失的时候,那Eureka服务端就会启动自我保护机制,不会剔除该EurekaClient客户端的服务,依然可以提供需求。

    两个应用都可用,但是结果不一致。


解决方案——BASE

一个可用的复杂的系统总是从可用的简单系统进化而来。反过来这句话也正确: 从零开始设计的复杂的系统从来都用不了,也没办法让它变的可用。 --John Gal 《系统学》 1975

分布式共享数据的系统,要实现CP,就需要牺牲可用性;如果实现AP,则牺牲了一致性。 对于大型应用来说,我们除非涉及到资金等严格要求一致性的场景,大部分我们都选择AP,即保留可用性和分区容错性。 但是我们也不一定完全牺牲掉一致性,我们可以对一致性做个柔性的变动,不要求立刻一致,可以在一定时间内达到一致即可,使用BASE理论可以达到这个目的。

BASE是Basically Available(基本可用)Soft state(软状态)Eventually consistent(最终一致性) 三个短语的简写,BASE是对CAP中一致性和可用性权衡的结果。 核心思想:即使无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。

  • 基本可用Basically Available 基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性——但请注意,这绝不等价于系统不可用,以下两个就是“基本可用”的典型例子。

    • 响应时间上的损失:正常情况下,一个在线搜索引擎需要0.5秒内返回给用户相应的查询结果,但由于出现异常(比如系统部分机房发生断电或断网故障),查询结果的响应时间增加到了1~2秒。

    • 功能上的损失:正常情况下,在一个电子商务网站上进行购物,消费者几乎能够顺利地完成每一笔订单,但是在一些节日大促购物高峰的时候,由于消费者的购物行为激增,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面。

    我们可以通过延迟响应,流量削峰等手段来保障系统的核心功能的正常,从而实现基本可用。

    • 削峰填谷 > 如果我们无法控制流量,那我们可以将数据保存到队列中的方式。我们系统可以根据自己处理能力,来消费队列数据,从而达到削峰填谷的目的。

    • 延迟响应 > 削峰填谷利用到了队列,对用户的请求不直接处理,而是缓存起来,慢慢处理,处理完毕后再返回,这对用户来说响应被延迟了,但是系统却可以处理更多的用户请求。

    • 服务降级 > 在超系统负载的大流量过来之后,我们牺牲系统非必要的功能,比如降低日志级别,比如显示分辨率更低的图片等手段,来降低系统的资源消耗,保证核心能力的正常运行。

    • 过载保护 > 如果以上手段,还是无法应对现在的流量,只能进入过载保护截断了,系统根据自己的能力抛弃超时的请求,或者随机抛弃一些请求,直接返回错误。

  • 软状态Soft state 软状态也称弱状态,和硬状态相对,是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。

    对于满足ACID的数据状态是硬状态。最终一致性的系统中,数据的读出来的不一定是最新的。

  • 最终一致性Eventually consistent 最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。

    比如DNS域名,DNS域名我们配置A记录后,域名和IP的关系不是立刻生效的,过多久也不确定,但是最终能保证生效。

    • 写时修复 > 在多个副本写的时候,如果其中一个写失败了,就缓存起来,系统自动通过重试的手段进行修复。

    • 读时修复 > 写数据的时候不关心失败还是成功,只是在读数据的时候,我们可以同时读多个节点的同一份数据,然后根据一定的规则,比如以超过半数的数据值为准的办法,来决定返回的值。

    • 异步修复 > 通过定时不同节点比对的方式,来对照数据是否需要修复,如果需要修复,则进行同步。

亚马逊首席技术官Werner Vogels在于2008年发表的一篇文章中对最终一致性进行了非常详细的介绍。他认为最终一致性时一种特殊的弱一致性:系统能够保证在没有其他新地更新操作的情况下,数据最终一定能够达到一致的状态,因此所有客户端对系统的数据访问都能够胡渠道最新的值。同时,在没有发生故障的前提下,数据达到一致状态的时间延迟,取决于网络延迟,系统负载和数据复制方案设计等因素。


Raft 协议

Raft协议将一致性协议的核心内容分拆成为几个关键阶段,以简化流程,提高协议的可理解性。 Raft协议的每个副本都会处于三种状态之一:Leader、Follower、Candidate。

  • Leader(主):所有请求的处理者,Leader副本接受client的更新请求,本地处理后再同步至多个其他副本;由所有节点选举,在candidate中产生,负责整个集群的状态以及元数据管理,当发现更大的term时,转化为follower

  • Follower(从):请求的被动更新者,从Leader接受更新请求,然后写入本地日志文件,集群初始化时所有节点的角色都是follower,若未发现leader心跳,则发起leader选举,并将角色转化为candidate;leader以及candidate在某些条件下也会转化成follower

  • Candidate(候选者):如果Follower副本在一段时间内没有收到Leader副本的心跳,则判断Leader可能已经故障,此时启动选主过程,此时副本会变成Candidate状态,直到选主结束。选举时得到多数选票,则转化为leader,若发现主节点或者更大的term则转化为follower


term

时间被分为很多连续的随机长度的term,term有唯一的id。 term大的优先级高,leader必须是拥有更大的term。


leader election process

Raft 使用心跳(heartbeat)触发Leader选举。当Server启动时,初始化为Follower。Leader向所有Followers周期性发送heartbeat。如果Follower在选举超时时间内没有收到Leader的heartbeat,就会等待一段随机的时间后发起一次Leader选举。 每个term一开始就进行选主:

  • Follower将自己维护的current_term_id加1。

  • 然后将自己的状态转成Candidate

  • 发送RequestVoteRPC消息(带上current_term_id) 给 其它所有server,要求选自己为主。

选举有三种结果:

  • 该candidate收到了多数(majority)的选票当选了leader,并发送leader心跳告知其他节点,其他节点全部变成follower,集群选主成功

  • 该candidate收到了其他节点发来的leader心跳,说明主节点已经选举成功,该candidate变成follower,集群选主成功

  • 一段时间内(election timeout),该candidate未收到超过半数的选票,也未收到leader心跳,则说明该轮选主失败,重复进行leader election,直到选主成功。为避免这种情况,每个Candidate的election timeout从150ms-300ms之间随机取。

选出Leader后,Leader通过定期向所有Follower发送心跳信息维持其统治。若Follower一段时间未收到Leader的心跳,则认为Leader可能已经挂了,然后再次发起选举过程。


log replication

当系统有了leader后,系统就进入对外工作期了。客户端的一切请求来发送到leader,leader来调度这些并发请求的顺序,并且保证leader与followers状态的一致性。raft中的做法是,将这些请求以及执行顺序告知followers。leader和followers以相同的顺序来执行这些请求,保证状态一致。

  1. 客户端向leader提交写入请求

  2. leader接收到客户端请求后封装RPC并行将修改发送到follower

  3. follower在接收到leader发送的RPC后,回复leader已经收到该请求

  4. 在leader接收到多数(majority,含leader)follower的回复后,回复客户端接收成功,将变更状态设置为commited,然后将变更写入到状态机,此时写入实际上已经生效,无法回滚

  5. leader与follower通信,协助follower完成变更的提交,当变更提交完毕后,follower会将变更写入到状态机,此时变更才真正的影响到节点,此时的状态可以理解为applied。此过程中,可能会出现各种问题,比如说网络连接超时,命令执行不成功等问题,leader会持续和follower进行通信,保证follower最终完成所有的操作,与leader达成最终一致性。这种最终一致性是对内的,对外部的client的透明的,外部的client只会看到leader上状态的强一致性。这种强一致性和最终一致性的配合使用,不仅降低了一致性实现的各种成本,还保证了系统的健壮性,能保证在各种异常情况下的恢复与状态同步。


最后更新于

这有帮助吗?