使用 USNChanged 轮询更改

DirSync 控件功能强大且高效,但也存在两个明显的局限性:

  • 仅适用于高特权应用程序:要使用 DirSync 控件,应用程序必须在域控制器上具有 SE_SYNC_AGENT_NAME 权限的帐户下运行。 很少有帐户拥有如此高的特权,因此普通用户无法运行使用 DirSync 控件的应用程序。
  • 无子树范围:DirSync 控件会返回命名上下文中发生的所有更改。 如果应用程序只对命名上下文的一个小型子树中发生的更改感兴趣,就必须浏览许多无关的更改,这对应用程序和域控制器而言都是低效的。

还可以通过查询 uSNChanged 属性来获取 Active Directory 中的更改,从而避免 DirSync 控件的限制。 此替代方法在各方面都不如 DirSync 控件,因为它需要在任何属性发生变化时传输所有属性,而且需要应用程序开发人员做更多工作才能正确处理某些故障情况。 目前,它是编写某些更改跟踪应用程序的最佳方式。

当域控制器修改对象时,它会将该对象的 uSNChanged 属性设置为一个比该对象 uSNChanged 属性的上一个值更大的值,并且设置的值也大于该域控制器上持有的所有其他对象 uSNChanged 属性的当前值。 因此,应用程序可以通过查找具有最大 uSNChanged 值的对象来查找域控制器上最近更改的对象。 域控制器上最近更改次数第二多的对象将具有第二大的 uSNChanged 值,依此类推。

uSNChanged 属性不会被复制,因此在两个不同的域控制器上读取一个对象的 uSNChanged 属性通常都会得到不同的值。

例如,uSNChanged 属性可用于跟踪子树 S 中的更改。首先,对子树 S 执行一次“完全同步”。假设 S 中任何对象的最大 uSNChanged 值为 U。定期查询子树 S 中 uSNChanged 值大于 U 的所有对象。将 U 设置为这些已更改对象中最大的 uSNChanged 值,然后就可以再次轮询了。

实现 uSNChanged 同步应用程序的微妙之处包括:

  • 使用 rootDSE 的 highestCommittedUSN 属性绑定 uSNChanged 筛选器。 也就是说,在开始完全同步之前,要先读取附属域控制器的 highestCommittedUSN。 然后,执行一次完整的同步查询(使用分页结果)来初始化数据库。 完成此操作后,存储完全同步查询前读取的 highestCommittedUSN 值;作为下一次同步时 uSNChanged 属性的下限。 之后,要执行渐进式同步,请重新读取 highestCommittedUSN rootDSE 属性。 然后,使用分页结果查询相关对象,这些对象的 uSNChanged 大于上次同步保存的 uSNChanged 属性值的下限。 使用这些信息来更新数据库。 完成后,根据渐进式同步查询前读取的 highestCommittedUSN 值来更新 uSNChanged 属性的下限。 始终将 uSNChanged 属性值的下限存储在应用程序与域控制器内容同步的同一存储区中。

    遵循此过程,而不是基于检索对象上的 uSNChanged 值的程序,可避免服务器重新检索不属于适用于应用程序的集合的更新对象。

  • 由于 uSNChanged 是一个不可复制的属性,因此应用程序每次运行时都必须绑定到同一个域控制器。 如果无法与该域控制器绑定,则它必须等待,直到可以绑定为止,或者与某个新的域控制器绑定,并与该域控制器执行完全同步。 当应用程序隶属于域控制器时,它会在稳定存储区中记录该域控制器的 DNS 名称,也就是与域控制器内容保持一致的同一个存储区。 然后,它会使用存储的 DNS 名称绑定到同一域控制器,以进行后续同步。

  • 应用程序必须检测当前隶属的域控制器何时已从备份中恢复,因为这可能会导致不一致。 当应用程序隶属于域控制器时,它会将该域控制器的“调用 ID”缓存到稳定存储区,即与域控制器内容保持一致的存储区。 域控制器的“调用 ID”是一个 GUID,它存储在域控制器服务对象的 invocationID 属性中。 要获取域控制器服务对象的可分辨名称,请读取 rootDSE 的 dsServiceName 属性。

    请注意,当从备份恢复应用程序的稳定存储区时,不会出现一致性问题,这是因为域控制器名称、调用 ID 和 uSNChanged 属性值的下限与域控制器内容会同步存储。

  • 在查询服务器(包括完全同步和渐进式同步)时使用分页,以避免同时检索大型结果集的可能性。 有关详细信息,请参阅指定其他搜索选项

  • 执行基于索引的查询,以避免在使用分页结果时强制服务器存储大量中间结果。 有关详细信息,请参阅索引的属性

  • 通常,不要使用服务器端对搜索结果进行排序,否则会强制服务器对大量的中间结果进行存储和排序。 这同时适用于完全同步和渐进式同步。 有关详细信息,请参阅指定其他搜索选项

  • 正常处理任何父条件。 应用程序可能会在识别对象的父对象之前识别该对象。 根据不同的应用程序,这可能会成为一个问题。 应用程序可以随时从目录中读取父进程的当前状态。

  • 要处理移动或删除的对象,请存储每个跟踪对象的 objectGUID 属性。 无论对象在整个林中移动到哪个位置,其 objectGUID 属性都不会变。

  • 要处理已移动的对象,要么执行定期的完全同步,要么增大搜索范围并在客户端筛选掉不感兴趣的更改。

  • 要处理已删除的对象,要么定期执行完全同步,要么在执行渐进式同步时单独搜索已删除的对象。 在查询已删除的对象时,请检索已删除对象的 objectGUID 以确定要从数据库中删除的对象。 有关详细信息,请参阅检索已删除的对象

  • 请注意,搜索结果只包括调用方有权限读取的对象和属性(基于各种对象上的安全描述符和 DACL)。 有关详细信息,请参阅安全性对查询的影响

有关详细信息,以及展示 USNChanged 同步应用程序基础的代码示例,请参阅使用 USNChanged 检索更改的代码示例