带你了解分布式系统的数据一致性问题

老刘是一名即将找工作的研二学生,写博客一方面是复习总结大数据开发的知识点,一方面是希望能够帮助和自己一样自学编程的伙伴。由于老刘是自学大数据开发,博客中肯定会存在一些不足,还希望大家能够批评指正,让我们一起进步!

今天给各位小伙伴聊聊分布式系统的数据一致性问题,这个一定要从服务器架构部署的发展历程讲起!文章篇幅较长,请大家耐心观看,精彩千万不要错过!

1. 背景

1.1. 集中式服务

首先要讲的是集中式服务,那集中式是什么?就是事情都由一台服务器搞定。

而集中式系统就是由一台或多台主计算机组成中心节点,数据集中存储于这个中心节点中,并且整个系统的所有业务都在这个中心节点上,系统所有的功能都由它做。

也就是说,在集中式系统中,每个客户端仅仅负责数据的输入和输出,而数据的存储与控制处理完全交给主机完成。

那集中式服务优点:

  1. 结构简单
  2. 部署简单
  3. 项目架构简单

但是它的缺点也是非常明显:

  1. 大型主机的研发和维护成本非常高
  2. 大型主机非常昂贵
  3. 存在单点故障问题,主机一挂,所有服务终止
  4. 大型主机的性能扩展受限于摩尔定律

什么是摩尔定律?

摩尔定律是由英特尔(Intel)创始人之一戈登·摩尔(Gordon Moore)提出来的。其内容为:当价格不变时,集成电路上可容纳的元器件的数目,约每隔18-24个月便会增加一倍,性能也将提升一倍。换言之,每一美元所能买到的电脑性能,将每隔18-24个月翻一倍以上。摘自:百度百科

摩尔定律告诉我们:纵向扩展理论上是受限的,所以只能考虑横向扩展,而且从理论上说,横向扩展理论上是不受限的!

那既然纵向扩展受限,我们就去尝试横向扩展,就有了分布式!

1.2. 分布式服务

分布式意味着可以采用更多的普通计算机(相对于昂贵的大型机)组成分布式集群对外提供服务。计算机越多,CPU、内存、存储资源等也就越多,能够处理的并发访问量也就越大。

例如一个由分布式系统实现的电子商城,在功能上可能被拆分成多个应用,分别提供不同的功能,组成一个分布式系统对外提供服务。

所以,分布式系统中的计算机在空间上是几乎没有限制的,这些计算机可能被放在不同的机柜上,也可能被部署在不同的机房中,还可能在不同的城市中。

和集中式系统相比,分布式系统的性价比更高、处理能力更强、可靠性更高、也有很好的扩展性。

但是,分布式解决了网站的高并发问题的同时也带来了一些其他问题。

首先,分布式的必要条件就是网络,这可能对性能甚至服务能力造成一定的影响。其次,一个集群中的服务器数量越多,服务器宕机的概率也就越大。另外,由于服务在集群中分布式部署,用户的请求只会落到其中一台机器上,所以,一旦处理不好就很容易产生数据一致性问题。

1.3. 分布式存在的异常

1、通信异常:网络不可用(消息延迟或者丢失),会导致分布式系统内部无法顺利进行网络通信,所以可能造成多个节点数据丢失和状态不一致,还有可能造成数据乱序。

2、网络分区:网络不连通,但各个子网络的内部网络是正常的,从而导致整个系统的网络环境被切分成若干个孤立的区域,分布式系统就出现了局部小集群造成的数据不一致。

3、节点故障:服务器节点出现的宕机的现象。

4、存储数据丢失:对于有状态节点来说,数据丢失意味着状态丢失,通常只能从其他节点读取、恢复存储的状态。解决方案:利用多副本机制。

1.4. 衡量分布式系统的性能指标

1、性能:这是一个非常让人头疼的问题,追求高吞吐的系统,往往很难做到低延迟;系统平均响应时间较长时,也很难提高QPS。

系统的吞吐能力,指系统在某一时间可以处理的数据总量,通常可以用系统每秒处理的总数据量来衡量; 
系统的响应延迟,指系统完成某一功能需要使用的时间; 
系统的并发能力,指系统可以同时完成某一功能的能力,通常也用QPS来衡量。

2、可用性:系统的可用性(availability)指系统在面对各种异常时可以正确提供服务的能力。可用性是分布式的重要指标,衡量了系统的鲁棒性,是系统容错能力的体现。

3、可扩展性:系统的可扩展性(scalability)指分布式系统通过扩展集群机器规模提高系统性能(吞吐、延迟、并发)、存储容量、计算能力的特性。

4、一致性:分布式系统为了提高可用性,总是不可避免地使用副本的机制,从而引发副本一致性的问题。

例如,就是一份数据存在分布式系统,存在多个不同的节点当中存着相同的数据。如果多个不同的节点存的数据不一样,多个客户端去访问的时候就会存在这种情况,第1个客户端去访问的结果为A,第2个客户端访问的结果为B,两个客户端访问得到不同的结果,那就是一致性做的不好。

说了这么多,我们如果设计一个优秀的分布式系统,它应该具有这些特点:吞吐高、响应延迟低、并发强、可用性很高、可扩展性很强、一致性很好。但并不是每个特点都能满足,有几个特点是相互矛盾的,需要我们想办法克服!

而在分布式场景中真正复杂的是数据一致性的问题!

1.5. 一致性理解

一致性也分很多种,这里说说老刘了解的三个。

强一致性:写操作完成之后,读操作一定能读到最新数据。通俗地讲就是客户端只要把结果写进去了,什么时候访问都能拿到最新的数据。但是在分布式场景中很难实现,后续的Paxos 算法,Quorum 机制,ZAB 协议等能实现!

弱一致性:不保证拿到最新的数据,也有可能拿到旧的数据。

最终一致性:不考虑中间的任何状态,只保证经过一段时间之后,最终系统内数据正确。在高并发场景中,它也是使用最广的一致性模型。

1.6. 分布式一致性的作用

说了那么多分布式一致性的内容,那它的作用是什么呢?

1、为了提高系统的可用性,一般都会使用多副本机制,多副本就会有分布式一致性的问题,它就是为了提高系统的可用性,防止单点节点故障引起的系统不可用。

2、提高系统的整体性能,数据分布在集群中多个节点上,它们都能为用户提供服务。

老刘说了这么多,大家有没有猜到想引出什么内容呢?

上述那么多内容只为引出分布式系统的数据一致性问题!我们用来解决分布式系统的数据一致性问题的方案有如下:

分布式事务+事务 
分布式一致性算法 
Quorum机制 
CAP和BASE理论

2. 分布式事务

分布式系统中,每个节点都能知道自己的事务操作是否成功,但是没法知道系统中的其他节点的事务是否成功。这就有可能会造成分布式系统中的各节点的状态出现不一致。因此当一个事务需要跨越服务器节点,并且要保证事务的ACID特性时,就必须引入一个协调者的角色。那么其他的各个进行事务操作的节点就都叫做参与者。

现实生活中有两种典型的分布式事务的提交模式:2PC和3PC。

2.1. 2PC提交过程

直接上图:

我让A去做一件事,让B去做另外一件事,并且这两件事在一个分布式事务中要保证同时成功或失败。那如何做到数据一致呢?

2PC分两个阶段: 

第一阶段:执行事务,但不提交。

第二阶段:当协调者收到第一阶段中所有事务参与者的正反馈时(事务都执行成功了),

就去发命令让所有参与者提交事务。

 

2.2. 2PC的问题

看了2PC的两个提交阶段和图,有经验的人一眼就会看出里面存在的问题。

1 阻塞问题

协调者发送命令给参与者,由于是网路发送命令,就会存在不同参与者收到的命令有先后、有延迟。例如参与者A很快就收到了,参与者B网络有问题,过了很久才收到命令。参与者A很快处理完发送反馈,
而参与者B就很久之后才发送反馈,导致协调者等待时间特别长。

这就是一个非常典型的阻塞问题,非常浪费资源,影响性能!

2 没有容错机制,存在单点故障问题

事务协调者是整个分布式事务的核心,一旦协调者出现故障,看看上面那张图,就会知道参与者就收不到
commit/rollback的通知,从而导致参与者节点一直处于事务无法完成的中间状态。

3 数据不一致

在第二阶段,如果发生局部网络问题,一个参与者收到提交的命令,另一个参与者没有收到提交的命令,
就会造成节点间数据不一致。

2.3. 3PC

3PC就是三阶段提交的意思,它是2阶段提交的改进版,把二阶段提交协议的 “提交事务请求” 一分为二,形成了cancommit,precommit,docommit 三个阶段。

除了在 2PC 的基础上增加了CanCommit阶段,还引入了超时机制。一旦事务参与者在指定时间内没有收到协调者的 commit/rollback 指令,就会自动本地 commit,这样可以解决协调者单点故障的问题。

2.4. 执行过程解析

第一阶段:CanCommit阶段

在第一阶段准备的时候,先问一下各个参与者是否可以进行事务操作以及超时机制,参与者在一定时间没
收到协调者的指令会自动提交。

第二阶段:PreCommit阶段

1、如果每个参与者返回的都是同意,协调者则向所有参与者发送预提交请求,并进入预提交阶段; 

2、参与者收到预提交请求后,执行事务操作。

3、参与者执行完本地事务之后,会向协调者发出Ack表示已准备好提交,并等待协调者下一步指令。

4、如果协调者收到预提交响应为拒绝或者超时,则执行中断事务操作,通知各参与者中断事务。 

5、参与者收到中断事务或者等待超时,都会主动中断事务/直接提交

第三阶段:doCommit阶段

1、协调者收到所有参与 的Ack,则从预提交入提交段,并向各参与者发送提交请求。 

2、参与者收到提交请求,正式提交事务(commit),并向协调者反馈提交结果Y/N。

3、协调者收到所有反馈消息,完成分布式事务。

4、如果协调者超时没有收到反馈,则发送中断事务指令。

5、参与者收到中断事务指令后,利用事务日志进行rollback。 

6、参与者反馈回滚结果,协调者接收反馈结果或者超时,完成中断事务。

2.5. 3PC的问题

3PC也可能出现数据不一致,第三阶段让所有参与者回滚事务,但有一个参与者在规定的时间内没有收到,它会默认进行提交操作,就会出现数据不一致。由于网络问题,第二阶段到第三阶段之间特别容易出现数据不一致问题。

3. 分布式一致性算法

在2PC和3PC的原理上,优秀的开发者们实现了分布式一致性算法,这里老刘先大致讲讲Poxos算法和ZAB协议的相关概念。如果想详细了解Paxos算法和ZAB协议,等老刘找完工作后,专门写一篇Zookeeper源码文章。

3.1. Paxos算法

Paxos 算法使用一个希腊故事来描述,在 Paxos 中,存在三种角色,分别为

1、Proposer(提议者,用来发出提案proposal),

2、Acceptor(接受者,可以接受或拒绝提案),

3、Learner(学习者,学习被选定的提案,当提案被超过半数的Acceptor接受后为被批准)。

映射到 zookeeper 集群:

leader:发起提案  主席(单点故障的解决办法是leader选举机制)

follower:参与投票  人大代表

observer:被动接受  全国所有人

以及有一个特别出名的机制:议会制

保证超过半数达成一致性即可的协议

总结下Paxos算法,它就是所有事务请求必须由一个全局唯一的服务器来协调处理,这样的服务器被称为 leader 服务器,而余下的其他服务器则成为 follower 服务器。

leader 服务器负责将一个客户端事务请求转换成一个事务proposal,并将该 proposal 分发给集群中所有的follower 服务器。之后 leader 服务器需要等待所有follower 服务器的反馈,一旦超过半数的 follower 服务器进行了正确的反馈后,那么 leader 就会再次向所有的 follower 服务器分发 commit 消息,要求其将前一个 proposal 进行提交。

3.2. ZAB协议

ZooKeeper的底层工作机制,就是依靠 ZAB 实现的。它实现了崩溃回复和消息广播两个主要功能。

ZAB协议保证数据一致性的两个重要特点就是:

1、ZAB协议需要确保那些已经在 leader 服务器上提交的事务最终被所有服务器都提交。

2、ZAB协议需要确保丢弃那些只在 leader 服务器上被提出的事务。

为了解决单点故障,有leader选举算法。在leader选举中,如果让 leader 选举算法能够保证新选举出来的 leader 服务器拥有集群中所有机器最高事务编号(ZXID)的事务proposal,那么就可以保证这个新选举出来的 leader 一定具有所有已经提交的提案。

因为事务的每次执行都会有一个编号,最高事务编号代表着最新的事务,即最新的数据。 根据上述ZAB协议内容,ZooKeeper实现了分布式系统数据的一致性!

4. 鸽巢原理

简单描述:若有n个笼子和n+1只鸽子,所有的鸽子都被关在鸽笼里,那么至少有一个笼子有至少2只鸽子 。

5. Quorum NWR机制

Quorum NWR:Quorum 机制是分布式场景中常用的,用来保证数据安全,并且在分布式环境中实现最终一致性的投票算法。这种算法的主要原理来源于鸽巢原理。它最大的优势,既能实现强一致性,而且还能自定义一致性级别!

N:总节点数

W:总写入成功数

R:总读取数

当W+R>N时,一定能保证读到最新的数据,即强一致性! 为什么这样说?

如上图,有4个箱子,3个箱子里面有东西,那如何保证一定能拿到有数据的箱子?最起码拿2个箱子就能拿到有东西的箱子!

就是利用这种原理,只要保证(W + R > N)就一定能读取到最新的数据,数据一致性级别完全可以根据读写副本数的约束来达到强一致性!

那现在分以下三种情况讨论:前提是N已经确定不改了!

W = 1, R = N,Write Once Read All

在分布式环境中,写一份,相当于只有只有一个箱子有东西,那么如果要读取到最新数据,即拿到有东西的箱子,就必须要读取所有节点,然后取最新版本的值了。写操作高效,但是读操作效率低。一致性高,但分区容错性差,可用性低。

W = N,R = 1, Read Only Write All

在分布式环境中,所有节点都同步完毕,才能读取,所以只要读取任意一个节点就可以读取到最新数据。读操作高效,但是写操作效率低。分区容错性好,一致性差,实现难度更高,可用性高 。

W = Q, R = Q where Q = N/2 + 1

可以简单理解为写超过一半节点,那么读也超过一半节点,取得读写性能平衡。一般应用适用,读写性能之间取得平衡。如 N=3, W=2, R=2,分区容错性,可用性,一致性取得一个平衡。

ZooKeeper就是这么干的!采用了第三种情况!

6. CAP理论

根据上述说的,做到强一致性了,就难做到高可用,两者是非常矛盾的。所以CAP理论就告诉我们,一个分布式系统不可能同时满足C,A,P三个需求。

C:Consistency,强一致性

分布式环境中多个数据副本保持一致

A:Availability,高可用性

系统提供的服务必须一直处于可用,对于用户的每一个操作请求总是能在有限时间内返回结果

P:Partiton Tolerance 分区容错性

分布式系统在遇到任何网络分区故障时,仍然需要能够保证对外提供满足一致性和可用性的服务

既然一个分布式系统不能同时满足C,A,P三个需求,那么如何选择?

CAP只能3选2,因为在分布式系统中,容错性P肯定是必须有的,所以这时候无非就两种情况,网络问题导致要么错误返回,要么阻塞等待,前者牺牲了一致性,后者牺牲了可用性。

对于单机软件,因为不同考虑P,所以肯定是CA型,比如MySQL。

对于分布式软件,因为一定会考虑P,所以又不能兼顾A和C的情况下,只能在A和C做权衡,比如HBase、Redis等。做到服务基本可用,并且数据最终一致性即可。 所以,就产生了BASE理论。

7. BASE理论

多数情况下,其实我们也并非一定要求强一致性,部分业务可以容忍一定程度的延迟一致,所以为了兼顾效率,发展出来了最终一致性理论 BASE,它的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。

一句话就是做事别走极端,BASE 是对 CAP 理论中的 C 和 A 进行权衡得到的结果。

BASE理论做到的不是强一致,而是最终一致;不是高可用,而是基本可用。

Basically Available(基本可用):基本可用是指分布式系统在出现故障的时候,允许损失部分可用性,保证核心可用。 例如:淘宝双11,为保护系统稳定性,正常下单,其他边缘服务可暂时不可用。

Eventually Consistent(最终一致):最终一致性是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。

以后开发分布式系统,就可以根据业务来决定到底追求高可用还是追求强一致性!

8. 总结

好啦,分布式系统的数据一致性问题大致聊得差不多了,老刘主要给大家讲了讲分布式系统一致性的背景以及实现。尽管当前水平可能不及各位大佬,但老刘还是希望能够变得更加优秀,能够帮助更多自学编程的伙伴。

如果有相关问题,请联系公众号:努力的老刘,和老刘进行愉快的交流,如果觉得帮到了您,不妨点赞关注支持一波!