分布式系统中的一致性问题

2014-07-07

前几天有个面试问到分布式存储中的一致性问题,没答上来。记得很久以前看过一些相关的,现在复习整理一下。

一致性定义

首先,是关于一致性定义。

强一致性,这个是要求最苛刻的一项,很好理解,就是任何条件下去读,读到的都应该是最后成功写入的记录。这个举个例子,没有副本,只能从一个地方读,那么就是强一致性的。

用户一致性,比强一致性弱一些,只要求每个用户看到的是一致的。也就是说,对每个用户,他每次读到的数据版本不会比它上次读到的更旧,但用户之间可以不一致。这个可以举个例子,每个用户从固定的某个副本上读,可以保证用户一致性。比如说A一直读的都是primary,B一直读的是secondary,那么A和B之间不能保证一致(primary和secondary同步需要时间)。但是分别对A和B用户,他们读到数据版本,不会比上一次的旧,这就是用户一致性。

会话一致性,比用户一致性弱一些,它跟用户一致性的区别是,只要求对每个用户,在某个session内的一致性。

最终一致性,这是一个较弱的一致性要求,但属于工业上比较常用的一个一致性标准。就是说客户端只要保持一直的读,在未来某个时刻,最终是能够读到一个一致的数据的。这个举个例子,就好比每个用户随便从primary读或是从secondary上读,只要保持一直读,由于最终primary是会跟secondary同步的,用户最终也能读到某个一致的数据。

弱一致性,概念忘记了。弱一致性好像不怎么实用,因为它太弱啦,弱暴了。

副本

CAP理论,P必须要保障,那么C跟A的冲突。副本使得可用性提高,但也是引入不一致的根本原因。

常用的是三副本。最简单的例子,比如一个master,二个slaver。这就是三份副本,只不过这里master起主导作用,slaver在master挂掉的时候才顶上去。

如果按类型分,可以分为提供数据和提供CPU。提供CPU类型副本的一般是不带状态,所以挂掉后可以直接切换。而提供数据类型副本的,是带状态的,处理起来比较复杂。

至于数据类型副本,也可以再具体划分。如果是机器级别的副本,就好比A是primary,B是secondary,B上的数据跟A上是完全一致的,B是作为A的一个副本。这种数据一般是实现起来比较简单粗暴,但是也有一些问题。首先是数据的利用率低,像A和B这样相当于只有50%的利用率。另一个问题是恢复速度,如果A挂了,那么数据要从B恢复到A,恢复速度慢,B如果停机下来恢复A,服务就不可用了。

另一种是按数据划分的副本,多个机器上都有某块数据的一部分,但每个机器上都不是完整的数据。这样做的优点是,存储利用率高很多,可靠性也提高,但是缺点是,实现起来比机器级的副本要麻烦。

策略

讲到最关键的部分了。分布的存储架构,有两种:中心化和去中心化,以Dynamo和BigTable为代表。

真正做到去中心化实现非常复杂,而且性能低于中心化方案。所以很多情况下,即使去中心化方案中,都是使用paxos协议选主,然后转为中心化方案,用gossip协议维护集群信息。

主副本比如说叫primary,其它副本叫secondary/third等。策略的不同,非primary可以提供服务或者不提供服务。如果是仅有primary能提供服务,就是一种典型的主从结构。而如果非primary也能提供服务,可能就会采用NRW。

先说主从吧,仅有primary提供服务,其它的副本不提供服务,采取一定的措施可以达到强一致性。比如,写入的时候,写所有的secondary/third等副本都成功的条件下,才返回。而如果写其中某个失败,怎么办呢?我们可以将失败的那个打上标记,从副本中移除,然后返回用户请求;等失败的那一个恢复并跟primary完全同步之后,解除标记,再次将它纳入primary的副本管理中。这种做法是能够保证强一致性的,当然,副本处于失败恢复期间系统可靠性降低了,也就是牺牲了可用性A换取一致性C。

接下来说NRM。要是写全部副本成功才返回给用户成功,这种一致性是保证了,但是响应时间长,可用性低了。那么应该如何取折中呢?Quorum就是一种折中方式,我直接把它叫NRW吧,比较好理解。N是指副本的数量,R是成功读副本数量,W是成功写副本数量。业界N一般取3,我们就以N=3为例。NRW中要求R+W>N。这里取R=2 W=2,表示的是,用户写请求,必须至少写成功两份副本,才会返回成功给用户。而用户读的时候,必须要读取两份副本。为什么是R+W>N呢?因为如果发生了一不致,用户能知道出现了副本不同版本差异。比如说写的时候primary成功了,secondary成功了,而third失败了。读取的时候,读到的是secondary和third两个副本,这里用户就能感知到secondary和third版本的不同,secondary版本较新,然后用户决定取舍。总之,是把处理权交给了用户。Dymano中是用了一个叫向量版本的东西。

问题

引起不一致的原因,主要几种:

  • 更新时差,导致secondary落后于primary
  • 脏数据,这种在一定的策略下可能发生
  • 新加入的结点,由于down机恢复或者是加入新机器等

如果这个世界没有错误没有异常发生,那么分布式就不是问题了,是hello world。但是分布式环境下,错误是必然的,所以分布式编程才不好做。举一个常见的问题。

副本切换这边,切换primary不能影响副本一致性,尤其是强一致性。primary切换时保证一致性是一个难点。比如说primary挂了,那么secondary要顶上,可能要充当primary身份。但是也有可能只是primary和secondary之间网络割裂之类的原因,primary并不是真的挂了,然后就会出现双主,问题就大了。顺便说一下,如何发现primary异常呢?一般是使用lease。

再比如说数据同步这边,同步期间系统还是要提供服务,我们要记录下同步点。等同步点完成了,但同步过程中会有新收到的数据,所以还是一个不同步的。继续同步这些数据,然后在这期间又有新来的数据...然后,变成鸡生蛋蛋生鸡无限循环了。

总结是:我懂得太少,说的都只一些皮毛,看完请鄙视的“呵呵”一下就行了...

一致性