前言
上一篇分享了共识算法,从 Paxos 共识算法家族,到近代最常用的共识算法 Raft,为了达成强一致性(Strong Consistency),我们在写入的性能上做了很大的牺牲。
不过不是每种数据都有强一致性的需求,有些数据可以接受一段时间内的不一致性,只要能达成最终一致性(Eventually Consistency)即可,像是第一篇提到过的点赞数的例子,同一篇帖子对不同使用者显示的点赞数暂时不一致并没有影响,只要最后数据库同步后的点赞数一致即可;另一个更著名的例子是我们每天都会用到的域名服务(Domain Name Service, DNS),通过域名服务,电脑可以找到某个网站的网址(IP),但网站随时都有变更 IP 的需求,域名服务商并不会为了强一致性就禁止某个网站更改 IP,不过域名服务商要确保经过一段时间后,使用者来查询网站位置时,可以提供最新的 IP。
这一篇会跟大家分享达成最终一致性的方法 --- Gossip 协议 ,并且说明如果系统无法达成强一致性,要如何通过 Quorum NWR 确保客户端看到的数据是一致的。
Gossip 协议
Gossip 让人下意识想到"八卦",没错!Gossip 协议的宗旨就是想让信息像流言蜚语一样散播在整个系统中。
Gossip 协议源自于全录(Xerox)公司的研究机构帕罗奥多研究中心(Palo Alto Research Center, PARC),这个地方可以说是电脑领域的圣地,我们现在用的图形界面(GUI)及鼠标最早都来自这个研究中心。1987 年,PARC 的研究员 Alan Demers 等人在 ACM 发表《Epidemic Algorithms for Replicated Database Maintenance》,因此 Gossip Protocol 又可以被称为 Epidemic Protocol。
由于 Gossip 协议提出的早,因此不管是 2000 年初提出的系统,像是 Amazon S3 、 Cassandra ,或是近十年的系统都有实现,像是 IPFS 、 InfluxDB 等,可以说 Gossip 协议是经过时间与不同系统验证过的有效方法。
Gossip 协议主要包含三个部分,Direct Mail(直接邮寄)、Rumor Mongering(谣言传播)及 Anti-Entropy(反熵),接下来就让我们分别看一下这三个部分。
Direct Mail
Direct Mail 是三个方法中最容易理解的,当一个节点收到客户端的新信息后,就把这个新信息传递给系统内的每个节点。
Direct Mail 虽然快速有效率,但想单纯靠 Direct Mail 达成最终一致性的是不实际的 ,因为信息传递的过程中,可能会面临接收节点断线、发送消息节点挂掉或是网络出问题导致信息无法传达,虽然论文有提到发送消息节点在确定其他节点都收到信息前,要把信息存在队列(Queue)里,不过这样的设计又会面临另一个问题,队列如果满了,最新的信息就无法保证传递给每个节点了。此外节点收到新信息就要传递给所有节点,代表任一个节点都要知道系统内的所有节点位置,这在节点快速变动的系统中不容易实现。
Rumor Mongering
Rumor Mongering 与 Direct Mail 相似,差别在于 Rumor Mongering 不用把新信息传给所有节点,而是任一节点收到信息后就变成活跃状态(Active),并将此信息传递给随机挑选的节点。
从下图中可以看到,总共有 40 个节点,每个节点收到信息后传递给其他 4 个节点,理想上,在不到 3 个回合内就可以将消息传递给整个系统,不过我们也观察到,因为节点要传递信息时,并不会知道哪些节点收过信息了,所以导致系统中有许多不需要的传输,另外如果消息传递给整个系统的次数过多,会导致系统有严重的消息延迟。
Anti-Entropy
反熵乍听之下是个摸不着头绪的词汇,原因是熵(Entropy)这个词不是电脑科学原有的词汇,而是从热力学引入的,熵代表着系统的无序程度或混乱度,系统越乱代表越不一致,因此反熵的意思就是要降低混乱度。
Anti-Entropy 同步数据的方式,是通过两个节点比对数据的异同,并修复节点缺失的数据,论文中提供三种方式来达成 Anti-Entropy:
- 推(Push):将自己的数据推送给其他节点,让其他节点进行比对,并更新其他节点的数据。
- 拉(Pull):向其他节点拉取数据进行比较,并修正自己的数据。
- 推拉(Push/Pull):将上述两步结合起来,同时修正两个节点的数据。
虽然论文中给出三种修正数据的方式,但如果数据比对的环节设计的不好,将会严重影响系统达成一致性的性能,像是两个数据库只做核对和比较(Checksum),还是逐条比较每笔数据,会导致在时间上有非常大的差异。
Anti-Entropy 是达成最终一致性的最后手段 ,因为会比较整个数据库的异同,不像 Direct Mail 或是 Rumor Mongering 只传递最新的消息,不过 Anti-Entropy 却也是最耗资源的方法 ,因此在实际上线的系统中,要尽量减少做 Anti-Entropy 的次数。
Gossip Protocol 三个方法中,Direct Message 是性能最好的,因为 Direct Message 的信息量最低,缺点是无法处理节点快速变动的情况,也无法只实现 Direct Message 就说系统能满足最终一致性;Rumor Mongering 在节点快速变动的环境依然可以运行,但缺点是系统会有很多重复的信息,且有信息延迟的问题;Anti-Entropy 虽然可以完整的修复数据不一致的情况,却也是性能最差的。在实际场景上,如果有新的节点加入系统,通常会先使用 Anti Entropy 的 Pull 跟其他节点同步该时间点以前的所有数据,新数据则通过 Direct Message 或是 Rumor Mongering 接收。以上是 Gossip Protocol,接下来会介绍 Quorum NWR。
Quorum NWR
客户端视角的一致性
在现实场景中,有些业务要能快速写入,同时又想满足强一致性,这样的需求,单从服务端来看几乎是不可能的,不过有没有可能把一致性的问题从服务端的视角抽离出来?或者更进一步地说从写入的视角抽离出来,让我们同时从写入与读取两个角度来解决一致性的问题?有的!Quorum NWR 就是从客户端读取的角度与系统端写入的角度并行,来解决快速写入又能读取一致的方法!
NWR 意思
N 代表机器的数量,W 代表要写入多少台机器才算写入成功,R 则代表要从多少台机器读取。接下来则是简单的数学式, 当 W + R > N 代表任一次的读取一定会跟最新的写入有重叠 ,这样说可能有点抽象,来看个例子。
假设现在有 3 台机器(N),系统设定要写入 2 台(W)才算成功,任一次的读取也要从 2 台机器读取(R),初始状态如下图,三台机器目前都是一样的状态,所以不管从哪 2 台机器读取,都会收到一样的数据。
接下来,客户端发起一个写入,要把 A 设定成 6,由于 W 设定为 2,所以要写入 2 台才算成功,假设成功写入 Node 1 & 2,如下图。
此时客户端要在读取 A 时,由于系统设定 R 为 2,所以从 3 个节点中的任两个读取都可以拿到最新的数据,如下图,红色线表示从 Node 1 & 2 读取,蓝色线表示从 Node 2 & 3 读取。
数据合并
数据不一致时,该以哪个节点的数据为主?
Quorum NWR 理解起来不难,实现也算容易,但却有个一定要解决的问题「如何合并数据?」。从上面的例子可以发现,不仅是系统端为了最终一致性进行 Gossip Protocol 时,要处理数据合并的问题,客户端从多个节点读取数据时,也要处理数据合并的问题。虽然我们可以简单地以「最后写入」为准,但有时还是可能碰到时间戳重复的问题,另外有些更复杂的业务可能要保证原子性(Atomic),可能就需要更复杂的数据合并流程。
在实现上,不管是 W 或 R,个别超过 N 都没有意义,数据存在多台机器上是为了预防机器损坏导致的数据遗失,但同一台机器存了多个同样的副本,并不会提升系统的可用性,因为那台机器损坏时,那些副本都会遗失。
最后虽然 Quorum NWR 提供了客户端一致性的便利,像是当 W = 1, R = N 时,有最高的写入性能,但却可能随时都会发生数据遗失的情况,因为任何一台机器的损坏,都可能造成数据永远遗失在系统中,所以建议还是让写入的机器数至少在 3 或是 N/2 以上。
总结
在第一篇,我们提到了 CAP 三元取舍的问题,在有分区(Partition)的情况下,系统无法同时满足一致性(Consistency)与可用性(Availability),而在互联网中,分区是系统一定要容忍的状况,所以我们只剩下 CP 模型以及 AP 模型可以选择,CP 模型可以提供强一致性,但同时也降低了系统写入的性能,AP 模型解放了单机写入的性能,但却无法保证数据是一致的。相比于 CP 模型强一致性的算法,AP 模型的算法并没有太多细节与步骤,反而比较像是提供了最终一致性的大方向,把细节留给不同业务的开发人员,以 Gossip Protocol 来说,大方向是 Direct Message、Rumor Mongering 以及 Anti Entropy,细节至少包含了数据比对、数据合并、节点发现(Peer Discovery)以及如何让整个集群通过 Anti Entropy 达成一致性。
Quorum NWR 让大家通过不同的视角来看待数据一致性的问题,同时还提供了数据一致性上的弹性,在第一篇文章,我有跟大家分享即使在同一个业务中,不是每个数据都有强一致性的需求,有些数据只要最终一致即可,而 Quorum NWR 就是提供了这个弹性,我们可以对不同的数据设定不同的 NWR,有强一致需求的满足 W + R > N,没有强一致需求的则可以 W + R ≤ N,通过写入(W)跟读取(R)的调控,让整个系统更有弹性!
希望大家下次遇到分布式的问题时,可以先通过 CAP 定理去拆解该问题要先满足 CP 还是 AP,如果要满足 CP,则要接着看系统会不会有拜占庭问题,并且如何在需要共识的情况下,提升系统的写入性能;如果要满足 AP,则要设计达成最终一致的方法。最后,分布式系统设计没有银弹,只能依据问题去设计适合的系统。