44. 复制或保存冲突之复制篇

Posted on 2013-07-27 21:29  冰天雪域  阅读(369)  评论(0编辑  收藏  举报

分布式数据库都存在复制的问题。对Notes数据库而言,复制尤为重要和基本。从理论上讲,复制要达到多个副本之间的数据完全一致的目的,或者将一个副本的数据完全覆盖另一个的数据,或者为了提高效率先检查副本之间的数据存在哪些差异,然后只写不同的部分。为了检查差异,有几种途径。首先是直接对比原始数据,这样做在效率上与不做比较直接覆盖没有多大优势。另一种是先生成原始数据的哈希值之类的短小的指纹数据,在对之进行比较。这样会增加额外的计算工作。最后一种就是比较数据的修改时间,这是比较粗略的方法,因为理论上副本之间的记录修改时间不同数据未必不同,而修改时间相同数据也可能不同。但是这样做最简单,速度也最快,Notes数据库的复制就采用了这种方式。也就是两份对应的数据(不论是什么级别),如果修改时间相同就认为数据相同,若时间不同则数据不同。
Notes数据库复制的详细过程在帮助文档中都有介绍。我们这里特别关心和主题有关的问题,也就是怎样判断副本之间的文档以至域存在冲突。
Notes每次复制成功都会在副本的复制历史里各保存一条记录,最重要的就是完成的时间。下次复制时,Notes会先搜索数据库里的文档,找到修改时间在上次复制的时间之后的集合。有时候我们遇到复制出现问题,副本之间的数据明显不一致,但是启动复制后很快显示完成,没有文档被同步。笔者的一个屡试不爽的办法就是将该数据库的复制历史删除,重新开始复制。
之所以提到复制历史,因为这个上次完成的时间在判断副本之间的数据的一致性时很重要。我们分两种情况来看决定是否有冲突的过程:
第一种是有上次成功复制时间这个参考值。Notes的复制器首先可以检查副本数据库的最近修改时间(NotesDatabase.LastModified),如果至少一方的最近修改时间晚于上次成功复制时间,就有数据需要复制;反之,则无。复制器再在改动过的副本里搜索最近修改时间(NotesDocument.LastModified)迟于复制时间的文档(不妨设其中一个文档为A),然后把它们与另一副本里的对应文档B做比较。如果B没有修改,就将A的改动写入B。如果B的最近修改时间也发生了变化,冲突就产生了。到这时,具体的处理又和这些文档所用的表单的一项设置有关,即怎样处理冲突,包括创建冲突文档、合并冲突、合并/丢弃冲突和丢弃冲突四种选项。注意这些选项是通过在使用该表单的文档里创建一个$ConflictActions域实现的,因此对旧文档修改了这项设置之后,需要再次用这个表单打开文档并保存才能生效。复制器检查$ConflictActions域的值,决定采取什么动作。默认的操作就是创建冲突文档。如果选择了合并冲突,Notes就开始进行字段级别的比较。我们知道Notes文档里有一个字段$Revisions,保存了最近一次以外的每次保存的时间。每个字段又有一个Seq Num值(可以在文档属性框的域列表页看到),对此Notes帮助文档的描述是” Refers to the amount of times the field has been edited. This is used with field level replication.”(奇怪的是至少在R6版本的帮助里有题为WORKING WITH DOCUMENTS的文章提到了这个值,而笔者为了写这篇文章去查时,在R9版本的帮助里却怎么也找不到了。)关键问题是这条简单的说明的第一句是错的,按照它的意思“指这个域被编辑的次数。”完全无法弄清楚我们现在讨论的字段级别的复制如何实现。实际上Seq Num的含义是字段被修改的序数。详细来说,当一个文档第一次被保存时,所有字段的Seq Num都为1。文档再次被保存时,记录每一次保存时间的$Revisions字段的Seq Num变为2,其他字段只有被修改了的,序数才增加为2。以后每次修改,$Revisions的序数都递增,其他字段如果发生变化,就变得和$Revisions的序数一样。也就是说,Seq Num记录的是字段最近一次的修改在整个文档的修改历史里的编号。这个序数和$Revisions记录的每次修改时间合在一起,就能确定每个字段的上次修改时间(NotesItem.LastModified)。Notes复制器就是利用这个信息来进行字段级别的复制。与上次成功复制时间相比,文档A和B里的字段F的最近修改时间,如果只有一个发生变化,就将这个新值写到B的域F里;如果两个都发生变化,那么就产生了字段级别的冲突,根据表单上的冲突设置,或者创建冲突文档,或者保存被选为主文档的域值,丢弃另一个。
第二种情况是没有上次成功复制时间这个参考值,也就是在复制历史被清除时。与第一种情况相比,复制的过程有部分差异。首先是检查数据库的上次修改时间这一步没有意义了。接下来要对副本里的所有文档进行比较。因为没有了能够确定上一次数据一致时的时间,当两个对应文档A和B的最近修改时间不同时,还不能断定发生了冲突,可能仅仅是某个时间以后A被修改了而B没有。所以需要比较两个文档的修改历史,即保存在$Revisions字段里的各次时间。如果一方的序列包含另一方的序列,那就说明前者在后者之后经过了更多的修改,因而只需要将前者的值写入后者即可。而如果双方的序列互不包含,那就说明各经历了不同的改动。这时候就可以参照第一种情况下字段级别的操作。同样也没有上次成功复制时间这个可以确保两个值相同的参照时间,并且字段也不像文档一样有每次修改的信息可以参考,只能根据上次修改时间,如果不同就产生了字段级别的冲突。
最后再说明一下数据库各个级别对象的最近修改时间和Seq Num。在Notes数据库里,这些时间和其他所有日期时间字段的值一样是以Domino特定的日期时间格式保存的,精确到百分之一秒,而上文列出以参考的LotusScript的各个对象的LastModified属性值是LotusScript里表示日期时间的Variant值,只保留到秒的信息。要根据修改时间来判断值是否相同,时间的进度当然高一点更准确。另外Seq Num只能用C API取得,在LotusScript里,可以获取到的是NotesItem.LastModified,相当于序数和$Revisions值综合后的结果。但是直接在程序里使用这个值并不可靠。在视图里计算某个文档的某个字段的最近修改时间是准确的,而在某个文档打开时获取该文档的某个字段的LastModified属性,则永远取得整个文档的最近修改时间。这个古怪,通过直接查看各个字段的Seq Num也可以发现。以只读状态打开一个文档,此时各序数值正确;进入编辑状态,都变成文档的修改次数;再保存,又恢复正确;手动刷新文档或运行一些脚本,再次都变为文档的修改次数。

Copyright © 2024 冰天雪域
Powered by .NET 9.0 on Kubernetes