当我们说一致性,我们到底在说什么

每次和别人说起微服务,就注定逃不掉要被问一句:“你把服务都拆分了,那怎么保证一致性呢?”一般我都会回答:“通过最终一致性来保证。”问的人对这个答案并不满意:“如果一项业务对一致性要求比较高,是不是就不能用微服务了?”对于这样的挑战,我会以开放式的答案来回答:“没有完美的架构,如果你希望享受微服务带来的好处,就要接受它的弊端,要根据你的业务,权衡利弊之后再做选择。”看起来无懈可击的回答,其实是没什么营养的答案。这个一致性的问题一直如鲠在喉,不把它解决掉,在做微服务架构设计时总是心中不安。

直到有一天,我在Reactive Design Patterns一书中,读到这段话[1]

Taking a step back from the abstractions of programming into the physical world, Einstein’s theory of relativity has the astonishing property that some events cannot be ordered with respect to each other: if even a ray of light cannot travel from the location of the first event to the location of the second before that event happens, then the observed order of the two events depends on how fast an observer moves relative to those locations.

这让我重新从本质上思考一致性问题。

一致性模型

我们先来看看一致性是如何定义的。

什么是一致性模型

一致性模型的简单解释:

  • 数据X有两个副本,分别保存在节点M和节点N上;
  • 客户端A修改了节点N上的数据X;
  • 一段时间后,客户端B读取在M节点上的数据X;

一致性模型就是要确定客户端B是否能读取到客户端A做的修改。

严格一致性

严格一致性(Strict Consistency)是最强的一致性模型,它要求在读取一块内存数据时,读取操作的返回值一定是最近一次对该内存块进行写操作的结果。这个模型看起来非常合理,就好像一个正常人的膝盖被小锤子敲了一下,就应该产生膝跳反射。如果这个人没有立即产生膝跳,而是过了两天才有反应,那一定是这个人的反射弧有问题。 事实上,对于单处理器系统,这个模型确实可以完美适配;但对于分布式系统(包括多处理器系统),要满足这个模型的要求,几乎是不可能的。

于是就有人提出相对弱一点的一致性模型,这些模型包括:线性一致性,原子一致性,顺序一致性,缓存一致性,静态一致性,处理器一致性,PRAM一致性,释放一致性,因果一致性,TSO一致性,PSO一致性,弱序一致性,本地一致性,连续一致性等等,当然,也包括我们要详细介绍的最终一致性。

为什么严格一致性不可能做到

维基百科上,对于严格一致性在分布式系统中难以实现举了个例子:在有两个处理器A和B的情况下,处理器A在某个时刻T0写入一个值,处理器B在T0之后的某个时刻T1读取这个值。处理器B是否能够读取到新的值,取决于处理器A所在光锥(light cone)在修改数据这个操作发出的光线,是否在T1时刻之前与处理器B的时间轴相交。

是不是有一种“每个字我都认识,但是放在一起我就不知道它在说什么”的感觉?

我们用一个更通俗的例子[2]来解释下:

在遥远的太空,小明穿着闪红色的太空服漂浮在黑暗的宇宙中,从他的角度,是完全静止的浮在宇宙之中;这时他看到远处有一点绿光,想他越来越靠近,最后他发现是另一位太空流浪者小红,两人在太空中相遇,又分开,互相挥了挥手。而从小红的角度看,她是静止的,而小明是从远处飘过来,两人挥了挥手,相遇,又分开。相对论的一个结论是,运动的物体,其时间会变慢。也就是说,在小明看来,小红是运动的,所以小红的时间要慢一点;在小红看来,小明是运动的,所以小明的时间要慢一点。如果两人身上各自带着一个数字钟,在两人相遇是,把他们的钟拨到12:00,那么在两人分开后,两人看到对方的时间,都会比自己的慢。也就是说,在小明看来,他的钟先到12:01;而在小红看来,她的钟先到12:01。至于慢多少,和相对运动的速度有关,如果两人的相对速度达到光速的99.5%,那么小明的钟到达12:01:00时,他看到小红的钟刚刚走过12:00:06;反之,小红的钟到达12:01:00时,她看到小明的钟刚刚走到12:00:06;如果两人的相对速度在16km/h,这个差别就只有一千万亿分之一秒这个数量级,这就是为什么在日常生活中,我们觉得大家的时间都是一样的。

相对论告诉我们,每个人,甚至每个粒子,其经历的时间都和其他人和粒子不一样,如果在同一个参考系内,没有相对运动,时间有可能是同步的,但如果存在相对运动,则各自经历的时间就存在差异。在我们生活的尺度下,我们根本感觉不到,但并不代表这个差异不存在。

回到两个处理器A和B的例子,设想一个极端情况,处理器A在修改一个值之后的一千万亿分之一秒后,处理器B尝试读取这个数值,那么在处理器A看来,处理器B是在它修改值之后才读取数据;而在处理器B看来,它是在处理器A修改数据之前读取的数据,那么到底B读取的数据应该是多少呢?

这种极端情况有可能出现么?我们的日常生活当中确实不会达到那么高的精度和速度。不过,如果处理器的频率达到100GHz以上,其每一条指令的周期就只有一千万亿分之一秒。虽然目前的处理器技术距离这个频率还非常遥远,但未来,谁知道呢?

正是由于上述的原因,绝对的严格一致性,是不可能做到的。

微服务系统的一致性要求

既然严格一致性不能达到,我们就要从那么多稍弱一点的一致性模型中,挑选适合我们应用场景的模型。

对于面向最终用户的互联网应用,以及面向企业用户的企业应用,其服务对象都是人。人对于一项服务最敏感的是能否满足他的需求。比如一个人在ATM取款机取款时,他的需求是能够立即拿到钱,而对于他银行账户里的数字是否变更、变更的数字是否正确,他并不要求马上知道。所以,在出现网络故障时,如果ATM无法提供服务,用户一定会投诉;而如果ATM依然允许用户取钱,只是会限制取钱的上限,而在恢复通信时,再更新用户账户的余额,用户就不会觉得有什么问题。

因此,在设计面向最终用户的应用,高可用性,或者说响应能力(responsiveness)是首要考虑的。

最终一致性

最终一致性(Eventual consistency)是一个为了高可用性提出的一致性模型,该模型要求,当没有新的修改应用在给定的数据项之上的前提下,最终所有对这块数据的访问,都会返回最后一次修改的值。达到最终一致状态的系统,被称为收敛系统(converged systen),或者是达到副本收敛(replica convergence)。最终一致性提供了一个弱保证:在系统达到收敛之前,可能会返回任意值。

假如在一个实现最终一致性的分布式系统中,同一个数据的不同副本出现了不同的值,在系统收敛后,这个数据的值该如何确定呢?最终一致性将协调差异的过程分成两部分:

  1. 在服务器节点之间叫唤数据的版本,或者修改记录,称为逆熵(anti-entropy)
  2. 当并发修改出现时,选择一个适当的最终状态,称为调谐(reconsiliation)

上面这两个步骤给系统设计者留下了两个决策点:

  1. 当并发修改出现时,如何确定适当的最终状态?通常这是根据应用系统的特点来确定。最简单的做法是以最后一次修改为准,而这个“最后”,又依赖于两个节点之间的时间是否同步,于是问题又回到了相对论带给我们的困境;更合理的做法是在业务上对于出现冲突的数据该怎么处理给出合理的设计;
  2. 调谐操作应该在什么时候进行?可以在读取数据发现冲突时进行,也可以在写入数据发生冲突时进行,也可以周期进行,具体该如何选择,还是取决于具体的业务应用。

为了解决最终一致性系统的弱保证问题,也有人提出了强最终一致性(Strong Eventual Consistency,SEC)系统,它保证了如果两个节点收到的修改操作是相同的(可以顺序不一致),那么两个系统最终状态一定是一致的。更进一步,如果系统是单调的(monotonic),也就是数据只增不减,或者只减不增,应用系统就绝不会出现数据回滚。无冲突副本数据类型(conflict-free replicated data type, CRDT)就是用来实现SEC的,这是另一个复杂的话题,我们留待以后再讲。

现实中的最终一致性

用一个星巴克咖啡的例子来说明最终一致性[^3]:

如果你在工作日的午休时间去星巴克点咖啡,可能在你前面已经有十几个人在排队,每排到一个顾客,营业员会问清楚顾客要什么咖啡,什么型号的杯子,有什么特殊需求,然后会拿一个对应的杯子,在上面用笔做好记号,放在一边。然后营业员会向点单的顾客收费,买好单的顾客可以站在一边等待自己的咖啡做好。另一边,咖啡师会从放杯子的台子上拿杯子,拿的顺序和放的顺序并不一定一样,因为咖啡师可能会将同样的咖啡放在同一批做出来。这就是为什么有时候你明明排在后面,却可以先拿到自己的咖啡。

我们要设计的软件系统,和星巴克的点单流程一样,要面临可能到来的大量并发用户;杯子就是存储空间;而用户最终拿到想要的咖啡,就是系统收敛后的状态。

我们来看看星巴克是如何做到调谐,也就是当数据冲突时,他们会如何处理:

  1. 如果在付款时你发现没带钱包,不得不放弃购买,这时如果咖啡还没有做,营业员会从台子上把对应的杯子拿掉;如果已经做了,就扔掉;
  2. 如果咖啡师看错了杯子上的标记,将摩卡做成了拿铁,他们会给你重做一杯;
  3. 如果你点了焦糖玛奇朵,咖啡师发现奶油没有了,无法做出来,他们会把钱退给你。

最终,如果一切顺利,你可以喝到你想要的咖啡,而如果出现问题,除了浪费一点时间,你并没有损失什么。

总结

虽然严格一致性更符合我们的直觉,但相对论告诉我们,所谓的一致性,只是相对的,不一致才是绝对的,我们要根据要设计的应用系统来决定采用哪一种一致性模型。

对于面向最终用户的业务应用系统,系统及时响应能力是首要目标,所以面向高可用性设计的最终一致性更适合互联网应用或者企业应用。

最终一致性要达到收敛,需要经过逆熵和调谐两个步骤,至于调谐过程中遇到冲突如何处理,最好的做法是在业务上给出明确的方案,星巴克处理很多顾客排队时的做法非常值得借鉴。

[^3]:这个例子借鉴自这篇文章


  1. Notes From: Roland Kuhn. “Reactive Design Patterns MEAP V10.” iBooks.

  2. 对于该例子的更详细解释,请参见《宇宙的琴弦》第二章 - 空间、时间和量子的困境