NoSQL生态系统——一致性RWN协议,向量时钟,gossip协议监测故障

13.5 一致性

在NoSQL中,通常有两个层次的一致性:第一种是强一致性,既集群中的所有机器状态同步保持一致。第二种是最终一致性,既可以允许短暂的数据不一致,但数据最终会保持一致。我们先来讲一下,在分布式集群中,为什么最终一致性通常是更合理的选择,然后再来讨论两种一致性的具体实现结节。

13.5.1 关于CAP理论

分区容忍性(P):集群中的某些节点在无法联系后,集群整体是否还能继续进行服务。 而CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的。所以我们只能在一致性和可用性之间进行权衡,没有NoSQL系统能同时保证这三点。 要保证数据一致性,最简单的方法是令写操作在所有数据节点上都执行成功才能返回成功。而这时如果某个结点出现故障,那么写操作就成功不了了,需要一直等到这个节点恢复。也就是说,如果要保证强一致性,那么就无法提供7×24的高可用性。 而要保证可用性的话,就意味着节点在响应请求时,不用完全考虑整个集群中的数据是否一致。只需要以自己当前的状态进行请求响应。由于并不保证写操作在所有节点都写成功,这可能会导致各个节点的数据状态不一致。 CAP理论导致了最终一致性和强一致性两种选择。当然,事实上还有其它的选择,比如在Yahoo! 的PNUTS中,采用的就是松散的一致性和弱可用性结合的方法。但是我们讨论的NoSQL系统没有类似的实现,所以我们在后续不会对其进行讨论。

13.5.2 强一致性

强一致性的保证,要求所有数据节点对同一个key值在同一时刻有同样的value值。虽然实际上可能某些节点存储的值是不一样的,但是作为一个整体,当客户端发起对某个key的数据请求时,整个集群对这个key对应的数据会达成一致。下面就举例说明这种一致性是如何实现的。 假设在我们的集群中,一个数据会被备份到N个结点。就是RWN协议。为了不至于让写操作太慢,对多个节点的写操作是并发异步进行的。在直到所有的节点都收到了新的数据后,会自动执行一个swap操作将新数据写入。这个操作是原子性和一致性的。保证了数据在所有节点有一致的值。

13.5.3 最终一致性

像Voldemort,Cassandra和Riak这些类Dynamo的系统,通常都允许用户按需要设置N,R,W三个值,即使是设置成W+R<= N也是可以的。也就是说他允许用户在强一致性和最终一致性之间*选择。而在用户选择了最终一致性,由于同一份数据在不同的节点可能存在不同值,对数据的版本控制和冲突监测就变得尤为重要。类Dynamo的系统通常都使用了一种叫vector clock(向量时钟)的版本控制机制。。。

Hinted Handoff

Cassandra、Riak和Voldemort都实现了一种叫Hinted Handoff的技术,用来保证在有节点故障后系统的写操作不受太大影响。它的过程是如果负责某个key值的某个节点宕机了,另一个节点会被选择作为其临时切换点,以临时保存在故障节点上面的写操作。这些写操作被单独保存起来,直到故障节点恢复正常,临时节点会把这些写操作重新迁移给刚刚恢复的节点。Dynamo 论文中提到一种叫“sloppy quorum”的方法,它会把通过 Hinted Handoff 写成功的临时节点也计算在成功写入数中。但是Cassandra和Voldemort并不会将临时节点也算在写入成功节点数内,如果写入操作并没有成功写在W个正式节点中,它们会返回写入失败。当然,Hinted Handoff 策略在这些系统中也有使用,不过只是用在加速节点恢复上。

Anti-Entropy

如果一个节点故障时间太长,或者是其 Hinted Handoff 临时替代节点也故障了,那么新恢复的节点就需要从其它节点中同步数据了。(译者:实际上就是要找出经过这段时间造成的数据差异,并将差异部分同步过来)。这种情况下Cassandra和Riak都实现了在Dynamo文档中提到的一种方法,叫做anti-entropy。在anti-entropy过程中,节点间通过交换Merkle Tree来找出那些不一致的部分。Merkle Tree是一个分层的hash校验机制:如果包含某个key值范围的hash值在两个数据集中不相同,那么不同点就在这个key值范围,同理,如果顶层的hash值相同,那么其负责的所有key值范围内的值都认为是相同的。这种方法的好处是,在节点恢复时,不用把所有的值都传一遍来检查哪些值是有变化的。只需要传几个hash值就能找到不一致的数据,重传这个数据即可。

Gossip

当一个分布式系统越来越大,就很难搞清集群中的每个节点的状态了。上面说到的类Dynamo 应用都采用了Dynamo文档中说到的一种古老的方法:Gossip。通过这个方法,节点间能够互相保持联系并能够检测到故障节点。其具体做法是,每隔一段时间(比如一秒),一个节点就会随便找一个曾经有过通信的节点与其交换一下其它节点的健康状态。通过这种方式,节点能够比较快速的了解到集群中哪些节点故障了,从而把这些节点负责的数据分配到其它节点去。(译者:Gossip其实是仿生学的设计,Gossip意思为流言,节点传播其它节点的健康信息,就像一个小村镇里的无聊妇人们互相说别人的闲话一样,基本上谁家谁人出什么事了,都能比较快地被所有人知道)。

13.6 写在最后的话

目前NoSQL系统来处在它的萌芽期,我们上面讨论到的很多NoSQL系统,他们的架构、设计和接口可能都会改变。本章的目的,不在于让你了解这些NoSQL系统目前是如何工作的,而在于让你理解这些系统之所以这样实现的原因。NoSQL系统把更多的设计工作留给了应用开发工作者来做。理解上面这些组件的架构,不仅能让您写出下一个NoSQL系统,更让您对现有系统应用得更好。