上一集:MartinFowler告诉你大数据架构师必备的NoSQL技能-版本戳(上)
上集我们说了在single-server以及在“主从复制模型”(master-slave replication)下的生成版本戳的特技。今天我们主要说的是在“对等分布模型”(peer-to-peer distribution model)的情况下如何生成版本戳!
Version Stamps on Multiple Nodes 在多节点下如何生成版本戳?
当你只有一个“数据的权威源”(authoritative source for data)的时候,比如采用单服务器(single server)或者“主从复制模型”(master-slave replication),这个时候,上一文中提到的那些基本的版本戳技术就可以搞定了,works well!在那种情况下版本戳是被一个主节点(master)负责生成,任何其他的从节点(slaves)只需要使用主节点生成的版本戳就可以了。然而!这样的做法当遇到“对等分布模型”(peer-to-peer distribution model)时,就不得不进行改进了,因为“对等分布模型”再也不会有一个地方统一设置版本戳了。
如果向两个节点要同一份数据,那么你很可能会得到两份不同的答案。如果发生了这种情况,我们的应对策略就是根据导致这种差异的发生的原因作出变化。比如说有可能是这个更新已经到达了一个节点,而另外一个节点还没收到更新。这种情况下你可以接受最新的那份数据。(这里假定你可以分辨出哪一份是最新的)。还有一个可能就是你遇到了更新不一致的问题,这样情况下,你就需要采用措施处理这种更新不一致的情况。在这种情况下,你仅仅使用GUID或者etag标签是没什么鸟用的,因为它们并不会告诉你两份数据的关系。
最简单的版本戳就是计数器(counter)(ps:上一文中我们提到过)。每次一个节点更新了数据,那么就将它加1,然后把这个最新的值放入版本戳中。我们现在假设你的某个主节点有两个副本,我们用“蓝色”和“绿色”来区分这两个从节点。如果在蓝色节点所给出的应答数据中,版本戳是4,而绿色节点则是6,那么你便知道绿色的数据要更新一些。
如果有多个主节点的话,那么就得好好的再思考一下了(need something fancier)。一种做法,就是采用像“分布式版本控制系统”(distributed version control systems)那样,就是确保让所有的节点都持有一份版本戳的记录(a history of version stamps)。那样的话,你就可以 知道蓝色节点给的数据是不是绿色数据的祖先(其实就是绿色数据是不是比较新的那一份的意思)。要想实现这一点,要么要求客户端必须保存“版本戳记录”,要么要求服务器节点持有版本戳记录并把它放入应答数据中,然后返给客户端。使用这种方法也可以侦测出不一致的问题:如果两份应答数据中的版本戳都无法在对方的“版本戳记录”中找到,那么就可以认定发生了“不一致”的问题。尽管在版本控制系统中都会保存这样的版本戳记录,但在NoSQL数据库中却没有这样的做法。(ps:也许现在已经有了)
有一种简单但有可能出问题的做法就是使用“时间戳”。这个做法的主要问题就在于通常我们很难确保所有的节点上的时间都是一致的。特别是当更新过于频繁的时候。假设某个节点的时钟不同步了,就会导致各种麻烦。另外就是,你无法通过时间戳侦测出类似“写写冲突”(write- write conflicts)的情况,所以说这种做法只有在“单主节点”(single-master)的情况下才没问题——而且在“单主节点”下,使用计数器会更好一点。
“对等分布式的NoSQL数据库系统”(peer-to-peer NoSQL systems)最常使用的做法就是使用一种特定的“组合方式的版本戳”,这里我们叫做vector stamp。(之后我们我们就统一叫“组合版本戳”)。本质上,“组合版本戳”就是一个计数器的集合,长得像数组一样,每个计数器对应一个节点。比如一个三个节点(blue,green,black)的“组合版本戳”可能长这个样子:[blue: 43, green: 54, black: 12]。每次某个节点做了一次内部的更新后,这个节点就会更新自己的计数器,所以现在如果绿色节点做了一次内部更新的话,那么绿色节点就会把自己的那个数组改成这个样子:[blue: 43, green: 55, black: 12]。不论何时两个节点进行通信,他们都会同步他们的“组合版本戳”。具体怎么个同步法,就有很多种不同的方法了。这里我们造了“组合版本戳”(vector stamp)的术语;你也许还会听到vector clocks以及version vectors—其实这些都是我们今天说的“组合版本戳”的具体的特定的一些形式,只不过他们的同步方式不同。
通过使用这种组合方式,你就可以分辨出一个版本戳是不是比另一个要新一些。因为那个新的戳要大于或等于那个旧的戳。所以像[blue: 1, green: 2, black: 5]就要比[blue:1, green: 1, black 5]新,因为他们中的一个计数器的值要大于另外一个。如果两个戳都有一个计数器的值要大于另外一个,像这样:[blue: 1, green: 2, black: 5] 和[blue: 2, green: 1, black: 5],那么这就是一个“写写冲突”(write-write conflict)。
还有就是这个数组中可能会少了某些值,在这种情况下,我就给它补“0”。比如[blue: 6, black: 2]这个,我们就给缺了的值补0,结果是这样:[blue: 6, green: 0, black: 2]。 这样的话,我们就可以轻松的增加新的节点了,而不需要放弃现存的“组合版本戳”。
“组合版本戳”是一个很有价值的工具,对于侦测不一致问题。但“组合版本戳”也仅限于“发现”问题,它并不会解决问题。解决冲突必须依赖于领域内的知识。就是具体问题具体分析的意思。这就像前面说到的“一致性”和“延迟”之间的权衡一样,都得依赖领域内的知识,具体问题具体分析。如果你要求强一致性,那么出现网络分区的时候,你的整个系统就不能用了;如果你追求的性能,你就不得不侦测并处理这些不一致。