在分布式系统中,我想大家都听过很多的专业名词:强一致性,弱一致性,最终一致性,CAP理论,FLP不可能性,Paxos算法,2阶段提交…

我们也听说过很多设计系统的策略:比如BASE,缓存,实时计算/离线计算、异步,重写轻读/重读轻写…

我们也听说过很多的系统衡量指标:性能、可靠性、高可用…

而所有这些,都绕不开一个话题:“一致性”。而本篇,则试图以自己的理解,来探讨一下这个最常见、最基本、也最容易迷糊的、看似是常识却暗含深刻哲理的问题。

因为在我看来,从哲学层面,去看透这个问题,很有利于各种计算机理论的理解。

现实世界的3个例子

皇帝驾崩

我们知道在古代,传递信息的交通工具通常是马。当现在皇帝驾崩、新皇登基的时候,这个消息要“马不停蹄”的送往各个州、县。

马从京城到最南边的海南,可能要好几个月。那在这过渡期的几个月里面,有的州县认为旧的皇帝还在执政,有的州县已经收到新皇帝登基的通知。

假如新皇版本了一个新的法令,那意味着在这个时期,旧的州县还在执行旧的法令,新的州县在执行新的法令。

这就是现实世界中的“不一致性“的例子。这个例子反映了一个什么常识性的哲理呢?

信息的传递需要“时间”。对,是“时间”!!!

淘宝秒杀

第2个例子,你正在写代码,你的同事告诉你淘宝现在有一个促销活动,秒杀免费获取一件大衣。

你收到这个消息,立马打开淘宝网页,等打开,显示活动已结束。

请问:是因为你同事告诉了一个“假的”,“不正确”的消息吗?可以说是,也可以说不是。

这个问题背后,反映了另外一个常识性的哲理:

世界一直是变化的,当你把世界的某1刻的“状态”传给另外一个人、或者另外一个地方的时候,此时世界的“状态”可能已经改变。而对方,收到的就是一个“过时”的消息。

这也就是人们常说的那句话:

“人不可能2次踏进同1条河流!!!”

将军问题

第3个例子,我想很多人都已经听说过,这个在此处就不再阐述。

对应到计算机里面,一个通俗的描述就是:客户端给服务器发了条消息,网络失败。请问,此时服务器到底是收到了这条消息呢?还是没有收到?

答案是:不确定!

这些算法通常以其弹性t作为特征,t表示算法可以应付的错误。很多经典算法问题只有在 n ≥ 3t+1 时才有解

总结

上面3个例子,反映了3个基本常识:

(1)信息的传播,需要“时间”。有“时间”,就有“延迟”;有“延迟”,就有不一致。

(2)信息所反映的“世界”一直在变,信息在传播,世界在变化。2者是并行发生的,也就意味着信息的“过时”。

(3)传递信息的通道,是不可靠的。

计算机世界 – 随处可见的“不一致性”

现在就让我们把上面实现世界的3个例子,对应到计算机世界中:

(1)信息的传播需要“时间”

例子1:Kafka中,zk选取出Controller。当旧的Controller挂了,新的Controller被选举出来。

这个过程可能很快,在计算机世界的度量里面,是毫秒、或者微秒级别,但即使再快,也需要“时间”,不是0。

那么在这个极端的时间内,其他的broker们,有的接受到的是旧Controller发来的消息;有的接受的是新Controller发来的消息。

或者说,旧的Controller挂了,其发出去的消息,还在网络上游荡呢,此时,新的Controller上台。

例子2: Kafka中,每个broker都维护了一个全局的Metadata。当topic或者partition变化时,所有broker的这个Metadata都需要更新。

很显然,这个过程也需要“时间”,网络还会超时,失败。最终结果必然是:所有broker维护的Metadata,是不一致的。

(2)信息所反映的“世界”在变化

例子:拿Kafka为例,客户端询问集群中的1个broker,我要发消息的(topic,partition)对应的机器列表是多少?broker回答:机器列表是(b1,b2,b3)。

这个结果是正确的,当前是正确的!但就在客户端拿到这个正确的消息,正要选择b1往外发送的时候,这个时候,b1挂了,即“世界”变了。此时发送失败。

(3)2将军问题。

这个在计算机科学中,反复提及,此处就不再详述。

终极结论

上面说了现实世界的例子,也说了计算机世界对应的例子,最终是想说明一个什么道理呢?

理论上,世界不存在“强一致性”

信息的传输需要时间,世界本身也一直在变化。从微观粒度去看,不管计算机的计算时间有多么短:毫秒,微妙,纳秒,时间总是可以细分到一个更细粒度。

在这个更细粒度来看,世界永远是不一致的!!!

那我们通常说的“强一致性”,是怎么得来的呢? 其实是从观察者的角度!!!

就拿单机版的Mysql转账,一个账号扣钱、一个账号加钱来说,必定存在一个短暂的时间窗口:在这个时间窗口内,1个账号的钱减了,但另1个账号的钱,没有加上。

只不过这个时间窗口很短,并且对外屏蔽了这个内部的”不一致性“,让客户端看来,是一致的。所以,在这里,客户端看到的是”强一致性“,但从内部来看,你可以认为是”最终一致性“。

这还只是单机版,如果换成集群,网络时延很大,这个问题就会放大,客户端可能就会明显感知到”不一致性“的存在。

再拿转账举个反向例子:

假如跨行转账的时间是2个小时,对于系统来说,这是“最终一致性”

但对于客户,假设这个客户很忙,好几天才查询一次账户,对于他来说,他看到的永远都是一致的,就是“强一致性”。

世界上不存在100%可靠的系统

我们经常会听到某个系统夸耀自己多么多么可靠,多么多么高可用。但从上面的分析,你可以看出,世界上不存在100%可靠的系统。

机器会宕机h,网络会断,不会断也会有延迟。有延迟,就有不一致。

所以你经常会听到,某个系统的可靠性,是几个9。

99%
99.9%
99.999%

但无论几个9,永远不可能100%。

没有永动机存在

说了这么多,就是想阐述一个最最基本的东西:在计算机世界,追求绝对的“一致性”,就好比在物理学中,追求永动机一样。

分布式系统之魅力

通过上面的分析,我们是不是彻底绝望了?我们发现系统中到处都是“坑”,到处都是“漏洞”,到处都是不可靠。那我们还如何设计系统呢?

而个人认为,分布式系统的魅力就在于:你如何在一个不可靠的、时刻都在变化的网络和硬件环境之上,构建一个相对可靠的软件系统??

很显然,因为没有绝对正确答案,才需要各种权衡、妥协。因为各种妥协、折中,才有了CAP理论, 有了BASE原则,有了Paxos…

下面我们就来看看,我们既然没办法彻底解决问题,那面对不一致性,我们经常都会做哪些策略呢?

重试

拿Kafka或者RocketMQ来讲,Metadata的信息不一致,又如何呢? 多个NameServer的状态,不一致,又如何呢?

不管这个不一致。客户端拿到Metadata之后,发送失败,重试!!!

多个局部的不可靠换来整体的可靠

对1个系统来讲,某个局部是可能出问题。但所有局部,同时出问题,这个概率就大大降低了。

比如Kakfa,多备份。一个备份的磁盘是可能挂,但多个备份的磁盘,同时挂,这个概率就低多了。

如果真的同时挂,怎么办呢?

你就认命吧!!如果老是遇到这种问题,你可以去买彩票了。

层层校验

数据不是有可能不一致吗?状态不是有可能随时会过时吗?

既然没有办法保证全局的、完全的一致,那就在系统的各个局部,多做各种错误校验,以此来修补全局的不一致性。

其他

上面只是列举了面对这个随处可见的“不一致性”的一些常见思路,具体到不同的系统,不管是业务系统、还是底层架构,侧重点不一样,会有各式各样的针对特定场景的办法,那就是“八仙过海、各显神通”了。