架构解密:从分布式到微服务(第2版)
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.2 分布式系统的一致性原理

对于分布式系统,我们必须要深刻理解和牢记一点:分布式系统的不可靠性。

可靠性指系统可以无故障地持续运行,如果一个系统在运行中意外宕机或者无法正常使用,它就是一个不可靠的系统,即使宕机和无法使用的时间很短。我们知道,分布式系统通常是由独立的服务器通过网络松散耦合组成的,网络在本质上是一个复杂的I/O系统,在通常情况下,I/O发生故障的概率和不可靠性要远远高于主机的CPU和内存,加之网络设备的引入,也增加了系统发生大面积“瘫痪”的可能性。总之,分布式系统中重要的理论和设计都是建立在分布式系统不可靠这一基础上的,因为系统不可靠,所以我们需要增加一些额外的复杂设计和功能,来确保由于分布式系统的不可靠导致系统不可用性的概率降到最低。可用性是一个计算指标,如果系统在每小时崩溃1ms,它的可用性就超过99.9999%;如果一个系统从来不崩溃,但是每年要停机两周,那么它是高度可靠的,但是可用性只有96%。

在理解了分布式系统的可靠性原理后,接下来我们开始接触分布式系统中影响深远的一个重要原理——一致性原理。分布式集群的一致性是在分布式系统里“无法绕开的一块巨石”,很多重要的分布式系统都涉及一致性问题,而目前解决此问题的几个一致性算法都非常复杂。

分布式集群中一致性问题的场景描述如下:

N个节点组成一个分布式集群,要保证所有节点都可以执行相同的命令序列,并达到一致的状态。即在所有节点都执行了相同的命令序列后,每个节点上的结果都完全相同。实际上,由于分布式系统的不可靠性,通常只要保证集群中超过半数的节点(N/2+1)正常并达到一致性即可。

前面说过,绝大多数分布式集群都采用了中心化的设计思想,上述最终一致性问题场景里的集群也遵循了这种设计,即存在一个Leader,但比较特别的是在这个场景里,集群中的其他节点都是Leader的追随者——Follower。客户端发送给Leader的所有指令,Follower都复制一遍。这听起来很简单,但在一个分布式环境下实际上很难实现。

如下图所示是分布式集群“一致性”算法的一个典型案例,来自Kafka。

当客户端向Kafka集群发起写Message请求时,集群的Leader就会先写一份数据到本地,同时向多个Follower发起远程写入请求,在这个过程中,可能会有意外情况导致某些Follower节点发生故障而无法应答(Ack)。此时,按照一致性算法,如果在集群中有超过一半以上的节点正常应答,则表明此次操作执行成功。在上图中包括Leader在内的两个节点成功,所以Leader会提交(Commit)Message数据并且返回成功应答给客户端,否则不会提交数据,此次写入请求失败。

由于一致性算法所描述的场景很有代表性,而且分布式系统中几乎每个涉及数据持久化的系统都会面临这一复杂问题,加上该算法本身的复杂性与挑战性,所以它一直是分布式领域的热点研究课题之一。早在1989年就诞生了著名的Paxos经典算法(ZooKeeper就采用了Paxos算法的“近亲兄弟”Zab算法),但由于Paxos算法非常难以理解、实现和排错,所以不断有人尝试简化这一算法,直到2013年才有了重大突破:斯坦福的Diego Ongaro、John Ousterhout以易懂性(Understandability)为目标设计了新的一致性算法——Raft,并发布了对应的论文In Search of an Understandable Consensus Algorithm,到现在已经有以十多种语言实现的Raft算法实现框架,较为出名的有以Go实现的Etcd,它的功能类似于ZooKeeper,但采用了更为主流的REST接口。

Raft算法把分布式集群的一致性问题抽象成一个特殊的状态机模型——Replicated State Machine,如下图所示。

在上述模型里,集群中的每台服务器都通过日志文件(ReplicatedLog)来持久化保存客户端发出的指令序列,供本地状态机(State Machine)顺序执行。只需保证每台服务器上日志文件的一致性,就能保证整个集群里状态机的一致性。

接下来谈谈分布式集群的最终一致性问题,其实最终一致性是降低了标准的一致性,即以数据一致性存在延时来换取数据读写的高性能。目前最终一致性基本成为越来越多的分布式系统所遵循的一个设计目标,对其场景的完整描述如下。

假设数据B被更新,则后续对数据B的读取操作得到的不一定是更新后的值,从数据B被更新到后续读取到数据B的最新值会有一段延时,这段延时又叫作不一致窗口(Inconsistency Window)。不一致窗口的最大值可以根据以下因素确定:通信延时、系统负载、复制方案涉及的副本数量等。最终一致性则保证了不一致窗口的时间是有限的,最终所有的读取操作都会返回数据B的最新值。DNS就是使用最终一致性的成功例子。

Facebook开源的分布式数据库Cassandra就是采用了最终一致性设计目标的一个知名NoSQL系统。如下所示是Cassandra的数据复制架构示意图,可以看到其与Kafka集群的数据复制流程类似。Cassandra被Digg、Twitter、360等公司大规模使用,其先进的架构和特性被众多技术精英所看好。2015年,KVM之父Avi Kivity用C++重新开发了一款兼容Cassandra的全新开源数据库——ScyllaDB,其声称每个节点每秒可处理100万TPS,拥有超过Cassandra 10多倍的吞吐量并减少了延时,一时引发轰动。