EagleBear2002 的博客

这里必须根绝一切犹豫,这里任何怯懦都无济于事

《NoSQL 精粹》第六章-版本戳

商业事务与系统事务

商业活动(Business Activity):例如,用户浏览产品目录,选中了一瓶价格很实惠的 Talisker 威士忌,填入信用卡信息,然后确认订单。

需要确保最终一致性,但是出于时间、交互的考虑,无法使用事务加以实现

  1. 如使用事务实现,必须锁住数据库中各个元素。而长时间锁定元素是不现实的。
  2. 因此,应用程序通常只在处理完用户交互操作之后才开始系统事务,这样的话,锁定时间就比较短了。
  3. 然而当需要计算和决策的时候,数据有可能已经改动了。价格表上 Talisker 威士忌的售价也许已经变了,或是某人可能会修改客户的地址,从而导致运费改变

条件更新(conditional update),客户端执行操作时,将重新读取商业活动所依赖的信息,并检测该信息在首次读取之后是否一直没有变动,若一直未变,则将其展示给用户。

通过保证数据库中的记录都有某种形式的版本戳(version stamp)实现乐观离线锁(Optimistic Offline Lock)。

  1. 版本戳是一个字段,每当记录中的底层数据改变时,其值也随之改变。
  2. 读取数据时可以记下版本戳,这样的话,在写入数据之前,就可以先检查一下数据版本是否已经变了。

使用版本戳避免更新冲突 ,维护会话一致性

CAS 操作(compare-and-set 操作):

  1. 既可以由数据库提供
  2. 也可以由开发者负责检测的执行

构建版本戳

  1. 使用计数器:每当资源更新时,就把它的值加 1。
    1. 根据它的值很容易就能看出哪个版本比较新。需要服务器来生成该值,并且要有一个主节点来保证不同版本的计数器值不会重复。
  2. 使用 GUID(全局唯一标识符),也就是一个值很大且保证唯一的随机数:
    1. 可以将日期、硬件信息,以及其他一些随机出现的资源组合起来以构建此值。
    2. 好处是任何人都可以生成,不用担心重复。
    3. 缺点则是数值比较大,而且无法通过直接比较来判断版本的新旧。
  3. 根据资源内容生成哈希码:只要哈希键足够大,那么内容哈希码(content hash)就可以像 GUID 那样全局唯一,而且任何人都可以来生成它。
    1. 好处在于,哈希码的内容是确定的:只要资源数据相同,那么任何节点生成的内容哈希码都是一样的。
    2. 但是,哈希码与 GUID 一样,都无法通过直接比较来看出版本新旧,而且比较冗长。
  4. 使用上一次更新时的时间戳(timestamp):
    1. 与计数器一样,时间戳也相当短小,而且可以直接通过比较其数值来确定版本先后
    2. 时间戳不需要由主节点来生成,可以由多台时钟同步的计算机生成。如果某个节点的时钟出错了,那么可能会导致各种数据损毁(data corruption)象。
    3. 若时间戳精确度过低,则可能重复:假如每毫秒都要更新很多次的话,那么将时间戳的精确度设为毫秒是不够的。

可以把几种时间戳生成方案的优点融合起来,同时使用多种手法创建出一个复合版本戳(composite stamp)。

在 CouchDB 创建版本戳时,使用了计数器与内容哈希码。

  1. 大部分情况下,只要比较版本戳就可以判定两个版本的新旧
  2. 万一碰到两个节点同时更新数据的情况,因为两个版本戳的计数器相同,而内容哈希码却不同,立刻就能发现冲突。

在多节点环境中生成版本戳

主从式复制模型中的版本戳

在主从式复制模型中,只有一个权威数据源(authoritative source for data),使用基本的版本戳生成方案

由主节点负责生成版本戳,而从节点必须使用主节点的版本戳。

  1. 以计数器为例,节点每次更新数据时,都将它加 1,并把其值放人版本戳中。
  2. 假设某主节点有两个副本,分别是蓝色节点和绿色节点。
  3. 如果在蓝色节点所给出的应答数据中,版本戳为 4,而绿色节点的版本戳是 6,那么绿色节点上的数据就比较新。

对等式分布模型中的版本戳

在对等式分布模型中,没有统一设置版本戳的节点

如果向两个节点索要同一份数据,那么有可能获得不同的答案

  1. 有可能是更新操作已经通知给其中一个节点了,而另外一个节点尚未收到通知,可以选用最新的数据
  2. 发生了更新不一致现象

确保所有节点都有一份版本戳记录(version stamp history)。从而判断出蓝色节点给出的应答数据是不是绿色节点所给数据的祖先 。

  1. 要么让客户端保存版本戳记录,要么由服务器节点来维护此记录,并且把它放在应答数据中,传给客户端。
  2. 用版本戳记录可以检测出数据不一致现象;如果两份应答数据中的版本戳都无法在对方的版本戳记录中找到,那么就可以判定发生了不一致问题。

使用时间戳

  1. 很难确保所有节点的时间都一致
  2. 无法检测写入冲突

数组式版本戳

数组式版本戳(vector stamp)由一系列计数器组成,每个计数器都代表一个节点。

假设有三个节点(分别记为蓝色、绿色、黑色),那么一个可能的数组式版本戳就类似 [blue:43, green:54, black: 12]

每当节点执行内部更新(internal update)作时,就将其计数器加 1;假设绿色节点执行了一次更新操作,那么现在这个数组式版本戳就成了 [blue:43, green:55, black:12]

只要两个节点通信,它们就同步其数组式版本戳。具体的同步方式有很多种。

使用此方案,就能辨别某个数组式版本戳是否比另外一个新,因为版本戳中的计数器总是大于或等于旧版本戳。比如,[blue:1, green:2, black:5] 就比 [blue:1, green:1, black:5] 新。

若两个版本戳中都有一个计数器比对方大,那么就发生了写入冲突。比如,[blue:1, green:2, black: 5][blue:2, green:1, black:5] 相冲突

数组中可能缺失某些值,我们将其视为 0。比如,[blue:6, black:2][blue:6, green:0, black:2] 等价。不需要弃用现有的数组式版本戳,就可以向其中轻易新增节点。

数组式版本戳是一种能够侦测出不一致现象的有用工具,然而它们无法解决此问题。要想解决冲突,就得依赖领域知识。

在一致性与延迟之间权衡时,如果偏向一致性,那么系统在出现网络分区现象时就无法使用;反之,若要减少延迟,则必须自己检测并处理不一致问题。