月度存档: 三月 2010

ND 6.5.x 服务即将到期 + ND 8.5.1FP2 发布

ND 6.5.x 系列即将到期,这意味着 IBM 不再提供此版本的技术支持,同时也不会再有新的修补版本。还在使用此版本的,请抓紧时间升级。

ND 8.5.1FP2 就无需多说,推荐安装。

请参考:End of Service (EOS) of Lotus Enterprise Integrator, Notes, and Domino 6.5.xLotus Notes/Domino 8.5.1 Fix Pack 2 Release Notice

Domino 环境中的字符编码

大概在很早之前,我就知道在域中一个中文字符占三个字节,但是不知道其中的细节。最近在处理一个关于域内容超限的问题的时候,研究了一下字符编码,终于明白其中的原因。

在 Domino 环境下开发时,我们接触的字符编码主要有三种:

  • UTF-16:LotusScript 内部的字符编码,中文和英文都占两字节。
  • LMBCS(Lotus Multibyte Character Set):存到文档域中的值以及公式等使用,中文占三字节,英文占一字节。这是在 Unicode 标准推出之前,Lotus 为了解决多语言问题自己使用的编码方式。
  • 操作系统的编码方式:我们的 Domino 一般都跑在 GB2312 环境下,中文占两字节,英文占一字节。

UTF-16 和 GB2312 就不多说了,网上介绍的文章非常多。这里着重说明一下 LMBCS。

LMBCS 为了解决多语言问题,将字符分成了若干个组并将其分别编号,从 0×01 到 0x1F,其中 0×01 为 Latin-1(每个文字一字节),0×13 为简体中文(每个文字两字节)。然后每个字符都以自己的组编号开头,这样就实现了在一个字符串里面包含多种语言。例如“中文Test”以如下几个字节表示:

13 d6 d0 13 ce c4 01 74 01 65 01 73 01 74

其中加粗的部分为组编号。很明显这种访问有个很大的问题:每个字符都要标识其组编号,大大的浪费了空间。所以有了改良方案:每个程序(application file)有个默认的组编号,如果一个字符的组编号和默认的相同,则可以忽略不写(我没有找到设置这个组编号的方法,测试过程中它总是 0×01)。于是在默认组编号为 0×01 的情况下,上面的内容变成了这样:

13 d6 d0 13 ce c4 74 65 73 74

这样就有了中文占三字节,英文占一字节的结论。有了上面的背景知识,我们可以得到以下结果:

  • Len(“中文Test”) = 6,6 个字符
  • Lenb(“中文Test”) = 12,在 LotusScript 中占用 6×2 个字节
  • Lenbp(“中文Test”) = 8,按照操作系统的字符编码,占用 2×2+4 个字节
  • 值为”中文Test”的域大小为 10 (2×3+4)字节

顺便一提,由于 LotusScript 采用的编码与 Domino 环境中其他地方不同,所以每次读写域值、print 语句都会进行转换,所以减少这种转换可以提升性能。比如:

for i=1 to 100
    print i
next i

进行了 100 此编码转换,而

strTemp = ""
for i=1 to 100
    i = strTemp & i
next i
print strTemp

只进行了一次转换,所以性能上会有优势。

请参考:Computing Strings @Length

Domino 中的视图和索引器任务

视图在 Domino 环境中起着重要的作用,它将文档按照定义好的方式组织起来,作为展现给用户的数据或者查找数据的索引。而保持视图中的数据随时更新,是由一个重要的服务器任务:索引器(Indexer)完成的。

索引器任务由 Update、Updall(Update all)两个任务组成,它们通过调用 Notes Index Facility (NIF) 和 Full-Text services,保持各视图和全文索引内容为最新。本文仅讨论视图和索引器任务相关的部分,全文索引相关内容并未涉及。

Update 任务默认写在 Notes.ini 文件的 ServerTasks 参数中,所以它是随服务器一起启动的,持续不断地运行,检查其工作队列以查找需要更新的视图和文件夹。Update 保持两个工作队列:一个即时队列,一个延迟队列。其他服务器组件,例如路由器(Router)和复制器(Replicator),在数据库发生修改时将请求投递给 Update 任务。有些请求投递为延迟,有些请求投递为即时。此请求只包含数据库的完整路径,不包含视图名。延迟队列请求在被处理之前会保持 15 秒,以便在此时间内更新同一数据库的请求作为重复请求而被忽略,通过统一处理来提高性能。这个 15 秒的延迟时间可以通过 Update_Suppression_Time 参数修改。

工作队列中记录了哪些数据库需要更新索引,那么数据库中的具体哪个视图需要更新呢?在数据库的头信息里面,记录了数据库的修改时间;同时在视图的索引信息里面也记录了上次更新索引的时间。通过比较两个时间就可以判断哪些视图需要更新。

Updall 与 Update 类似,但它不是持续不断地运行,也不使用队列;而是根据需要运行。默认情况下,Updall 包括在 NOTES.INI 设置 ServerTasksAt2 中,因此它在每天凌晨 2 点运行。在运行 Updall 时您可以指定选项,但是如果不指定这些选项,则 Updall 会更新服务器上所有数据库中,需要更新的任何视图索引或全文搜索索引。为节省磁盘空间,Updall 还从数据库中清除删除存根(deletion stubs ),并删除符合条件视图的视图索引。

在视图属性的高级页签中,有两个和索引相关的选项:刷新、废弃。先看看刷新选项:

  • 第一次使用后自动刷新
    新建视图时的默认选项,也是一个功能和性能比较均衡的选项,无特殊情况下推荐使用。在用户第一次访问之前,索引器不会更新它,减小了无用视图对性能的影响。用户第一次访问时建立视图索引,所以这次访问会比较慢。不过一旦索引建立起来,索引器就会负责更新它,从而保证用户访问时无需等待。
  • 自动刷新
    与上一个类似,只是不管用户是否使用过它。这个选项对于用户的体验是最好的,因为打开视图时一般来说无需等待就能查看最新的数据;但对服务器来说压力是最大的,因为即使视图从没有人使用过,依然需要更新它的索引。情谨慎使用。
  • 手工刷新
    索引器不更新视图索引,完全依赖用户手工操作,对于服务器来说压力最小。用户打开视图时没有任何延迟,但是看到的数据可能不是最新的。此情况下视图左侧会显示刷新图标,提示用户数据并非最新的,用户点击图标或者按 F9 刷新数据。但对于浏览器应用来说,没有任何方式可以触发索引更新,所以无法使用。
  • 间隔 n 小时自动刷新
    手工刷新和自动刷新的混合体。如果距上次更新索引的时间超过了 n 小时,与自动刷新类似;反之与手工刷新类似。用于可预知更新频率的视图,比如每天晚上定时任务会创建文档,则可以设定为 24 小时刷新一次。

废弃选项:

  • 如果闲置 45 天
    45 天内没有任何用户访问此视图,则删除其索引。可以通过 Default_Index_Lifetime_Days 参数修改。
  • 每次使用后
    用了就删,下次用再重新创建。此方式对服务器压力会很大,一般很少用到,除非真的几乎是一次性使用。
  • 如果闲置 n 天
    与第一个选项类似。

请参考:The Indexer and Its FunctionalityThe basics of Notes view index refreshingUpdall switches, Update and NIF – Notes indexing basicsIndexer tasks: Update and Updall

慎用 7.0.2 中的 JSON 功能

之前介绍过 domino 7.0.2 引入了一个新的功能:以 JSON 格式返回视图的内容。当时我只是做了简单的测试,并没有用于实际项目。最近我在一个项目中使用了这个功能,没想到遇到了一个棘手的问题:服务器返回的 JSON 内容是以 GB2312 编码的,但是却没有在 Http Header 中声明。由此带来的结果就是,xmlHttpRequest 对象将其以默认的 UTF-8 编码处理,最终导致乱码等错误。而同样的代码在 Domino 8.0 上就没有问题,因为它返回的 JSON 是以 UTF-8 编码的。

找到问题的来源后,我通过 800 联系了 IBM,得到的答复是:此功能是在 8.0 版正式引入的,没有任何正式文档说明在 7.0.2 版中有此功能,所以无法进行技术支持。我查来查去才想起来,这个功能是在 Lotusphere 08 的一个 PPT 上看到的,还真没啥官方文档的说明。对于各种非正式文档记载的功能,大家用之前也要三思啊。

Domino 环境中各种 ID 的介绍

基于 Domino 平台的开发过程中,我们经常会用到各种 ID,如 UNID、Note ID、复本 ID 等等。本文简要介绍各种 ID 的组成以及用途。

在开始之前,我们首先来分析一下文档属性对话框-标识符标签中的各部分。以如下数据为例:

OF0000039D:3836C29F-ON85255DC9:0056FB94
SD00255DF4:0057B8FA-SN00000003
DB85255CD9:00567287-NT0000C092

其中红色部分为数据名称的简写,其余部分才是数据本身。各部分含义如下:

OF : Originator ID.File
虽然名字叫 File,其实是个完完全全的随机数(唯一),与数据库文件没有任何关系 – -

ON : Originator ID.Note
Note(可理解为文档,实际为文档、设计元素、ACL等。下同)最初的创建日期及时间

SD : Sequence Datetime
Note 的修改日期及时间

SN : Sequence Number
Note 的修改序列号,每修改一次加一

DB : Database ID
数据库的创建日期及时间

NT : Note ID
Note 在 Record Relocation Vector (RRV) 中的位置,可以理解为在数据库文件中的位置偏移

接下来我们再介绍各种常见的 ID 由以上哪些部分组成。

UNID(Universal ID, Unique ID) = OF+ON。UNID 是我们最常用的文档标识,它不随着文档的修改而改变。同一个文档在各个复本中具有相同的 UNID,反过来它也是确定两个文档是否为复本的根据。新建数据库拷贝后 UNID 也不变。编程过程中可以通过 getDocumentByUNID 方法来获取文档,在浏览器中也可以通过 db.nsf/view/UNID 的方式访问文档。根据我的测试,@Created 公式返回的值就是 ON 这个部分。

Note ID = NT。它也不随着文档的修改而改变,但是在不同的副本、新建数据库拷贝中,Note ID 可能与原数据库中的不同。编程过程中可以通过 getDocumentByID 方法来获取文档。

Replica ID = DB。复本 ID 用来识别两个数据库是否具有复本关系,除此之外复本 ID 还经常用来作为数据库的唯一标识,如浏览器中可以用复本 ID 代替数据库路径来访问文档、视图等。

Originator ID(OID) = UNID + SD + SN。OID 我们一般很少接触,因为它是用于后台的复制任务。复制过程大概如下(以数据库 A 向具有复本关系的数据库 B 发起单向复制为例):

  1. 从 A 的复制历史中找到与 B 完成最后一次复制的时间
  2. 在 A 中搜索在此时间之后新建及修改的文档,返回一组 OID 值
  3. 使用上一步返回的 OID 中的 UNID 部分作为条件,在 B 中查找文档,返回 OID 值
    1. 如果查找不到,则该文档视为新建的,添加到 B 中
    2. 如果两个 OID 相同,则两个文档已经同步,视为不需要复制
    3. 如果两个 OID 不同,则两个文档不同步,通过分析 $Revisions 域值判断是否存在冲突,通过 $ConflictAction 域判断是否合并冲突,通过分析 SD 和 SN 决定哪个将作为冲突文档(此过程较复杂,如果大家感兴趣,可以另开一篇文章讨论)

请参考:Anatomy of a Note ID

以浏览器方式保存文档时,更改 form 域的值

有些时候,我们会通过不同的表单显示同一个文档,以实现针对不同用户或场景的展现。但是一旦进行保存操作,文档的 form 域就会发生改变。以如下场景为例:

  1. 用户 A 通过 Form1 新建一个文档并保存,此时 form 的值为”From1″
  2. 用户 B 通过 Form2 打开此文档并保存,此时 form 域的值则变为”From2″

这样的话,本来应该属于同一类的文档,却有着不同的 form 域值。考虑到建立视图时的选择公式、搜索时的搜索条件的可维护性,我们希望将这些文档的 form 域值统一为”From1″。

首先想到的方法是在 Form2 上建立 form 计算域,将其计算公式写为”From1″,但是经测试此方法无效。可行的方法为将 Form2 的 WebQuerySave 公式写为 @SetField(“Form”;”From1″)。

请参考:Fix for modified Form field value is ignored when document is submitted from the Web

更改数据库的复本ID

项目中有的时候需要将某个数据库的复本关系切断,之前往往都是通过新建拷贝的方法获得一个新的复本ID。但如果要处理很多库的话,这样就很麻烦。好在 IBM 有提供相应的工具:ANTRID

请参考:How to change the replica ID of a database