邮箱同步和 Exchange 中的 EWS

了解使用 EWS 访问 Exchange 时邮箱同步的工作原理。

Exchange 中的 EWS 使用两种类型的同步来检索邮箱内容和对邮箱内容的更改:

  • 文件夹同步
  • 项同步

在本文中,你将了解这两种类型的同步、同步的工作原理、同步设计模式和同步最佳做法。

文件夹和项同步

文件夹同步会同步文件夹结构或文件夹层次结构。 项同步会同步文件夹中的项。 同步项目时,必须独立同步邮箱中的每个文件夹。 可以在应用程序中使用 EWS 或 EWS 托管 API 来实现文件夹和项同步。

表 1. 用于同步文件夹和项目的 EWS 操作和 EWS 托管 API 方法

EWS 操作 EWS 托管 API 方法
SyncFolderHierarchy ExchangeService.SyncFolderHierarchy 方法
SyncFolderItems ExchangeService.SyncFolderItems 方法

发生的同步范围因它是初始同步还是正在进行的同步而异,如下所示:

  • 初始同步将服务器上的所有文件夹或项同步到客户端。 初始同步后,客户端具有一个同步状态,它存储该状态以供将来同步。 同步状态表示服务器与客户端通信的服务器上所有的更改。
  • 正在进行的同步会同步自上一次同步后添加、删除或更改的任何项目或文件夹。 服务器使用同步状态计算在每个正在进行的同步循环期间向客户端报告的更改。

每个同步方法或操作都会返回更改列表,而不是更改的实际文件夹或消息。 对项和文件夹的更改通过以下更改类型进行报告:

  • 创建 — 指示应在客户端上创建的新项或文件夹。
  • 更新 — 指示应在客户端上更改的项或文件夹。
  • 删除 — 指示应在客户端上删除的项或文件夹。
  • 适用于 EWS 的 ReadStateChange 或适用于 EWS 托管 API 的 ReadFlagChange —指示项的读取状态已从“未读”更改为“已读”或从“已读”更改为“未读”。

在 Exchange Online 中,Exchange Online 作为 Office 365 中的一部分,以及从 Exchange 2010 SP2 开始的 Exchange 版本,项目和文件夹按从最新到最早的顺序返回的。 在早期版本的 Exchange 中,项目和文件夹从旧版本返回到最新版本。

EWS 同步的工作原理是什么?

简而言之,如果是首次同步邮箱,请使用图 1 中所示的过程。 虽然可以使用其他 同步设计模式,但我们建议对可扩展的应用程序使用此方法。

图 1. 初始同步设计模式

此插图显示初始同步设计模式。客户端调用 SyncFolderHierarchy 和 Load 或 GetItem,获取文件夹,然后调用 SyncFolderItems 和 LoadPropertiesForItems 或 GetItem,获取每个文件夹中的项目。

如果在客户端上使用现有同步状态来同步邮箱,我们建议你实现图 2 所示的设计模式。

图 2. 持续同步设计模式

此插图显示正在进行的同步设计模式。客户端收到通知,然后调用 SyncFolderHierarchy 或 SyncFolderItems,获取属性,然后更新客户端,或者只更新客户端中的读取标记。

同步设计模式

可以使用应用程序中的两种同步设计模式之一来使邮箱保持最新: 基于通知的同步或仅同步方法。

图 2 所示,基于通知的同步依赖于通知,以提醒客户端调用 EWS Managed API 的 SyncFolderItemsSyncFolderHierarchy 方法,或者 EWS 的 SyncFolderHierarchySyncFolderItems 操作。 通常建议将这种类型的同步用于可扩展的应用程序,但它可能不是适合所有人的最佳方法。 基于通知的同步具有以下优势:

对通知进行了优化,以减少对后端 Exchange 数据库的调用。 事件队列和订阅由邮箱服务器 (或 Exchange 2010 和 Exchange 2007 中的客户端访问服务器) 管理;但是,事件和订阅的管理使用的资源少于替代资源,替代方案对 Exchange 数据库的同步调用更频繁。 此外,Exchange 还针对通知和订阅具有特定的 限制策略,以保护资源消耗。

但是,使用基于通知的同步也有一些缺点:

  • 通知是干扰性通知,因为大多数方案涉及一个用户意向的多个通知。 日历文件夹尤其如此。 例如,当收到单个会议请求时,将创建多个项目和文件夹通知,包括用于创建项的通知,以及另一个用于修改该项的通知。 缓解此缺点的一种方法是在 LoadLoadPropertiesForItemsGetItemGetFolder 调用中生成几秒钟的延迟。 对于会议请求,如果立即调用 GetItem 操作,则可以有一个调用来创建项目,另一个调用以修改项目。 相反,通过延迟调用,可以调用一次 GetItem 操作,同时获取包含项创建和修改的更改。
  • 通知在邮箱服务器上排队,订阅保存在邮箱服务器上。 如果管理订阅的邮箱服务器不可用,则会丢失任何新通知,邮箱将不会同步,并且必须重新订阅通知。
  • 如果通知失败,则需要规划缓解策略。 这样,第二种方法 (仅限同步的设计模式) 比基于通知的同步更具弹性,因为它只需要客户端保持同步状态 — 不存在任何与管理订阅的邮箱服务器的相关性问题。

如果按照建议实现,则基于通知的订阅设计模式依赖于:

  • 用于确定数据 何时 更改的通知。
  • EWS 托管 API SyncFolderHierarchySyncFolderItems 方法,或 EWS SyncFolderHierarchySyncFolderItems 操作来确定 更改的内容 ,从而优化返回的同步事件数。 是否已创建、更新或删除新项? 这就是你需要从这些方法中了解的全部信息,不要依赖它们来获取更改的属性列表。 (请勿执行 GetItemLoadPropertiesForItems 调用返回的所有项目或文件夹)。
  • 使用 EWS 托管 API 中的 LoadLoadPropertiesForItems 方法或 EWS GetItem 操作来确定数据 如何 更改,并根据需要从服务器检索属性,根据将返回的数据量组织批处理请求。 然后,对客户端上的属性和刚从服务器返回的属性进行比较,并最终在客户端上创建、删除或修改项或文件夹。

仅同步方法完全依赖于SyncFolderItemsSyncFolderHierarchy EWS 托管 API 方法,或 SyncFolderHierarchySyncFolderItems EWS 操作,可以连续调用,也可以作为计时事件调用。 此选项也有优点和缺点。 仅同步方法的复原能力更高,因为同步状态存储在邮箱级别的客户端上,并且不需要同步状态与维护通知订阅的任何邮箱服务器之间的唯一关系。 由于邮箱故障转移独立于邮箱服务器,因此同步方法可以在邮箱故障转移后继续运行。 但是,同步方法会增加用户的延迟,因为项目是按时间或间歇方式的基础上同步 — 不是在项目到达时实时同步。 此方法的成本也更高,因为当可能未发生任何更改的情况下正在调用 Exchange 数据库。

同步最佳做法

对于高度可扩展的应用程序,建议应用以下最佳做法来同步应用程序中的邮箱:

  • 调用 EWS 托管 API SyncFolderItemsSyncFolderHierarchy 方法时,请使用 propertySet 参数的 IdOnly 值, 或者在使用 EWS SyncFolderHierarchySyncFolderItems 操作时,请使用BaseShape 值的 IdOnly 值来减少对 Exchange 数据库的调用。 在 SyncFolderItemsSyncFolderHierarchy 调用中请求的属性越多,创建的后端调用就越多。 对请求的每个属性值进行新的 RPC 调用,而对于请求,仅进行一次 RPC 调用来检索所有 ItemIds - 无论要报告的结果数如何。 因此,IdOnly 请求会导致一个数据库调用, 而主题和发件人的属性包请求会导致三个数据库调用: 一个用于 主题,一个用于 发件人, 还有一个用于 ItemId

  • 请勿调用 EWS 托管 API 的 LoadLoadPropertiesForItems 方法, 或 EWS 的 GetItemGetFolder 操作, 对同步响应中的每个项执行此操作。 相反,分析结果; 查找不需要检索所有属性的更改,如读取状态更改。 如果响应包括读取状态更改,只需更新客户端上的标志即可; 无需获取所有项属性。 并确保不会因为进行源自同一客户的更改而重复工作。 例如,如果同步响应包括删除项,并且删除发生在本地客户端上,则无需再次删除该消息或获取该项的所有属性。

  • 通过执行以下操作,避免受到限制:

    • 当你调用 EWS 托管 API LoadPropertiesForItems 方法或 EWS GetItem 操作以获取批处理中的项时,请勿在请求中批处理过多的项目; 否则,可能会受到 限制。 建议每批包含 10 个项目。
    • 不要在太短的时间内发出过多的请求。 这也会导致限制,并增加响应时间,而不是缩短响应时间。
    • 如果要对项目进行批处理,请批处理所有具有相同的 FolderId 元素的 IdChangeKey 属性值的项目。
    • 如果受到限制,请停止发送请求。 重新发送请求将延长恢复工作。 相反,请等待回退时间过期,然后再次尝试发送同步请求。
  • 取决于收到的 通知事件 的类型:

    • 对于 NewMail修改 事件,请调用 EWS 托管 API SyncFolderItems 方法或 EWS SyncFolderItems 操作,因为通知不提供 ChangeKey,和通知不会调用读取状态更改。
    • 对于 删除 事件,如果通知订阅在上一次同步之前处于活动状态,只需在本地删除事件。 无需在删除后立即调用 EWS 托管 API SyncFolderItems 方法或 EWS SyncFolderItems 操作。
    • 如果 修改 事件是由读取状态更改引起的,请勿调用 EWS 托管 API LoadPropertiesForItems 方法或 EWS GetItem 操作,只需更改项上的标志。
  • 同步日历数据时,步骤如下:

    • 使用类似于基于通知的同步方法。 由于 SyncFolderItem 不包括任何日历逻辑,因此请使用 EWS 托管 API FindAppointments 方法,或 EWS FindItem 操作CalendarView 元素来查看两个日期之间的约会, 然后调用 EWS 托管 API LoadPropertiesForItems 方法或 EWS GetItem 操作来检索日历项的项属性。

    • 请勿使用 EWS 托管 API FindAppointments 方法,或 EWS FindItem操作与 CalendarView 元素进行轮询。

  • 同步搜索文件夹时:

    • 使用类似于基于通知的同步方法。

    • 使用通知确定数据何时更改。

    • 由于无法在搜索文件夹中使用 SyncFolderItem,因此请使用排序和分页的 EWS 托管 API FindItems方法,或设置 FractionalPageItemViewSortOrder 元素集的 EWS FindItem操作,以确定更改的内容。

    • 使用 EWS 托管 API LoadPropertiesForItems 方法或 EWS GetItem 操作来检索数据。

筛选的同步

EWS 托管 API SyncFolderItems 方法和 EWS SyncFolderItems 操作允许你能够根据其 ItemIds 忽略特定项, 通过在 EWS 托管 API 中设置 ignoreItemIds 参数或在 EWS 中设置 Ignore 元素。 例如,当个人开始回复发送给公司中所有人的电子邮件时,这是理想之选。

你可能想知道,如果特定属性发生更改 (因此仅触发同步),我是否可以筛选我的通知? 尽管这看起来是合理的,但由于通知订阅基于更改类型 (创建、更新、删除),而不是要更新的属性,因此无法以这种方式筛选通知。 相反,可以执行下列操作:

  • 使用基于通知的订阅设计模式。
  • 重复调用 EWS 托管 API SyncFolderItemsSyncFolderHierarchy 方法,并将 propertySet 参数设置为 IdOnly,以使同步状态成为最新状态。 或者,如果使用 EWS,请重复调用 SyncFolderHierarchySyncFolderItems 操作,并将 BaseShape 值设置为 IdOnly
  • 放弃响应 (不要对其进行分析或进行任何属性比较)。
  • 使用 EWS 托管 API FindItems 方法或 EWS FindItem 操作以及排序和页面来预填充你关注的筛选范围内的项目。
  • 使用同步状态继续调用 EWS 托管 API SyncFolderItems 方法或 EWS SyncFolderItems 操作, 但仅监视筛选项集中的更改。 如果创建了新项,则必须查看这些新项是否在筛选范围内。

本节内容

另请参阅